From 1016904ad1fb2defb166b77a38a9a82d84662f86 Mon Sep 17 00:00:00 2001 From: Artur Tynecki <77382963+ATmobica@users.noreply.github.com> Date: Tue, 18 Jul 2023 21:00:15 +0200 Subject: [PATCH] [OIS] OTA requestor and DFU implementation (#27573) * [OIS] Add OTAImageProcessorImpl class Provides the implementation of the OTA Image Processor class for Open IOT SDK platform. Signed-off-by: ATmobica * [OIS] Add Matter OTA support Add Device Firmware Update manager class implementation. It provides firmware update functionality based on Matter OTA Requestor cluster. Add OTA enable support flag in Cmake build, passing it to Matter stack build. Add DFU manager to OIS platform code. Add post-build command to create OTA update image from signed binary. Extend OIS example documentation with TF-M and DFU support. Signed-off-by: ATmobica * [OIS] Add OTA Requestor application OIS implementation of OTA requestor example. Add new example to build script and VScode tasks. Signed-off-by: ATmobica * [OIS] Extend integration tests suite Add updateBinaryPath, otaProvider and softwareVersion options to OIS integraiton test suite. Propagating them to fixtures module. Add terminal device implementation. Run Linux applications as subprocess, read/write process output/input. Add ota_provider fixture - run OTA provider application as terminal device. Signed-off-by: ATmobica * [OIS] Add OTA requestor integration test and CI Add OTA requestor integration test implementation. Add OTA requestor test options in run script. Extending OIS workflow with build the OTA requestor example, build the OTA provider (Linux), test OTA requestor example. Signed-off-by: ATmobica --------- Signed-off-by: ATmobica --- .github/workflows/examples-openiotsdk.yaml | 32 +- config/openiotsdk/CMakeLists.txt | 1 + config/openiotsdk/cmake/chip.cmake | 1 + config/openiotsdk/cmake/sdk.cmake | 36 ++ docs/guides/README.md | 1 + docs/guides/openiotsdk_examples.md | 47 ++- .../openiotsdk_examples_software_update.md | 229 ++++++++++++ .../ota-requestor-app/openiotsdk/.gitignore | 1 + .../openiotsdk/CMakeLists.txt | 64 ++++ .../ota-requestor-app/openiotsdk/README.md | 81 +++++ .../openiotsdk/cmsis-config/RTE_Components.h | 22 ++ .../freertos-config/FreeRTOSConfig.h | 257 ++++++++++++++ .../main/include/CHIPProjectConfig.h | 26 ++ .../openiotsdk/main/main_ns.cpp | 64 ++++ .../openiotsdk/tf-m-config/TfmProjectConfig.h | 168 +++++++++ .../platform/openiotsdk/app/CMakeLists.txt | 17 + .../app/dfu/openiotsdk_dfu_manager.cpp | 68 ++++ .../app/dfu/openiotsdk_dfu_manager.h | 65 ++++ .../openiotsdk/app/openiotsdk_platform.cpp | 13 + .../openiotsdk/supported_examples.txt | 1 + scripts/examples/openiotsdk_example.sh | 12 + src/platform/openiotsdk/BUILD.gn | 7 + .../openiotsdk/OTAImageProcessorImpl.cpp | 331 ++++++++++++++++++ .../openiotsdk/OTAImageProcessorImpl.h | 95 +++++ .../integration-tests/common/fixtures.py | 38 ++ .../common/terminal_device.py | 95 +++++ .../openiotsdk/integration-tests/conftest.py | 6 + .../ota-requestor-app/__init__.py | 0 .../ota-requestor-app/test_app.py | 190 ++++++++++ 29 files changed, 1966 insertions(+), 2 deletions(-) create mode 100644 docs/guides/openiotsdk_examples_software_update.md create mode 100644 examples/ota-requestor-app/openiotsdk/.gitignore create mode 100644 examples/ota-requestor-app/openiotsdk/CMakeLists.txt create mode 100644 examples/ota-requestor-app/openiotsdk/README.md create mode 100644 examples/ota-requestor-app/openiotsdk/cmsis-config/RTE_Components.h create mode 100644 examples/ota-requestor-app/openiotsdk/freertos-config/FreeRTOSConfig.h create mode 100644 examples/ota-requestor-app/openiotsdk/main/include/CHIPProjectConfig.h create mode 100644 examples/ota-requestor-app/openiotsdk/main/main_ns.cpp create mode 100644 examples/ota-requestor-app/openiotsdk/tf-m-config/TfmProjectConfig.h create mode 100644 examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.cpp create mode 100644 examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.h create mode 100644 src/platform/openiotsdk/OTAImageProcessorImpl.cpp create mode 100644 src/platform/openiotsdk/OTAImageProcessorImpl.h create mode 100644 src/test_driver/openiotsdk/integration-tests/common/terminal_device.py create mode 100644 src/test_driver/openiotsdk/integration-tests/ota-requestor-app/__init__.py create mode 100644 src/test_driver/openiotsdk/integration-tests/ota-requestor-app/test_app.py diff --git a/.github/workflows/examples-openiotsdk.yaml b/.github/workflows/examples-openiotsdk.yaml index 338067b53cbe63..e81e15635e607d 100644 --- a/.github/workflows/examples-openiotsdk.yaml +++ b/.github/workflows/examples-openiotsdk.yaml @@ -49,7 +49,7 @@ jobs: - name: Checkout submodules & Bootstrap uses: ./.github/actions/checkout-submodules-and-bootstrap with: - platform: openiotsdk + platform: openiotsdk linux extra-submodule-parameters: " --recursive" - name: Set up environment for size reports @@ -101,11 +101,28 @@ jobs: examples/all-clusters-app/openiotsdk/build/chip-openiotsdk-all-clusters-app-example.elf \ /tmp/bloat_reports/ + - name: Build ota-requestor-app example + id: build_ota_requestor_app + timeout-minutes: 10 + run: | + scripts/examples/openiotsdk_example.sh -v 1 -V 0.0.1 ota-requestor-app + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + openiotsdk release ota-requestor-app \ + examples/ota-requestor-app/openiotsdk/build/chip-openiotsdk-ota-requestor-app-example.elf \ + /tmp/bloat_reports/ + - name: Build unit tests (mbedtls) id: build_unit_tests_mbedtls run: | scripts/examples/openiotsdk_example.sh -b mbedtls unit-tests + - name: Build the OTA provider (Linux) + id: build_ota_provider_app + if: steps.build_ota_requestor_app.outcome == 'success' + timeout-minutes: 10 + run: | + scripts/examples/gn_build_example.sh examples/ota-provider-app/linux/ out/ota-provider chip_config_network_layer_ble=false + - name: "Test: shell example" if: steps.build_shell.outcome == 'success' run: | @@ -138,6 +155,19 @@ jobs: 'scripts/run_in_ns.sh ${TEST_NETWORK_NAME}ns scripts/examples/openiotsdk_example.sh --no-activate -C test -n ${TEST_NETWORK_NAME}tap all-clusters-app' scripts/setup/openiotsdk/network_setup.sh -n $TEST_NETWORK_NAME down + - name: "Test: ota-requestor-app example" + if: steps.build_ota_requestor_app.outcome == 'success' && steps.build_ota_provider_app.outcome == 'success' + timeout-minutes: 30 + run: | + mkdir out/binaries + cp examples/ota-requestor-app/openiotsdk/build/chip-openiotsdk-ota-requestor-app-example.elf out/binaries/ + scripts/examples/openiotsdk_example.sh -c -v 2 -V 0.0.2 ota-requestor-app + cp examples/ota-requestor-app/openiotsdk/build/chip-openiotsdk-ota-requestor-app-example.ota out/binaries/ + scripts/setup/openiotsdk/network_setup.sh -n $TEST_NETWORK_NAME up + scripts/run_in_python_env.sh out/venv \ + 'scripts/run_in_ns.sh ${TEST_NETWORK_NAME}ns scripts/examples/openiotsdk_example.sh --no-activate -p out/binaries -v 2 -V 0.0.2 -C test -n ${TEST_NETWORK_NAME}tap ota-requestor-app' + scripts/setup/openiotsdk/network_setup.sh -n $TEST_NETWORK_NAME down + - name: "Test: unit-tests (mbedtls)" if: steps.build_unit_tests_mbedtls.outcome == 'success' run: | diff --git a/config/openiotsdk/CMakeLists.txt b/config/openiotsdk/CMakeLists.txt index eca153e3f59505..ad659d091c4fd3 100644 --- a/config/openiotsdk/CMakeLists.txt +++ b/config/openiotsdk/CMakeLists.txt @@ -64,6 +64,7 @@ matter_add_gn_arg_bool ("chip_error_logging" CONFIG_CHIP_ERRO matter_add_gn_arg_string("chip_crypto" "${CONFIG_CHIP_CRYPTO}") matter_add_gn_arg_string("chip_openiotsdk_software_version" "${CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION}") matter_add_gn_arg_string("chip_openiotsdk_software_version_string" "${CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION_STRING}") +matter_add_gn_arg_bool ("chip_enable_ota_requestor" CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE) if (TARGET cmsis-rtos-api) matter_add_gn_arg_string("target_os" "cmsis-rtos") endif() diff --git a/config/openiotsdk/cmake/chip.cmake b/config/openiotsdk/cmake/chip.cmake index 7b5d77fddf0fdb..1cb5a61cdeff02 100644 --- a/config/openiotsdk/cmake/chip.cmake +++ b/config/openiotsdk/cmake/chip.cmake @@ -35,6 +35,7 @@ set(CONFIG_CHIP_ERROR_LOGGING YES CACHE BOOL "Enable logging at error level") set(CONFIG_CHIP_CRYPTO "mbedtls" CACHE STRING "Matter crypto backend. Mbedtls as default") set(CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION "0" CACHE STRING "Software version number") set(CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION_STRING ${TFM_NS_APP_VERSION} CACHE STRING "Software version in string format x.x.x") +set(CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE NO CACHE BOOL "Enable OTA support") set(CONFIG_GN_DEPENDENCIES "") if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") diff --git a/config/openiotsdk/cmake/sdk.cmake b/config/openiotsdk/cmake/sdk.cmake index f4d151455c6eea..cee548ae457d19 100644 --- a/config/openiotsdk/cmake/sdk.cmake +++ b/config/openiotsdk/cmake/sdk.cmake @@ -300,6 +300,42 @@ function(sdk_post_build target) VERBATIM ) iotsdk_tf_m_merge_images(${target} 0x10000000 0x38000000 0x28060000) +if(CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE) + add_custom_command( + TARGET + ${target} + POST_BUILD + DEPENDS + $/${target}.bin + COMMAND + # Sign the update image + python3 ${BINARY_DIR}/install/image_signing/scripts/wrapper/wrapper.py + --layout ${BINARY_DIR}/install/image_signing/layout_files/signing_layout_ns.o + -v ${MCUBOOT_IMAGE_VERSION_NS} + -k ${BINARY_DIR}/install/image_signing/keys/root-RSA-3072_1.pem + --public-key-format full + --align 1 --pad-header -H 0x400 -s auto -d "(0, 0.0.0+0)" + $/${target}.bin + --overwrite-only + --measured-boot-record + $/${target}_signed.ota + COMMAND + # Create OTA udpate file + ${CHIP_ROOT}/src/app/ota_image_tool.py + create + -v 0xfff1 -p 0x8001 + -vn ${CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION} + -vs "${CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION_STRING}" + -da sha256 + $/${target}_signed.ota + $/${APP_NAME}.ota + # Cleanup + COMMAND rm + ARGS -Rf + $/${target}_signed.ota + VERBATIM + ) +endif() # Cleanup add_custom_command( TARGET diff --git a/docs/guides/README.md b/docs/guides/README.md index d37c959e3a602c..552f3a30772327 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -27,6 +27,7 @@ - [Open IoT SDK - Examples](./openiotsdk_examples.md) - [Open IoT SDK - Unit Tests](./openiotsdk_unit_tests.md) - [Open IoT SDK - Commissioning](./openiotsdk_commissioning.md) +- [Open IoT SDK - Software Update](./openiotsdk_examples_software_update.md) ## Development Guides diff --git a/docs/guides/openiotsdk_examples.md b/docs/guides/openiotsdk_examples.md index d779f34b6c5358..b80ce6304f9ba0 100644 --- a/docs/guides/openiotsdk_examples.md +++ b/docs/guides/openiotsdk_examples.md @@ -12,6 +12,7 @@ shell lock-app tv-app all-clusters-app +ota-requestor-app ``` You can use these examples as a reference for creating your own applications. @@ -291,7 +292,20 @@ Processing Environment (`NSPE`). The bootloader and the secure part are also built from `TF-M` sources. All components are merged into a single executable file at the end of the building process. -You can also provide the own version of Matter example by setting +The project-specific configuration of `TF-M` can be provide by defining its own +header file for `TF-M` config and passing the path to it via the +`TFM_PROJECT_CONFIG_HEADER_FILE` variable. + +``` +set(TFM_PROJECT_CONFIG_HEADER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/tf-m-config/TfmProjectConfig.h") +``` + +If the project-specific configuration is not provided the base `TF-M` settings +are used +[config_base.h](https://git.trustedfirmware.org/TF-M/trusted-firmware-m.git/tree/config/config_base.h). +It can be used as a pattern for the custom configuration header. + +You can also provide your own version of a Matter example by setting the `TFM_NS_APP_VERSION` variable. ``` @@ -365,6 +379,37 @@ cmake -G <...> -DCONFIG_CHIP_CRYPTO= <...> > The `TF-M PSA crypto` option requires enabling [TF-M](#trusted-firmware-m) > support. +### Device Firmware Update + +Device Firmware Update (`DFU`) can be enabled in the application by setting the +`CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE` variable: + +``` +set(CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE YES) +``` + +This provides the proper service for Matter's `OTA Requestor` cluster. The +[TF-M Firmware Update Service](https://arm-software.github.io/psa-api/fwu/1.0/) +is the backend for all firmware update operations. The `DFU Manager` module is +attached to the application and allows full usage of the `OTA Requestor` +cluster. + +You can also provide your own version of the Matter example to the Matter stack +by setting `CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION` and +`CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION_STRING` variables. + +``` +set(CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION "1") +set(CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION_STRING "0.0.1") +``` + +The default value for `CONFIG_CHIP_OPEN_IOT_SDK_SOFTWARE_VERSION_STRING` is set +to `TFM_NS_APP_VERSION`. + +> 💡 **Notes**: +> +> The `DFU` option requires enabling [TF-M](#trusted-firmware-m) support. + ## Building You can build examples using the dedicated VSCode task or by calling directly diff --git a/docs/guides/openiotsdk_examples_software_update.md b/docs/guides/openiotsdk_examples_software_update.md new file mode 100644 index 00000000000000..73129b84e50966 --- /dev/null +++ b/docs/guides/openiotsdk_examples_software_update.md @@ -0,0 +1,229 @@ +# Matter Open IoT SDK Example Device Firmware Upgrade + +Matter Open IoT SDK examples can support over-the-air (`OTA`) Device Firmware +Upgrade (`DFU`) based on Matter-compliant OTA update protocol that uses the +Matter operational network for querying and downloading a new firmware image. + +## Device Firmware Upgrade over Matter OTA cluster + +The `DFU` over Matter OTA clusters requires two kinds of nodes: + +- `OTA Provider` is an application that implements the OTA provider cluster + service. It can respond to the OTA Requestors' queries about available + software updates and share the update image with them. +- `OTA Requestor` is an application that implements the OTA requestor cluster + service. It can communicate with the OTA Provider to fetch applicable + software updates and apply them. + +The last required element is a Matter controller. This application controls both +nodes and manages the entire software update process. + +In the procedure described below, the `OTA Provider` will be a +[Linux application](../../examples/ota-provider-app/linux/README.md) and the +Open IoT SDK example with +[DFU support](./openiotsdk_examples.md#device-firmware-update) will work as the +OTA Requestor. The [chip-tool](../../examples/chip-tool/README.md) application +used as the Matter controller. Each application should be launched in a separate +terminal. + +List of `OIS` examples that currently support the `DFU` over Matter: + +- ota-requestor-app + +### Device Firmware Upgrade over Matter procedure + +1. Navigate to the CHIP root directory. + +2. Build the `OTA Provider` application: + + ``` + scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/ota-provider chip_config_network_layer_ble=false + ``` + + More details about the `OTA provider` application can be found + [here](../../examples/ota-provider-app/linux/README.md). + +3. Build `chip-tool`: + + ``` + scripts/examples/gn_build_example.sh examples/chip-tool out/chip-tool + ``` + + More details about the `chip-tool` application can be found + [here](../../examples/chip-tool/README.md). + +4. Build `OIS` example application + + ``` + scripts/examples/openiotsdk_example.sh -v 1 -V 0.0.1 + ``` + + This is first version of the application that can be updated. + +5. Build `OIS` update image + + ``` + scripts/examples/openiotsdk_example.sh -p out/update -v 2 -V 0.0.2 + ``` + + Pass the build path (`-p out/update`) in this step to not override the + example application. In that directory you should find the update image file + with `.ota` extension (`chip-openiotsdk--example.ota`). + +6. Setup `OIS` network environment + + ``` + sudo ./scripts/setup/openiotsdk/network_setup.sh -n OISupdate up + ``` + + More details about `OIS` network environment can be found + [here](./openiotsdk_examples.md#networking-setup). + +7. In `terminal 1`: run the `OTA Provider` application in the network + environment and provide a path to the update image: + + ``` + scripts/run_in_ns.sh OISupdate out/ota-provider/chip-ota-provider-app --KVS /tmp/chip-ota-provider --discriminator 3841 --secured-device-port 5580 --filepath out/update/chip-openiotsdk--example.ota + ``` + + The node details should be printed: + + ``` + ... + CHIP:DL: Device Configuration: + CHIP:DL: Serial Number: TEST_SN + CHIP:DL: Vendor Id: 65521 (0xFFF1) + CHIP:DL: Product Id: 32769 (0x8001) + CHIP:DL: Hardware Version: 0 + CHIP:DL: Setup Pin Code (0 for UNKNOWN/ERROR): 20202021 + CHIP:DL: Setup Discriminator (0xFFFF for UNKNOWN/ERROR): 3841 (0xF01) + CHIP:DL: Manufacturing Date: (not set) + CHIP:DL: Device Type: 65535 (0xFFFF) + CHIP:SVR: SetupQRCode: [MT:-24J0IRV01KA0648G00] + CHIP:SVR: Copy/paste the below URL in a browser to see the QR Code: + CHIP:SVR: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3A-24J0IRV01KA0648G00 + CHIP:SVR: Manual pairing code: [34970112332] + CHIP:SWU: Using OTA file: out/update/chip-openiotsdk-ota-requestor-app-example.ota + ... + ``` + + > 💡 **Notes**: + > + > Set the custom Key Value Store path for `OTA provider`. + > + > The `OTA provider` discriminator must be different from the value used by + > the example application. `OIS` examples use the default discriminator + > value `3840`. + > + > The `OTA provider` UDP port must be different from the value used by the + > example application. `OIS` examples use the default discriminator value + > `5540`. + +8. In `terminal 2`: run the `OIS` example application in the network + environment: + + ``` + scripts/run_in_ns.sh OISupdate scripts/examples/openiotsdk_example.sh -C run -n OISupdatetap + ``` + + The node details should be printed to terminal: + + ``` + ... + [INF] [DL] Device Configuration: + [INF] [DL] Serial Number: TEST_SN + [INF] [DL] Vendor Id: 65521 (0xFFF1) + [INF] [DL] Product Id: 32769 (0x8001) + [INF] [DL] Hardware Version: 0 + [INF] [DL] Setup Pin Code (0 for UNKNOWN/ERROR): 20202021 + [INF] [DL] Setup Discriminator (0xFFFF for UNKNOWN/ERROR): 3840 (0xF00) + [INF] [DL] Manufacturing Date: (not set) + [INF] [DL] Device Type: 65535 (0xFFFF) + [INF] [SVR] SetupQRCode: [MT:-24J0AFN00KA0648G00] + [INF] [SVR] Copy/paste the below URL in a browser to see the QR Code: + [INF] [SVR] https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3A-24J0AFN00KA0648G00 + [INF] [SVR] Manual pairing code: [34970112332] + [INF] [-] Current software version: [1] 0.0.1 + [INF] [SWU] Stopping the watchdog timer + [INF] [SWU] Starting the periodic query timer, timeout: 86400 seconds + [INF] [-] Open IoT SDK ota-requestor-app example application run + ... + ``` + +9. In `terminal 3`: commission the `OTA Provider` application into the Matter + network: + + ``` + scripts/run_in_ns.sh OISupdate ./out/chip-tool/chip-tool pairing onnetwork-long 123 20202021 3841 + ``` + + Set node ID to `123`. + + The confirmation of commissioning success will be printed on the terminal: + + ``` + CHIP:SVR: Commissioning completed successfully + ``` + +10. In `terminal 3`: commission the `OIS` example application into the Matter + network: + + ``` + scripts/run_in_ns.sh OISupdate ./out/chip-tool/chip-tool pairing onnetwork-long 321 20202021 3840 + ``` + + Set node ID to `321`. + + The confirmation of commissioning success will be printed on the terminal: + + ``` + [INF] [SVR] Commissioning completed successfully + ``` + +11. In `terminal 3`: configure the `OTA Provider` with the access control list + (ACL) that grants _Operate_ privileges to all nodes in the fabric. This is + necessary to allow the nodes to send cluster commands to the `OTA Provider`: + + ``` + scripts/run_in_ns.sh OISupdate ./out/chip-tool/chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [321], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": null, "targets": null}]' 123 0 + ``` + +12. Initiate the `DFU` procedure: + + In `terminal 3`: send the `Announce OTA Provider` command to the example + application. The numeric arguments are: provider node ID, provider vendor + ID, announcement reason, provider endpoint ID, requestor node ID and + requestor Endpoint Id, respectively. + + ``` + scripts/run_in_ns.sh OISupdate ./out/chip-tool/chip-tool otasoftwareupdaterequestor announce-otaprovider 123 0 2 0 321 0 + ``` + + The example application is notified of the `OTA Provider` node and it + automatically queries for a new firmware image. If a new version of firmware + is available, the downloading step starts. + +13. When the firmware image download is complete, the device is automatically + rebooted to apply the update. + + The new version of firmware will be printed in the log `terminal 2`: + + ``` + ... + [INF] [-] Current software version: [2] 0.0.2 + ... + ``` + +14. Now, you can manually close the nodes applications and terminals sessions: + + - `OTA Provider` - `terminal 1` + - `OIS` example - `terminal 2`. More details about terminating `OIS` + example can be found [here](./openiotsdk_examples.md#running). + +15. Cleanup after update (disable network environment, remove `KVS` storage + files): + + ``` + sudo ./scripts/setup/openiotsdk/network_setup.sh -n OISupdate down + sudo rm /tmp/chip-ota-provider + ``` diff --git a/examples/ota-requestor-app/openiotsdk/.gitignore b/examples/ota-requestor-app/openiotsdk/.gitignore new file mode 100644 index 00000000000000..567609b1234a9b --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/examples/ota-requestor-app/openiotsdk/CMakeLists.txt b/examples/ota-requestor-app/openiotsdk/CMakeLists.txt new file mode 100644 index 00000000000000..a8b178aa5df9c9 --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/CMakeLists.txt @@ -0,0 +1,64 @@ +# +# Copyright (c) 2022 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +cmake_minimum_required(VERSION 3.21) + +get_filename_component(CHIP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../.. REALPATH) +get_filename_component(OPEN_IOT_SDK_CONFIG ${CHIP_ROOT}/config/openiotsdk REALPATH) +get_filename_component(OPEN_IOT_SDK_EXAMPLE_COMMON ${CHIP_ROOT}/examples/platform/openiotsdk REALPATH) + +list(APPEND CMAKE_MODULE_PATH ${OPEN_IOT_SDK_CONFIG}/cmake) + +set(APP_TARGET chip-openiotsdk-ota-requestor-app-example_ns) + +set(TFM_PROJECT_CONFIG_HEADER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/tf-m-config/TfmProjectConfig.h") +set(CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE YES) + +# Toolchain files need to exist before first call to project +include(toolchain) + +project(${APP_TARGET} LANGUAGES C CXX ASM) + +include(sdk) + +add_executable(${APP_TARGET}) + +# Application CHIP build configuration +include(chip) + +add_subdirectory(${OPEN_IOT_SDK_EXAMPLE_COMMON}/app ./app_build) + +chip_add_data_model(openiotsdk-app PUBLIC ota-requestor) + +target_include_directories(${APP_TARGET} + PRIVATE + main/include +) + +target_sources(${APP_TARGET} + PRIVATE + main/main_ns.cpp +) + +target_link_libraries(${APP_TARGET} + openiotsdk-startup + openiotsdk-app +) + +include(linker) +set_target_link(${APP_TARGET}) + +sdk_post_build(${APP_TARGET}) diff --git a/examples/ota-requestor-app/openiotsdk/README.md b/examples/ota-requestor-app/openiotsdk/README.md new file mode 100644 index 00000000000000..d9a98c44d3888a --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/README.md @@ -0,0 +1,81 @@ +# Matter Open IoT SDK OTA-Requestor-App Example Application + +The Open IoT SDK OTA-Requestor Example demonstrates how to remotely trigger +update image downloading and apply it if needed. It provides the service for +Matter's `OTA` clusters. This application plays both roles: the server for the +`OTA Requestor` cluster, and the client of the `OTA Provider` cluster. It can +initiate a software update with a given `OTA Provider` node, download a binary +file and apply it. + +The application is configured to support: + +- [TF-M](../../../docs/guides/openiotsdk_examples.md#trusted-firmware-m) +- [Device Firmware Update](../../../docs/guides/openiotsdk_examples.md#device-firmware-update) + +The example behaves as a Matter accessory, device that can be paired into an +existing Matter network and can be controlled by it. + +## Build-run-test-debug + +For information on how to build, run, test and debug this example and further +information about the platform it is run on see +[Open IoT SDK examples](../../../docs/guides/openiotsdk_examples.md). + +The example name to use in the scripts is `ota-requestor-app`. + +## Example output + +When the example runs, these lines should be visible: + +``` +[INF] [-] Open IoT SDK ota-requestor-app example application start +... +[INF] [-] Open IoT SDK ota-requestor-app example application run +``` + +This means the ota-requestor-app application launched correctly and you can +follow traces in the terminal. + +### Commissioning + +Read the +[Open IoT SDK commissioning guide](../../../docs/guides/openiotsdk_commissioning.md) +to see how to use the Matter controller to commission and control the +application. + +### Device Firmware Upgrade + +Read the +[Matter Open IoT SDK Example Device Firmware Upgrade](../../../docs/guides/openiotsdk_examples_software_update.md) +to see how to use Matter OTA for firmware update. + +### OtaSoftwareUpdateRequestor cluster usage + +The application fully supports the `OTA Requestor` cluster. Use its commands to +trigger actions on the device. You can issue commands through the same Matter +controller you used to perform the commissioning step above. + +Example command: + +``` +chip-tool otasoftwareupdaterequestor announce-ota-provider 1234 0 2 0 4321 0 +``` + +The `OTA Requestor` application with node ID 1234 will process this command and +send a `QueryImage` command to the `OTA Provider` with node ID 4321. This starts +the `OTA` process. On receiving the `QueryImageResponse` from the `OTA Provider` +application, the `OTA Requestor` application will verify that the software +version specified in the `SoftwareVersion` field of the response contains a +value newer than the current running version. The available version will be +printed to the terminal, for example: + +``` +[INF] [-] New version of the software is available: 2 +``` + +If the validation does not pass the update will not proceed. The next step is +downloading the update image. If this step is completed, a new image will be +installed and the application will be reboot. + +More details about device firmware update over Matter can be found +[here](../../../docs/guides/openiotsdk_examples_software_update.md). diff --git a/examples/ota-requestor-app/openiotsdk/cmsis-config/RTE_Components.h b/examples/ota-requestor-app/openiotsdk/cmsis-config/RTE_Components.h new file mode 100644 index 00000000000000..e86df2b4e44e06 --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/cmsis-config/RTE_Components.h @@ -0,0 +1,22 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RTE_COMPONENTS_H +#define RTE_COMPONENTS_H + +#endif // RTE_COMPONENTS_H diff --git a/examples/ota-requestor-app/openiotsdk/freertos-config/FreeRTOSConfig.h b/examples/ota-requestor-app/openiotsdk/freertos-config/FreeRTOSConfig.h new file mode 100644 index 00000000000000..9efedc9f133712 --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/freertos-config/FreeRTOSConfig.h @@ -0,0 +1,257 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +/*----------------------------------------------------------- + * Application specific definitions. + * + * These definitions should be adjusted for your particular hardware and + * application requirements. + * + * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE + * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. + * + * See http://www.freertos.org/a00110.html + *----------------------------------------------------------*/ + +#if (defined(__ARMCC_VERSION) || defined(__GNUC__) || defined(__ICCARM__)) +#include + +extern uint32_t SystemCoreClock; +#endif + +// Minimal stack size [words] <0-65535> +// Stack for idle task and default task stack in words. +// Default: 4kB +#define configMINIMAL_STACK_SIZE ((uint16_t)(4 * 1024)) + +// Total heap size [bytes] <0-0xFFFFFFFF> +// Heap memory size in bytes. +// Default: 32kB +#define configTOTAL_HEAP_SIZE ((size_t)(32 * 1024)) + +// Kernel tick frequency [Hz] <0-0xFFFFFFFF> +// Kernel tick rate in Hz. +// Default: 1000 +#define configTICK_RATE_HZ ((TickType_t) 1000) + +// Timer task stack depth [words] <0-65535> +// Stack for timer task in words. +// Default: 80 +#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE + +// Timer task priority <0-56> +// Timer task priority. +// Default: 40 (High) +#define configTIMER_TASK_PRIORITY 40 + +// Timer queue length <0-1024> +// Timer command queue length. +// Default: 5 +#define configTIMER_QUEUE_LENGTH 5 + +// Preemption interrupt priority +// Maximum priority of interrupts that are safe to call FreeRTOS API. +// Default: 16 +#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << (8 - configPRIO_BITS)) + +// Use time slicing +// Enable setting to use timeslicing. +// Default: 1 +#define configUSE_TIME_SLICING 1 + +// Idle should yield +// Control Yield behaviour of the idle task. +// Default: 1 +#define configIDLE_SHOULD_YIELD 1 + +// Check for stack overflow +// <0=>Disable <1=>Method one <2=>Method two +// Enable or disable stack overflow checking. +// Callback function vApplicationStackOverflowHook implementation is required when stack checking is enabled. +// Default: 0 +#define configCHECK_FOR_STACK_OVERFLOW 2 + +// Use idle hook +// Enable callback function call on each idle task iteration. +// Callback function vApplicationIdleHook implementation is required when idle hook is enabled. +// Default: 0 +#define configUSE_IDLE_HOOK 0 + +// Use tick hook +// Enable callback function call during each tick interrupt. +// Callback function vApplicationTickHook implementation is required when tick hook is enabled. +// Default: 0 +#define configUSE_TICK_HOOK 0 + +// Use deamon task startup hook +// Enable callback function call when timer service starts. +// Callback function vApplicationDaemonTaskStartupHook implementation is required when deamon task startup hook is +// enabled. Default: 0 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +// Use malloc failed hook +// Enable callback function call when out of dynamic memory. +// Callback function vApplicationMallocFailedHook implementation is required when malloc failed hook is enabled. +// Default: 0 +#define configUSE_MALLOC_FAILED_HOOK 0 + +// Queue registry size +// Define maximum number of queue objects registered for debug purposes. +// The queue registry is used by kernel aware debuggers to locate queue and semaphore structures and display +// associated text names. Default: 0 +#define configQUEUE_REGISTRY_SIZE 0 + +// Event Recorder configuration +// Initialize and setup Event Recorder level filtering. +// Settings have no effect when Event Recorder is not present. + +// Initialize Event Recorder +// Initialize Event Recorder before FreeRTOS kernel start. +// Default: 1 +#define configEVR_INITIALIZE 1 + +// Setup recording level filter +// Enable configuration of FreeRTOS events recording level +// Default: 1 +#define configEVR_SETUP_LEVEL 1 + +// Tasks functions +// Define event recording level bitmask for events generated from Tasks functions. +// Default: 0x05 +// <0x00=>Off <0x01=>Errors <0x05=>Errors + Operation <0x0F=>All +#define configEVR_LEVEL_TASKS 0x05 + +// Queue functions +// Define event recording level bitmask for events generated from Queue functions. +// Default: 0x05 +// <0x00=>Off <0x01=>Errors <0x05=>Errors + Operation <0x0F=>All +#define configEVR_LEVEL_QUEUE 0x05 + +// Timer functions +// Define event recording level bitmask for events generated from Timer functions. +// Default: 0x05 +// <0x00=>Off <0x01=>Errors <0x05=>Errors + Operation <0x0F=>All +#define configEVR_LEVEL_TIMERS 0x05 + +// Event Groups functions +// Define event recording level bitmask for events generated from Event Groups functions. +// Default: 0x05 +// <0x00=>Off <0x01=>Errors <0x05=>Errors + Operation <0x0F=>All +#define configEVR_LEVEL_EVENTGROUPS 0x05 + +// Heap functions +// Define event recording level bitmask for events generated from Heap functions. +// Default: 0x05 +// <0x00=>Off <0x01=>Errors <0x05=>Errors + Operation <0x0F=>All +#define configEVR_LEVEL_HEAP 0x05 + +// Stream Buffer functions +// Define event recording level bitmask for events generated from Stream Buffer functions. +// Default: 0x05 +// <0x00=>Off <0x01=>Errors <0x05=>Errors + Operation <0x0F=>All +#define configEVR_LEVEL_STREAMBUFFER 0x05 +// +// + +// Port Specific Features +// Enable and configure port specific features. +// Check FreeRTOS documentation for definitions that apply for the used port. + +// Use Floating Point Unit +// Using Floating Point Unit (FPU) affects context handling. +// Enable FPU when application uses floating point operations. +// Default: 1 +#define configENABLE_FPU 1 + +// Use Memory Protection Unit +// Using Memory Protection Unit (MPU) requires detailed memory map definition. +// This setting is only releavant for MPU enabled ports. +// Default: 0 +#define configENABLE_MPU 0 + +// Use TrustZone Secure Side Only +// This settings prevents FreeRTOS contex switch to Non-Secure side. +// Enable this setting when FreeRTOS runs on the Secure side only. +#define configRUN_FREERTOS_SECURE_ONLY 0 + +// Use TrustZone Security Extension +// Using TrustZone affects context handling. +// Enable TrustZone when FreeRTOS runs on the Non-Secure side and calls functions from the Secure side. +// Default: 1 +#define configENABLE_TRUSTZONE 0 + +// Minimal secure stack size [words] <0-65535> +// Stack for idle task Secure side context in words. +// This setting is only relevant when TrustZone extension is enabled. +// Default: 128 +#define configMINIMAL_SECURE_STACK_SIZE ((uint32_t) 128) +// + +#ifdef __NVIC_PRIO_BITS +#define configPRIO_BITS __NVIC_PRIO_BITS +#else +#define configPRIO_BITS 4 +#endif + +//------------- <<< end of configuration section >>> --------------------------- + +/* Defines needed by FreeRTOS to implement CMSIS RTOS2 API. Do not change! */ +#define configCPU_CLOCK_HZ (SystemCoreClock) +#define configSUPPORT_STATIC_ALLOCATION 1 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configUSE_PREEMPTION 1 +#define configUSE_TIMERS 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configUSE_TRACE_FACILITY 1 +#define configUSE_16_BIT_TICKS 0 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configMAX_PRIORITIES 56 +#define configKERNEL_INTERRUPT_PRIORITY (0x07 << (8 - configPRIO_BITS)) + +/* Defines that include FreeRTOS functions which implement CMSIS RTOS2 API. Do not change! */ +#define INCLUDE_xEventGroupSetBitsFromISR 1 +#define INCLUDE_xSemaphoreGetMutexHolder 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskDelayUntil 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_xTimerPendFunctionCall 1 + +/* Map the FreeRTOS port interrupt handlers to their CMSIS standard names. */ +#define xPortPendSVHandler PendSV_Handler +#define vPortSVCHandler SVC_Handler + +/* Ensure Cortex-M port compatibility. */ +#define SysTick_Handler xPortSysTickHandler + +#include "RTE_Components.h" +#include CMSIS_device_header + +#endif /* FREERTOS_CONFIG_H */ diff --git a/examples/ota-requestor-app/openiotsdk/main/include/CHIPProjectConfig.h b/examples/ota-requestor-app/openiotsdk/main/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..6a1fe3b8068078 --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/main/include/CHIPProjectConfig.h @@ -0,0 +1,26 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 0 +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP 0 + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 diff --git a/examples/ota-requestor-app/openiotsdk/main/main_ns.cpp b/examples/ota-requestor-app/openiotsdk/main/main_ns.cpp new file mode 100644 index 00000000000000..31002663cc001a --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/main/main_ns.cpp @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "openiotsdk_platform.h" + +int main() +{ + ChipLogProgress(NotSpecified, "Open IoT SDK ota-requestor-app example application start"); + + if (openiotsdk_platform_init()) + { + ChipLogError(NotSpecified, "Open IoT SDK platform initialization failed"); + return EXIT_FAILURE; + } + + if (openiotsdk_chip_init()) + { + ChipLogError(NotSpecified, "Open IoT SDK CHIP stack initialization failed"); + return EXIT_FAILURE; + } + + if (openiotsdk_network_init(true)) + { + ChipLogError(NotSpecified, "Network initialization failed"); + return EXIT_FAILURE; + } + + if (openiotsdk_chip_run()) + { + ChipLogError(NotSpecified, "CHIP stack run failed"); + return EXIT_FAILURE; + } + + ChipLogProgress(NotSpecified, "Open IoT SDK ota-requestor-app example application run"); + + while (true) + { + // Add forever delay to ensure proper workload for this thread + osDelay(osWaitForever); + } + + openiotsdk_chip_shutdown(); + + return EXIT_SUCCESS; +} diff --git a/examples/ota-requestor-app/openiotsdk/tf-m-config/TfmProjectConfig.h b/examples/ota-requestor-app/openiotsdk/tf-m-config/TfmProjectConfig.h new file mode 100644 index 00000000000000..c32bddaba40d20 --- /dev/null +++ b/examples/ota-requestor-app/openiotsdk/tf-m-config/TfmProjectConfig.h @@ -0,0 +1,168 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TFM_PROJECT_CONFIG_H +#define TFM_PROJECT_CONFIG_H + +/* Platform Partition Configs */ + +/* Size of input buffer in platform service */ +#define PLATFORM_SERVICE_INPUT_BUFFER_SIZE 64 + +/* Size of output buffer in platform service */ +#define PLATFORM_SERVICE_OUTPUT_BUFFER_SIZE 64 + +/* The stack size of the Platform Secure Partition */ +#define PLATFORM_SP_STACK_SIZE 0x500 + +/* Disable Non-volatile counter module */ +#define PLATFORM_NV_COUNTER_MODULE_DISABLED 0 + +/* Crypto Partition Configs */ + +/* + * Heap size for the crypto backend + * CRYPTO_ENGINE_BUF_SIZE needs to be >8KB for EC signing by attest module. + */ +#define CRYPTO_ENGINE_BUF_SIZE 0x2080 + +/* The max number of concurrent operations that can be active (allocated) at any time in Crypto */ +#define CRYPTO_CONC_OPER_NUM 8 + +/* Enable PSA Crypto random number generator module */ +#define CRYPTO_RNG_MODULE_ENABLED 1 + +/* Enable PSA Crypto Key module */ +#define CRYPTO_KEY_MODULE_ENABLED 1 + +/* Enable PSA Crypto AEAD module */ +#define CRYPTO_AEAD_MODULE_ENABLED 1 + +/* Enable PSA Crypto MAC module */ +#define CRYPTO_MAC_MODULE_ENABLED 1 + +/* Enable PSA Crypto Hash module */ +#define CRYPTO_HASH_MODULE_ENABLED 1 + +/* Enable PSA Crypto Cipher module */ +#define CRYPTO_CIPHER_MODULE_ENABLED 1 + +/* Enable PSA Crypto asymmetric key signature module */ +#define CRYPTO_ASYM_SIGN_MODULE_ENABLED 1 + +/* Enable PSA Crypto asymmetric key encryption module */ +#define CRYPTO_ASYM_ENCRYPT_MODULE_ENABLED 0 + +/* Enable PSA Crypto key derivation module */ +#define CRYPTO_KEY_DERIVATION_MODULE_ENABLED 1 + +/* Default size of the internal scratch buffer used for PSA FF IOVec allocations */ +#define CRYPTO_IOVEC_BUFFER_SIZE 5120 + +/* Use stored NV seed to provide entropy */ +#define CRYPTO_NV_SEED 1 + +/* + * Only enable multi-part operations in Hash, MAC, AEAD and symmetric ciphers, + * to optimize memory footprint in resource-constrained devices. + */ +#define CRYPTO_SINGLE_PART_FUNCS_DISABLED 0 + +/* The stack size of the Crypto Secure Partition */ +#define CRYPTO_STACK_SIZE 0x1B00 + +/* FWU Partition Configs */ + +/* Size of the FWU internal data transfer buffer */ +#define TFM_FWU_BUF_SIZE PSA_FWU_MAX_WRITE_SIZE + +/* The stack size of the Firmware Update Secure Partition */ +#define FWU_STACK_SIZE 0x600 + +/* Attest Partition Configs */ + +/* Include optional claims in initial attestation token */ +#define ATTEST_INCLUDE_OPTIONAL_CLAIMS 1 + +/* Include COSE key-id in initial attestation token */ +#define ATTEST_INCLUDE_COSE_KEY_ID 0 + +/* The stack size of the Initial Attestation Secure Partition */ +#define ATTEST_STACK_SIZE 0x700 + +/* Set the initial attestation token profile */ +#define ATTEST_TOKEN_PROFILE_PSA_IOT_1 1 + +/* ITS Partition Configs */ + +/* Create flash FS if it doesn't exist for Internal Trusted Storage partition */ +#define ITS_CREATE_FLASH_LAYOUT 1 + +/* Enable emulated RAM FS for platforms that don't have flash for Internal Trusted Storage partition */ +#define ITS_RAM_FS 0 + +/* Validate filesystem metadata every time it is read from flash */ +#define ITS_VALIDATE_METADATA_FROM_FLASH 1 + +/* The maximum asset size to be stored in the Internal Trusted Storage */ +#define ITS_MAX_ASSET_SIZE 512 + +/* + * Size of the ITS internal data transfer buffer + * (Default to the max asset size so that all requests can be handled in one iteration.) + */ +#define ITS_BUF_SIZE ITS_MAX_ASSET_SIZE + +/* The maximum number of assets to be stored in the Internal Trusted Storage */ +#define ITS_NUM_ASSETS 10 + +/* The stack size of the Internal Trusted Storage Secure Partition */ +#define ITS_STACK_SIZE 0x720 + +/* PS Partition Configs */ + +/* Create flash FS if it doesn't exist for Protected Storage partition */ +#define PS_CREATE_FLASH_LAYOUT 1 + +/* Enable emulated RAM FS for platforms that don't have flash for Protected Storage partition */ +#define PS_RAM_FS 0 + +/* Enable rollback protection for Protected Storage partition */ +#define PS_ROLLBACK_PROTECTION 1 + +/* Validate filesystem metadata every time it is read from flash */ +#define PS_VALIDATE_METADATA_FROM_FLASH 1 + +/* The maximum asset size to be stored in the Protected Storage */ +#define PS_MAX_ASSET_SIZE 2048 + +/* The maximum number of assets to be stored in the Protected Storage */ +#define PS_NUM_ASSETS 30 + +/* The stack size of the Protected Storage Secure Partition */ +#define PS_STACK_SIZE 0x700 + +/* SPM Partition Configs */ + +/* The maximal number of secure services that are connected or requested at the same time */ +#define CONFIG_TFM_CONN_HANDLE_MAX_NUM 8 + +/* Disable the doorbell APIs */ +#define CONFIG_TFM_DOORBELL_API 0 + +#endif /* TFM_PROJECT_CONFIG_H */ diff --git a/examples/platform/openiotsdk/app/CMakeLists.txt b/examples/platform/openiotsdk/app/CMakeLists.txt index c4caf953b34e9b..f7cc06f9fa14d1 100644 --- a/examples/platform/openiotsdk/app/CMakeLists.txt +++ b/examples/platform/openiotsdk/app/CMakeLists.txt @@ -53,3 +53,20 @@ target_link_libraries(openiotsdk-app PUBLIC chip ) + +if(CONFIG_CHIP_OPEN_IOT_SDK_OTA_ENABLE) + target_include_directories(openiotsdk-app + PUBLIC + dfu + ) + + target_sources(openiotsdk-app + PUBLIC + dfu/openiotsdk_dfu_manager.cpp + ) + + target_compile_definitions(openiotsdk-app + PUBLIC + CHIP_OPEN_IOT_SDK_OTA_ENABLE + ) +endif() diff --git a/examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.cpp b/examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.cpp new file mode 100644 index 00000000000000..583f49df43f18d --- /dev/null +++ b/examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.cpp @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file provides the Device Firmware Update manager class implementation. + * It provides firmware update functionality based on Matter OTA Requestor cluster. + */ + +#include "openiotsdk_dfu_manager.h" + +#include "psa/fwu_config.h" + +DFUManager DFUManager::sDFUMgr; + +UserConsentState CustomOTARequestorUserConsent::GetUserConsentState(const UserConsentSubject & subject) +{ + UserConsentState curUserConsentState = CheckDeferredUserConsentState(); + + ChipLogProgress(SoftwareUpdate, "New version of the software is available: %d", subject.requestorTargetVersion); + SetUserConsentState(chip::ota::UserConsentState::kGranted); + + return curUserConsentState; +} + +CHIP_ERROR DFUManager::Init() +{ + int ret; + CHIP_ERROR err = CHIP_NO_ERROR; + + // Set the global instance of the OTA requestor core component + SetRequestorInstance(&mRequestorCore); + + // Periodic query timeout must be set prior to the driver being initialized + mRequestorDriver.SetPeriodicQueryTimeout(0); + + // Watchdog timeout can be set any time before a query image is sent + mRequestorDriver.SetWatchdogTimeout(0); + + mRequestorStorage.Init(chip::Server::GetInstance().GetPersistentStorage()); + mRequestorCore.Init(chip::Server::GetInstance(), mRequestorStorage, mRequestorDriver, mDownloader); + mRequestorDriver.Init(&mRequestorCore, &mImageProcessor); + + mImageProcessor.SetOTADownloader(&mDownloader); + mImageProcessor.SetImageId(FWU_COMPONENT_ID_NONSECURE); + + // Set the image processor instance used for handling image being downloaded + mDownloader.SetImageProcessorDelegate(&mImageProcessor); + + mRequestorDriver.SetUserConsentDelegate(&mUserConsentProvider); + + return err; +} diff --git a/examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.h b/examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.h new file mode 100644 index 00000000000000..0e216c4c742ced --- /dev/null +++ b/examples/platform/openiotsdk/app/dfu/openiotsdk_dfu_manager.h @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file provides the Device Firmware Update manager class implementation. + * It provides firmware update functionality based on Matter OTA Requestor cluster. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::ota; +using namespace ::chip::DeviceLayer; + +class CustomOTARequestorUserConsent : public DefaultOTARequestorUserConsent +{ +public: + UserConsentState GetUserConsentState(const UserConsentSubject & subject) override; +}; + +class DFUManager +{ +public: + CHIP_ERROR Init(); + +private: + friend DFUManager & GetDFUManager(void); + + static DFUManager sDFUMgr; + + DefaultOTARequestor mRequestorCore; + DefaultOTARequestorStorage mRequestorStorage; + ExtendedOTARequestorDriver mRequestorDriver; + BDXDownloader mDownloader; + OTAImageProcessorImpl mImageProcessor; + CustomOTARequestorUserConsent mUserConsentProvider; +}; + +inline DFUManager & GetDFUManager(void) +{ + return DFUManager::sDFUMgr; +} diff --git a/examples/platform/openiotsdk/app/openiotsdk_platform.cpp b/examples/platform/openiotsdk/app/openiotsdk_platform.cpp index e787112a0efb1d..29662d6e8df4e9 100644 --- a/examples/platform/openiotsdk/app/openiotsdk_platform.cpp +++ b/examples/platform/openiotsdk/app/openiotsdk_platform.cpp @@ -48,6 +48,10 @@ #include #endif // USE_CHIP_DATA_MODEL +#ifdef CHIP_OPEN_IOT_SDK_OTA_ENABLE +#include "openiotsdk_dfu_manager.h" +#endif // CHIP_OPEN_IOT_SDK_OTA_ENABLE + #include "psa/fwu_config.h" #include "psa/update.h" #include "tfm_ns_interface.h" @@ -295,6 +299,15 @@ int openiotsdk_chip_run(void) ChipLogProgress(NotSpecified, "Current software version: [%ld] %s", uint32_t(CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION), CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING); +#ifdef CHIP_OPEN_IOT_SDK_OTA_ENABLE + err = GetDFUManager().Init(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "DFU manager initialization failed: %s", err.AsString()); + return EXIT_FAILURE; + } +#endif + return EXIT_SUCCESS; } diff --git a/examples/platform/openiotsdk/supported_examples.txt b/examples/platform/openiotsdk/supported_examples.txt index b7d7f985fb87bf..3e75abe968fc9b 100644 --- a/examples/platform/openiotsdk/supported_examples.txt +++ b/examples/platform/openiotsdk/supported_examples.txt @@ -2,3 +2,4 @@ shell lock-app tv-app all-clusters-app +ota-requestor-app diff --git a/scripts/examples/openiotsdk_example.sh b/scripts/examples/openiotsdk_example.sh index 968e95127f871d..ea33d6026526e9 100755 --- a/scripts/examples/openiotsdk_example.sh +++ b/scripts/examples/openiotsdk_example.sh @@ -251,6 +251,18 @@ function run_test() { TEST_OPTIONS+=(--networkInterface="$FVP_NETWORK") fi + if [[ "$EXAMPLE" == "ota-requestor-app" ]]; then + TEST_OPTIONS+=(--updateBinaryPath="${EXAMPLE_EXE_PATH/elf/"ota"}") + # Check if OTA provider exists, if so get the path to it + OTA_PROVIDER_APP=$(find . -type f -name "chip-ota-provider-app") + if [ -z "$OTA_PROVIDER_APP" ]; then + echo "Error: OTA provider application does not exist." >&2 + exit 1 + fi + TEST_OPTIONS+=(--otaProvider="$OTA_PROVIDER_APP") + TEST_OPTIONS+=(--softwareVersion="$APP_VERSION:$APP_VERSION_STR") + fi + if [[ -f $EXAMPLE_TEST_PATH/test_report_$EXAMPLE.json ]]; then rm -rf "$EXAMPLE_TEST_PATH/test_report_$EXAMPLE".json fi diff --git a/src/platform/openiotsdk/BUILD.gn b/src/platform/openiotsdk/BUILD.gn index 843e3ad08ec5f0..3826e1d7eae37d 100644 --- a/src/platform/openiotsdk/BUILD.gn +++ b/src/platform/openiotsdk/BUILD.gn @@ -76,5 +76,12 @@ static_library("openiotsdk") { "${chip_root}/src/platform:platform_base", ] + if (chip_enable_ota_requestor) { + sources += [ + "OTAImageProcessorImpl.cpp", + "OTAImageProcessorImpl.h", + ] + } + cflags = [ "-Wconversion" ] } diff --git a/src/platform/openiotsdk/OTAImageProcessorImpl.cpp b/src/platform/openiotsdk/OTAImageProcessorImpl.cpp new file mode 100644 index 00000000000000..ca1edf6d2d9cd2 --- /dev/null +++ b/src/platform/openiotsdk/OTAImageProcessorImpl.cpp @@ -0,0 +1,331 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides the implementation of the OTA Image Processor class + * for Open IOT SDK platform. + */ + +#include +#include + +#include "OTAImageProcessorImpl.h" + +namespace chip { + +CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Finalize() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Apply() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleApply, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Abort() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::OnOTAStateChange(DeviceLayer::OtaState state) +{ + DeviceLayer::ChipDeviceEvent otaChange; + otaChange.Type = DeviceLayer::DeviceEventType::kOtaStateChanged; + otaChange.OtaStateChanged.newState = state; + CHIP_ERROR error = DeviceLayer::PlatformMgr().PostEvent(&otaChange); + + if (error != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Posting OTA state change failed %" CHIP_ERROR_FORMAT, error.Format()); + } +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) +{ + // Store block data for HandleProcessBlock to access + CHIP_ERROR err = SetBlock(block); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot set block data: %" CHIP_ERROR_FORMAT, err.Format()); + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +bool OTAImageProcessorImpl::IsFirstImageRun() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return false; + } + + return requestor->GetCurrentUpdateState() == OTARequestorInterface::OTAUpdateStateEnum::kApplying; +} + +CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return CHIP_ERROR_INTERNAL; + } + + uint32_t currentVersion; + uint32_t targetVersion = requestor->GetTargetVersion(); + ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion)); + if (currentVersion != targetVersion) + { + ChipLogError(SoftwareUpdate, "Current software version = %" PRIu32 ", expected software version = %" PRIu32, currentVersion, + targetVersion); + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + psa_status_t rc = psa_fwu_start(imageProcessor->mImageId, NULL, 0); + if (rc != PSA_SUCCESS) + { + ChipLogError(SoftwareUpdate, "Begin a firmware update operation for image %d failed [%ld]", imageProcessor->mImageId, rc); + return; + } + + imageProcessor->mParams.downloadedBytes = 0; + imageProcessor->mParams.totalFileBytes = 0; + + imageProcessor->mHeaderParser.Init(); + + imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaDownloadInProgress); +} + +void OTAImageProcessorImpl::HandleFinalize(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + psa_status_t rc = psa_fwu_finish(imageProcessor->mImageId); + if (rc != PSA_SUCCESS) + { + ChipLogError(SoftwareUpdate, "Mark the image %d as ready for installation failed [%ld]", imageProcessor->mImageId, rc); + imageProcessor->UpdateShutdown(); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaDownloadFailed); + return; + } + + imageProcessor->ReleaseBlock(); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaDownloadComplete); +} + +void OTAImageProcessorImpl::HandleApply(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + VerifyOrReturn(imageProcessor != nullptr); + + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + VerifyOrReturn(requestor != nullptr); + + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaApplyInProgress); + + // Only check ready for reboot state. + // The PSA_SUCCESS_RESTART is related to restarting the external components which are not supported here. + psa_status_t rc = psa_fwu_install(); + if (rc != PSA_SUCCESS_REBOOT) + { + ChipLogError(SoftwareUpdate, "Start the installation of the image %d failed [%ld]", imageProcessor->mImageId, rc); + imageProcessor->UpdateShutdown(); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaApplyFailed); + return; + } + + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaApplyComplete); + + DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { DeviceLayer::PlatformMgr().HandleServerShuttingDown(); }); + DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { + DeviceLayer::PlatformMgr().StopEventLoopTask(); + DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().Shutdown(); + psa_fwu_request_reboot(); + }); +} + +void OTAImageProcessorImpl::HandleAbort(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->UpdateShutdown(); + + imageProcessor->ReleaseBlock(); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaDownloadAborted); +} + +void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + ByteSpan block = imageProcessor->mBlock; + CHIP_ERROR error = imageProcessor->ProcessHeader(block); + if (error != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Image does not contain a valid header"); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_INVALID_FILE_IDENTIFIER); + imageProcessor->UpdateShutdown(); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaDownloadFailed); + return; + } + + psa_status_t rc = + psa_fwu_write(imageProcessor->mImageId, (size_t) imageProcessor->mParams.downloadedBytes, block.data(), block.size()); + if (rc != PSA_SUCCESS) + { + ChipLogError(SoftwareUpdate, "Write image %d chunk failed [%ld]", imageProcessor->mImageId, rc); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED); + imageProcessor->UpdateShutdown(); + imageProcessor->OnOTAStateChange(DeviceLayer::kOtaDownloadFailed); + return; + } + + imageProcessor->mParams.downloadedBytes += block.size(); + imageProcessor->mDownloader->FetchNextData(); +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessHeader(ByteSpan & block) +{ + if (mHeaderParser.IsInitialized()) + { + OTAImageHeader header; + CHIP_ERROR error = mHeaderParser.AccumulateAndDecode(block, header); + + // Needs more data to decode the header + ReturnErrorCodeIf(error == CHIP_ERROR_BUFFER_TOO_SMALL, CHIP_NO_ERROR); + ReturnErrorOnFailure(error); + + mParams.totalFileBytes = header.mPayloadSize; + mHeaderParser.Clear(); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::SetBlock(ByteSpan & block) +{ + if (!IsSpanUsable(block)) + { + ReleaseBlock(); + return CHIP_NO_ERROR; + } + if (mBlock.size() < block.size()) + { + if (!mBlock.empty()) + { + ReleaseBlock(); + } + uint8_t * mBlock_ptr = static_cast(chip::Platform::MemoryAlloc(block.size())); + if (mBlock_ptr == nullptr) + { + return CHIP_ERROR_NO_MEMORY; + } + mBlock = MutableByteSpan(mBlock_ptr, block.size()); + } + CHIP_ERROR err = CopySpanToMutableSpan(block, mBlock); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot copy block data: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::ReleaseBlock() +{ + if (mBlock.data() != nullptr) + { + chip::Platform::MemoryFree(mBlock.data()); + } + + mBlock = MutableByteSpan(); + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::UpdateShutdown() +{ + psa_fwu_component_info_t info; + + psa_status_t rc = psa_fwu_query(mImageId, &info); + + if (rc == PSA_SUCCESS) + { + switch (info.state) + { + case PSA_FWU_WRITING: + case PSA_FWU_CANDIDATE: + psa_fwu_cancel(mImageId); + psa_fwu_clean(mImageId); + break; + case PSA_FWU_FAILED: + case PSA_FWU_UPDATED: + psa_fwu_clean(mImageId); + break; + } + } +} + +} // namespace chip diff --git a/src/platform/openiotsdk/OTAImageProcessorImpl.h b/src/platform/openiotsdk/OTAImageProcessorImpl.h new file mode 100644 index 00000000000000..59180add71752c --- /dev/null +++ b/src/platform/openiotsdk/OTAImageProcessorImpl.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides the implementation of the OTA Image Processor class + * for Open IOT SDK platform. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace chip { + +class OTAImageProcessorImpl : public OTAImageProcessorInterface +{ +public: + //////////// OTAImageProcessorInterface Implementation /////////////// + CHIP_ERROR PrepareDownload() override; + CHIP_ERROR Finalize() override; + CHIP_ERROR Apply() override; + CHIP_ERROR Abort() override; + CHIP_ERROR ProcessBlock(ByteSpan & block) override; + bool IsFirstImageRun() override; + CHIP_ERROR ConfirmCurrentImage() override; + + void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } + void SetImageId(psa_fwu_component_t id); + +private: + //////////// Actual handlers for the OTAImageProcessorInterface /////////////// + static void HandlePrepareDownload(intptr_t context); + static void HandleFinalize(intptr_t context); + static void HandleApply(intptr_t context); + static void HandleAbort(intptr_t context); + static void HandleProcessBlock(intptr_t context); + + /** + * Called to post OTA state change event + */ + void OnOTAStateChange(DeviceLayer::OtaState state); + + /** + * Called to accumulate and decode header from data chunk + */ + CHIP_ERROR ProcessHeader(ByteSpan & block); + + /** + * Called to allocate memory for mBlock if necessary and set it to block + */ + CHIP_ERROR SetBlock(ByteSpan & block); + + /** + * Called to release allocated memory for mBlock + */ + CHIP_ERROR ReleaseBlock(); + + /** + * Called to clean up image preparation + */ + void UpdateShutdown(); + + MutableByteSpan mBlock; + OTADownloader * mDownloader; + OTAImageHeaderParser mHeaderParser; + psa_fwu_component_t mImageId; +}; + +inline void OTAImageProcessorImpl::SetImageId(psa_fwu_component_t id) +{ + mImageId = id; +} + +} // namespace chip diff --git a/src/test_driver/openiotsdk/integration-tests/common/fixtures.py b/src/test_driver/openiotsdk/integration-tests/common/fixtures.py index 1bbb75d9e9eab6..08f9b55d6d61dc 100644 --- a/src/test_driver/openiotsdk/integration-tests/common/fixtures.py +++ b/src/test_driver/openiotsdk/integration-tests/common/fixtures.py @@ -27,6 +27,7 @@ from .fvp_device import FvpDevice from .telnet_connection import TelnetConnection +from .terminal_device import TerminalDevice log = logging.getLogger(__name__) @@ -65,6 +66,24 @@ def networkInterface(request): return None +@pytest.fixture(scope="session") +def otaProvider(request, rootDir): + if request.config.getoption('otaProvider'): + return request.config.getoption('otaProvider') + else: + return os.path.join(rootDir, 'out/chip-ota-provider-app') + + +@pytest.fixture(scope="session") +def softwareVersion(request): + if request.config.getoption('softwareVersion'): + version = request.config.getoption('softwareVersion') + params = version.split(':') + return (params[0], params[1]) + else: + return ("1", "0.0.1") + + @pytest.fixture(scope="function") def device(fvp, fvpConfig, binaryPath, telnetPort, networkInterface): connection = TelnetConnection('localhost', telnetPort) @@ -107,3 +126,22 @@ def controller(controllerConfig): certificateAuthorityManager.Shutdown() chipStack.Shutdown() os.remove(controllerConfig['persistentStoragePath']) + + +@pytest.fixture(scope="session") +def ota_provider(otaProvider, otaProviderConfig): + args = [ + '--discriminator', otaProviderConfig['discriminator'], + '--secured-device-port', otaProviderConfig['port'], + '-c', + '--KVS', otaProviderConfig['persistentStoragePath'], + '--filepath', otaProviderConfig['filePath'], + ] + + device = TerminalDevice(otaProvider, args, "OTAprovider") + device.start() + + yield device + + device.stop() + os.remove(otaProviderConfig['persistentStoragePath']) diff --git a/src/test_driver/openiotsdk/integration-tests/common/terminal_device.py b/src/test_driver/openiotsdk/integration-tests/common/terminal_device.py new file mode 100644 index 00000000000000..1d0d3881f588d7 --- /dev/null +++ b/src/test_driver/openiotsdk/integration-tests/common/terminal_device.py @@ -0,0 +1,95 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging +import subprocess +import threading + +from .device import Device + +log = logging.getLogger(__name__) + + +class TerminalDevice(Device): + + def __init__(self, app, args, name=None): + + self.run = False + super(TerminalDevice, self).__init__(name) + + input_thread_name = '<-- {}'.format(self.name) + output_thread_name = '--> {}'.format(self.name) + + self.cmd = [app] + args + + self.it = threading.Thread( + target=self._input_thread, name=input_thread_name) + self.ot = threading.Thread( + target=self._output_thread, name=output_thread_name) + + def start(self, expectedStartupLog: str = None, startupTimeout: int = 20): + """ + Start the terminal application + """ + log.info('Starting "{}" runner...'.format(self.name)) + + self.proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + + if self.proc.poll() is not None: + raise Exception("Terminal application start failed") + + self.run = True + self.it.start() + self.ot.start() + log.info('"{}" runner started'.format(self.name)) + + def stop(self): + """ + Stop the the terminal application + """ + log.info('Stopping "{}" runner...'.format(self.name)) + self.run = False + self.proc.stdin.close() + self.proc.terminate() + self.proc.wait() + self.oq.put(None) + self.it.join() + self.ot.join() + log.info('"{}" runner stoped'.format(self.name)) + + def _input_thread(self): + while self.run: + line = self.proc.stdout.readline().decode('utf8') + # Check if process still running + if line == '' and self.proc.poll() is not None: + pass + else: + if self.verbose: + log.info('<--|{}| {}'.format(self.name, line.strip())) + self.iq.put(line) + + def _output_thread(self): + while self.run: + line = self.oq.get() + if self.proc.poll() is not None: + pass + if line: + if self.verbose: + log.info('-->|{}| {}'.format(self.name, line.strip())) + self.proc.stdin.write(line) + else: + log.debug('Nothing sent') diff --git a/src/test_driver/openiotsdk/integration-tests/conftest.py b/src/test_driver/openiotsdk/integration-tests/conftest.py index 0181911de7e8b2..922d36501e101d 100644 --- a/src/test_driver/openiotsdk/integration-tests/conftest.py +++ b/src/test_driver/openiotsdk/integration-tests/conftest.py @@ -34,3 +34,9 @@ def pytest_addoption(parser): help='Telnet terminal port number.', default="5000") parser.addoption('--networkInterface', action='store', help='FVP network interface name') + parser.addoption('--updateBinaryPath', action='store', + help='Application update binary path') + parser.addoption('--otaProvider', action='store', + help='Path to OTA provider application') + parser.addoption('--softwareVersion', action='store', + help='Software version of update image in the format : eg. 1:0.0.01') diff --git a/src/test_driver/openiotsdk/integration-tests/ota-requestor-app/__init__.py b/src/test_driver/openiotsdk/integration-tests/ota-requestor-app/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/test_driver/openiotsdk/integration-tests/ota-requestor-app/test_app.py b/src/test_driver/openiotsdk/integration-tests/ota-requestor-app/test_app.py new file mode 100644 index 00000000000000..9470d5a16e46ef --- /dev/null +++ b/src/test_driver/openiotsdk/integration-tests/ota-requestor-app/test_app.py @@ -0,0 +1,190 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging +import os +import re + +import chip.interaction_model +import pytest +from chip.clusters.Objects import OtaSoftwareUpdateRequestor +from chip.clusters.Types import NullValue +from common.utils import (connect_device, disconnect_device, discover_device, get_setup_payload, send_zcl_command, + write_zcl_attribute) + +log = logging.getLogger(__name__) + + +@pytest.fixture(scope="session") +def binaryPath(request, rootDir): + if request.config.getoption('binaryPath'): + return request.config.getoption('binaryPath') + else: + return os.path.join(rootDir, 'examples/ota-requestor-app/openiotsdk/build/chip-openiotsdk-ota-requestor-app-example.elf') + + +@pytest.fixture(scope="session") +def updateBinaryPath(request, rootDir): + if request.config.getoption('updateBinaryPath'): + return request.config.getoption('updateBinaryPath') + else: + return os.path.join(rootDir, 'examples/ota-requestor-app/openiotsdk/build/chip-openiotsdk-ota-requestor-app-example.ota') + + +@pytest.fixture(scope="session") +def controllerConfig(request): + config = { + 'vendorId': 0xFFF1, + 'fabricId': 1, + 'persistentStoragePath': '/tmp/openiotsdk-test-storage.json' + } + return config + + +@pytest.fixture(scope="session") +def otaProviderConfig(request, updateBinaryPath): + config = { + 'discriminator': '3841', + 'port': '5580', + 'filePath': f'{updateBinaryPath}', + 'persistentStoragePath': '/tmp/openiotsdk-test-ota-provider.json' + } + return config + + +@pytest.mark.smoketest +def test_smoke_test(device): + ret = device.wait_for_output("Open IoT SDK ota-requestor-app example application start") + assert ret is not None and len(ret) > 0 + ret = device.wait_for_output("Open IoT SDK ota-requestor-app example application run") + assert ret is not None and len(ret) > 0 + + +@pytest.mark.commissioningtest +def test_commissioning(device, controller): + assert controller is not None + devCtrl = controller + + ret = device.wait_for_output("Open IoT SDK ota-requestor-app example application start") + assert ret is not None and len(ret) > 0 + + setupPayload = get_setup_payload(device) + assert setupPayload is not None + + commissionable_device = discover_device(devCtrl, setupPayload) + assert commissionable_device is not None + + assert commissionable_device.vendorId == int(setupPayload.attributes['VendorID']) + assert commissionable_device.productId == int(setupPayload.attributes['ProductID']) + assert commissionable_device.addresses[0] is not None + + nodeId = connect_device(devCtrl, setupPayload, commissionable_device) + assert nodeId is not None + log.info("Device {} connected".format(commissionable_device.addresses[0])) + + ret = device.wait_for_output("Commissioning completed successfully") + assert ret is not None and len(ret) > 0 + + assert disconnect_device(devCtrl, nodeId) + + +OTA_REQUESTOR_CTRL_TEST_ENDPOINT_ID = 0 + + +@pytest.mark.ctrltest +def test_update_ctrl(device, controller, ota_provider, softwareVersion): + assert controller is not None + devCtrl = controller + version_number, version_str = softwareVersion + + log.info("Setup OTA provider...") + + # Get OTA provider setup payload + setupPayloadProvider = get_setup_payload(ota_provider) + assert setupPayloadProvider is not None + + # Discover and commission the OTA provider + commissionable_provider_device = discover_device(devCtrl, setupPayloadProvider) + assert commissionable_provider_device is not None + + providerNodeId = connect_device(devCtrl, setupPayloadProvider, commissionable_provider_device) + assert providerNodeId is not None + + ret = ota_provider.wait_for_output("Commissioning completed successfully") + assert ret is not None and len(ret) > 0 + + log.info("OTA provider ready") + log.info("Setup OTA requestor...") + + # Get OTA requestor setup payload + setupPayload = get_setup_payload(device) + assert setupPayload is not None + + # Discover and commission the OTA requestor + commissionable_requestor_device = discover_device(devCtrl, setupPayload) + assert commissionable_requestor_device is not None + + requestorNodeId = connect_device(devCtrl, setupPayload, commissionable_requestor_device) + assert requestorNodeId is not None + + ret = device.wait_for_output("Commissioning completed successfully") + assert ret is not None and len(ret) > 0 + + log.info("OTA requestor ready") + log.info("Install ACL entries") + + # Install necessary ACL entries in OTA provider to enable access by OTA requestor + err, res = write_zcl_attribute(devCtrl, "AccessControl", "Acl", providerNodeId, OTA_REQUESTOR_CTRL_TEST_ENDPOINT_ID, + [{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [requestorNodeId], "targets": NullValue}, + {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": NullValue, "targets": [{"cluster": 41, "endpoint": NullValue, "deviceType": NullValue}]}]) + assert err == 0 + assert res[0].Status == chip.interaction_model.Status.Success + + ota_provider.set_verbose(False) + + log.info("Announce the OTA provider and start the firmware update process") + + # Announce the OTA provider and start the firmware update process + err, res = send_zcl_command(devCtrl, "OtaSoftwareUpdateRequestor", "AnnounceOTAProvider", requestorNodeId, OTA_REQUESTOR_CTRL_TEST_ENDPOINT_ID, + dict(providerNodeID=providerNodeId, vendorID=int(setupPayloadProvider.attributes['VendorID']), + announcementReason=OtaSoftwareUpdateRequestor.Enums.OTAAnnouncementReason.kUrgentUpdateAvailable, + metadataForNode=None, endpoint=0)) + + ret = device.wait_for_output("New version of the software is available") + assert ret is not None and len(ret) > 1 + + version = ret[-1].split()[-1] + assert version_number == version + + device.set_verbose(False) + + log.info("New software image downloading and installing...") + + ret = device.wait_for_output("Open IoT SDK ota-requestor-app example application start", timeout=1200) + assert ret is not None and len(ret) > 0 + + device.set_verbose(True) + + ret = device.wait_for_output("Current software version") + assert ret is not None and len(ret) > 1 + + version_app = ret[-1].split()[-2:] + assert version_number == re.sub(r"[\[\]]", "", version_app[0]) + assert version_str == version_app[1] + + assert disconnect_device(devCtrl, requestorNodeId) + assert disconnect_device(devCtrl, providerNodeId)