diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index be2320f975beae..051af08a2893da 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -27,7 +27,7 @@ concurrency: env: CHIP_NO_LOG_TIMESTAMPS: true - + jobs: qemu-esp32: @@ -75,12 +75,7 @@ jobs: name: Tizen runs-on: ubuntu-latest - # NOTE: job temporarely disabled as it seems flaky. The flake does not result in usable - # logs so the current theory is that we run out of space. This is unusual as - # larger docker images succeed at bootstrap, however it needs more investigation - # to detect an exact/real root cause. - if: false - # if: github.actor != 'restyled-io[bot]' + if: github.actor != 'restyled-io[bot]' container: image: ghcr.io/project-chip/chip-build-tizen-qemu:54 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 70f40efc502234..72080ea65ad772 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -506,112 +506,6 @@ jobs: run: | mkdir -p out/trace_data scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/controller/python/test/test_scripts/mobile-device-test.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ACE_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ACE_1_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ACE_1_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ACE_1_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_AccessChecker.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CC_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CC_10_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CADMIN_1_9.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CGEN_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CNET_1_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DA_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DA_1_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DA_1_7.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DGGEN_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DRLK_2_12.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DRLK_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DRLK_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DeviceBasicComposition.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DeviceConformance.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_6.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_7.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_8.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DEM_2_9.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EPM_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EPM_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_FAN_3_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_FAN_3_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_FAN_3_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_FAN_3_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_FAN_3_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_3_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_3_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDManagementCluster.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_IDM_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_IDM_1_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_IDM_4_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_PWRTL_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RR_1_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_SC_3_6.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_10.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_11.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_12.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_13.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_6.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_7.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_8.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_2_9.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TIMESYNC_3_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_TestEventTrigger.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TestBatchInvoke.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TestGroupTableReports.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OCC_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OCC_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OCC_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPCREDS_3_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPCREDS_3_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_6.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_5.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOCTRL_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOCTRL_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOCTRL_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOM_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_PS_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCRUNM_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCRUNM_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCRUNM_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCCLEANM_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCCLEANM_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCCLEANM_2_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCOPSTATE_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCOPSTATE_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCOPSTATE_2_4.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_SC_7_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_SWTCH.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_WHM_1_2.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_WHM_2_1.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_LVL_2_3.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TCP_Tests.py' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestConformanceSupport.py" --script-args "--trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestMatterTestingSupport.py" --script-args "--trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestSpecParsingSupport.py" --script-args "--trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/TestTimeSyncTrustedTimeSourceRunner.py' @@ -619,6 +513,7 @@ jobs: scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestIdChecks.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestSpecParsingDeviceType.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceSupport.py' + scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestChoiceConformanceSupport.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_IDM_10_4.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_TC_SC_7_1.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/TestDecorators.py' diff --git a/.mergify.yml b/.mergify.yml index c27419c8d0fc3e..77530c4521b676 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,20 +1,29 @@ pull_request_rules: + - name: Label conflicting pull requests + description: Add a label to a pull request with conflict to spot it easily + conditions: + - conflict + - '-closed' + actions: + label: + toggle: + - conflict - name: Automatic merge on PullApprove conditions: - or: - - "check-success=pullapprove" - - label="fast track" - - "#approved-reviews-by>=1" - - "#review-threads-unresolved=0" - - "-draft" - - "label!=docker" # Don't auto merge docker images - - "#check-failure=0" # Don't auto merge with a failure - - "#check-pending=0" # Don't auto merge with anything pending - - "check-success~=Build" # Don't auto merge unless a build has succeeded, needed because above is true on a fresh PR before builds + - check-success=pullapprove + - label="fast track" + - '#approved-reviews-by>=1' + - '#review-threads-unresolved=0' + - '-draft' + - label!=docker + - '#check-failure=0' + - '#check-pending=0' + - check-success~=Build - or: - - "check-success=pullapprove" - - "check-skipped=pullapprove" - - "check-neutral=pullapprove" + - check-success=pullapprove + - check-skipped=pullapprove + - check-neutral=pullapprove actions: merge: method: squash diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 3de1749b37d58a..fee0d745f6d136 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -334,7 +334,11 @@ config("warnings_third_party") { } config("symbols_default") { - cflags = [ "-g${symbol_level}" ] + if (strip_symbols) { + cflags = [ "-s" ] + } else { + cflags = [ "-g${symbol_level}" ] + } } config("std_default") { diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni index 74dc224f3ecec7..63b1840bc884d2 100644 --- a/build/config/compiler/compiler.gni +++ b/build/config/compiler/compiler.gni @@ -30,11 +30,11 @@ declare_args() { symbol_level = 2 # Enable position independent code (-fPIC). - enable_pic = - current_os == "linux" || current_os == "mac" || current_os == "android" + enable_pic = current_os == "linux" || current_os == "mac" || + current_os == "android" || current_os == "tizen" # Enable position independent executables (-pie). - enable_pie = current_os == "linux" + enable_pie = current_os == "linux" || current_os == "tizen" # Remove unwind tables from the binary to save space. exclude_unwind_tables = current_os != "android" @@ -48,6 +48,9 @@ declare_args() { # enable libfuzzer is_libfuzzer = false + # Remove all symbol table and relocation information from the binary. + strip_symbols = false + # Generate code coverage analysis artifacts when enabled. use_coverage = false diff --git a/examples/all-clusters-app/infineon/psoc6/README.md b/examples/all-clusters-app/infineon/psoc6/README.md index 75a01995d8c9e4..138f132f7a0dba 100644 --- a/examples/all-clusters-app/infineon/psoc6/README.md +++ b/examples/all-clusters-app/infineon/psoc6/README.md @@ -30,10 +30,11 @@ will then join the network. ## Building -- [Modustoolbox Software](https://www.cypress.com/products/modustoolbox) +- Download and install + [Modustoolbox Software v3.2](https://www.infineon.com/modustoolbox) - Refer to `integrations/docker/images/chip-build-infineon/Dockerfile` or - `scripts/examples/gn_psoc6_example.sh` for downloading the Software and + Refer to `integrations/docker/images/stage-2/chip-build-infineon/Dockerfile` + or `scripts/examples/gn_psoc6_example.sh` for downloading the Software and related tools. - Install some additional tools (likely already present for Matter @@ -62,11 +63,12 @@ will then join the network. - Put CY8CKIT-062S2-43012 board on KitProg3 CMSIS-DAP Mode by pressing the `MODE SELECT` button. `KITPROG3 STATUS` LED is ON confirms board is in - proper mode. + proper mode. (Modustoolbox Software needs to be installed) - On the command line: $ cd ~/connectedhomeip + $ export CY_TOOLS_PATHS=/tools_3.2 $ python3 out/infineon-psoc6-all-clusters/chip-psoc6-clusters-example.flash.py ## Commissioning and cluster control diff --git a/examples/all-clusters-minimal-app/infineon/psoc6/README.md b/examples/all-clusters-minimal-app/infineon/psoc6/README.md index 4ebd3832f43de6..80b8e1ceb66791 100644 --- a/examples/all-clusters-minimal-app/infineon/psoc6/README.md +++ b/examples/all-clusters-minimal-app/infineon/psoc6/README.md @@ -30,10 +30,11 @@ will then join the network. ## Building -- [Modustoolbox Software](https://www.cypress.com/products/modustoolbox) +- Download and install + [Modustoolbox Software v3.2](https://www.infineon.com/modustoolbox) - Refer to `integrations/docker/images/chip-build-infineon/Dockerfile` or - `scripts/examples/gn_psoc6_example.sh` for downloading the Software and + Refer to `integrations/docker/images/stage-2/chip-build-infineon/Dockerfile` + or `scripts/examples/gn_psoc6_example.sh` for downloading the Software and related tools. - Install some additional tools (likely already present for Matter @@ -62,11 +63,12 @@ will then join the network. - Put CY8CKIT-062S2-43012 board on KitProg3 CMSIS-DAP Mode by pressing the `MODE SELECT` button. `KITPROG3 STATUS` LED is ON confirms board is in - proper mode. + proper mode. (Modustoolbox Software needs to be installed) - On the command line: $ cd ~/connectedhomeip + $ export CY_TOOLS_PATHS=/tools_3.2 $ python3 out/infineon-psoc6-all-clusters-minimal/chip-psoc6-clusters-minimal-example.flash.py ## Commissioning and cluster control diff --git a/examples/fabric-bridge-app/fabric-bridge-common/BUILD.gn b/examples/fabric-bridge-app/fabric-bridge-common/BUILD.gn index 10cb48c31c584e..7f2fbcbbfe0556 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/BUILD.gn +++ b/examples/fabric-bridge-app/fabric-bridge-common/BUILD.gn @@ -19,20 +19,34 @@ config("config") { include_dirs = [ "include" ] } -chip_data_model("fabric-bridge-common") { +chip_data_model("fabric-bridge-common-zap") { zap_file = "fabric-bridge-app.zap" is_server = true cflags = [ "-DDYNAMIC_ENDPOINT_COUNT=16" ] } +# This includes all the clusters that only exist on the dynamic endpoint. +source_set("fabric-bridge-common") { + public_configs = [ ":config" ] + + sources = [ + "${chip_root}/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp", + "${chip_root}/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h", + ] + + public_deps = [ ":fabric-bridge-common-zap" ] +} + source_set("fabric-bridge-lib") { public_configs = [ ":config" ] sources = [ "include/BridgedDevice.h", + "include/BridgedDeviceBasicInformationImpl.h", "include/BridgedDeviceManager.h", "include/CHIPProjectAppConfig.h", "src/BridgedDevice.cpp", + "src/BridgedDeviceBasicInformationImpl.cpp", "src/BridgedDeviceManager.cpp", "src/ZCLCallbacks.cpp", ] diff --git a/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDeviceBasicInformationImpl.h b/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDeviceBasicInformationImpl.h new file mode 100644 index 00000000000000..23403438ab2be8 --- /dev/null +++ b/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDeviceBasicInformationImpl.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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. + */ +#pragma once + +#include +#include + +class BridgedDeviceBasicInformationImpl : public chip::app::AttributeAccessInterface +{ +public: + BridgedDeviceBasicInformationImpl() : + chip::app::AttributeAccessInterface(chip::NullOptional /* endpointId */, + chip::app::Clusters::BridgedDeviceBasicInformation::Id) + {} + + // AttributeAccessInterface implementation + CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & path, chip::app::AttributeValueEncoder & encoder) override; + CHIP_ERROR Write(const chip::app::ConcreteDataAttributePath & path, chip::app::AttributeValueDecoder & decoder) override; +}; diff --git a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceBasicInformationImpl.cpp b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceBasicInformationImpl.cpp new file mode 100644 index 00000000000000..62630d730b4355 --- /dev/null +++ b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceBasicInformationImpl.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 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 "BridgedDeviceBasicInformationImpl.h" + +#include "BridgedDeviceManager.h" + +#include +#include +#include + +#include + +static constexpr unsigned kBridgedDeviceBasicInformationClusterRevision = 4; +static constexpr unsigned kBridgedDeviceBasicInformationFeatureMap = 0; + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::app::Clusters; + +CHIP_ERROR BridgedDeviceBasicInformationImpl::Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) +{ + // Registration is done for the bridged device basic information only + VerifyOrDie(path.mClusterId == app::Clusters::BridgedDeviceBasicInformation::Id); + + BridgedDevice * dev = BridgeDeviceMgr().GetDevice(path.mEndpointId); + VerifyOrReturnError(dev != nullptr, CHIP_ERROR_NOT_FOUND); + + switch (path.mAttributeId) + { + case BasicInformation::Attributes::Reachable::Id: + encoder.Encode(dev->IsReachable()); + break; + case BasicInformation::Attributes::NodeLabel::Id: + encoder.Encode(CharSpan::fromCharString(dev->GetName())); + break; + case BasicInformation::Attributes::ClusterRevision::Id: + encoder.Encode(kBridgedDeviceBasicInformationClusterRevision); + break; + case BasicInformation::Attributes::FeatureMap::Id: + encoder.Encode(kBridgedDeviceBasicInformationFeatureMap); + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR BridgedDeviceBasicInformationImpl::Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) +{ + VerifyOrDie(path.mClusterId == app::Clusters::BridgedDeviceBasicInformation::Id); + + BridgedDevice * dev = BridgeDeviceMgr().GetDevice(path.mEndpointId); + VerifyOrReturnError(dev != nullptr, CHIP_ERROR_NOT_FOUND); + + if (!dev->IsReachable()) + { + return CHIP_ERROR_NOT_CONNECTED; + } + + ChipLogProgress(NotSpecified, "Bridged device basic information attempt to write attribute: ep=%d", path.mAttributeId); + + // nothing writable right now ... + + return CHIP_ERROR_INVALID_ARGUMENT; +} diff --git a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp index 170b39cd2c94e2..64b88e49bb5c96 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp +++ b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp @@ -45,8 +45,13 @@ using namespace chip::app::Clusters; namespace { -constexpr uint8_t kMaxRetries = 10; -constexpr int kNodeLabelSize = 32; +constexpr uint8_t kMaxRetries = 10; +constexpr int kNodeLabelSize = 32; +constexpr int kUniqueIdSize = 32; +constexpr int kVendorNameSize = 32; +constexpr int kProductNameSize = 32; +constexpr int kHardwareVersionSize = 32; +constexpr int kSoftwareVersionSize = 32; // Current ZCL implementation of Struct uses a max-size array of 254 bytes constexpr int kDescriptorAttributeArraySize = 254; @@ -76,27 +81,51 @@ constexpr int kDescriptorAttributeArraySize = 254; // - Bridged Device Basic Information // - Administrator Commissioning +// clang-format off // Declare Descriptor cluster attributes DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs) -DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::DeviceTypeList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */ - DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ServerList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */ - DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ClientList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */ - DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::PartsList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* parts list */ - DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::DeviceTypeList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */ + DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ServerList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */ + DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ClientList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */ + DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::PartsList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* parts list */ +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); // Declare Bridged Device Basic Information cluster attributes DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs) -DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, kNodeLabelSize, 0), /* NodeLabel */ - DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, 0), /* Reachable */ - DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* feature map */ + // The attributes below are MANDATORY in the Bridged Device Information Cluster + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::UniqueID::Id, CHAR_STRING, kUniqueIdSize, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::FeatureMap::Id, BITMAP32, 4, 0), + + // The attributes below are OPTIONAL in the bridged device, however they are MANDATORY in the BasicInformation cluster + // so they can always be provided if desired + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::VendorName::Id, CHAR_STRING, kVendorNameSize, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::VendorID::Id, INT16U, 2, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductName::Id, CHAR_STRING, kProductNameSize, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductID::Id, INT16U, 2, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, kNodeLabelSize, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::HardwareVersion::Id, INT16U, 2, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::HardwareVersionString::Id, CHAR_STRING, + kHardwareVersionSize, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::SoftwareVersion::Id, INT32U, 4, 0), + DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::SoftwareVersionString::Id, CHAR_STRING, + kSoftwareVersionSize, 0), +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + +// Declare Ecosystem Information cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(ecosystemInformationBasicAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::RemovedOn::Id, EPOCH_US, kNodeLabelSize, ATTRIBUTE_MASK_NULLABLE), + DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::DeviceDirectory::Id, ARRAY, kDescriptorAttributeArraySize, 0), + DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::LocationDirectory::Id, ARRAY, kDescriptorAttributeArraySize, 0), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); // Declare Administrator Commissioning cluster attributes DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(AdministratorCommissioningAttrs) -DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::WindowStatus::Id, ENUM8, 1, 0), /* NodeLabel */ - DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminFabricIndex::Id, FABRIC_IDX, 1, 0), /* Reachable */ - DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminVendorId::Id, VENDOR_ID, 2, 0), /* Reachable */ - DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::WindowStatus::Id, ENUM8, 1, 0), + DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminFabricIndex::Id, FABRIC_IDX, 1, 0), + DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminVendorId::Id, VENDOR_ID, 2, 0), +DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); +// clang-format on constexpr CommandId administratorCommissioningCommands[] = { app::Clusters::AdministratorCommissioning::Commands::OpenCommissioningWindow::Id, @@ -109,6 +138,7 @@ constexpr CommandId administratorCommissioningCommands[] = { DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedNodeClusters) DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), + DECLARE_DYNAMIC_CLUSTER(EcosystemInformation::Id, ecosystemInformationBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), DECLARE_DYNAMIC_CLUSTER(AdministratorCommissioning::Id, AdministratorCommissioningAttrs, ZAP_CLUSTER_MASK(SERVER), administratorCommissioningCommands, nullptr) DECLARE_DYNAMIC_CLUSTER_LIST_END; diff --git a/examples/fabric-bridge-app/fabric-bridge-common/src/ZCLCallbacks.cpp b/examples/fabric-bridge-app/fabric-bridge-common/src/ZCLCallbacks.cpp index 9a0467be4e27e8..d4da982cfa45a5 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/src/ZCLCallbacks.cpp +++ b/examples/fabric-bridge-app/fabric-bridge-common/src/ZCLCallbacks.cpp @@ -25,67 +25,26 @@ using namespace ::chip; using namespace ::chip::app::Clusters; -#define ZCL_DESCRIPTOR_CLUSTER_REVISION (1u) -#define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION (2u) -#define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP (0u) +#define ZCL_ADMINISTRATOR_COMMISSIONING_CLUSTER_REVISION (1u) // External attribute read callback function Protocols::InteractionModel::Status emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId, const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer, uint16_t maxReadLength) { - AttributeId attributeId = attributeMetadata->attributeId; - - BridgedDevice * dev = BridgeDeviceMgr().GetDevice(endpoint); - if (dev != nullptr && clusterId == app::Clusters::BridgedDeviceBasicInformation::Id) + if (clusterId == AdministratorCommissioning::Id) { - using namespace app::Clusters::BridgedDeviceBasicInformation::Attributes; - ChipLogProgress(NotSpecified, "HandleReadBridgedDeviceBasicAttribute: attrId=%d, maxReadLength=%d", attributeId, - maxReadLength); - - if ((attributeId == Reachable::Id) && (maxReadLength == 1)) + // TODO(#34791) This is a workaround to prevent crash. CADMIN is still reading incorrect + // Attribute values on dynamic endpoint as it only reads the root node and not the actual bridge + // device we are representing here, when addressing the issue over there we can more easily + // resolve this workaround. + if ((attributeMetadata->attributeId == AdministratorCommissioning::Attributes::ClusterRevision::Id) && (maxReadLength == 2)) { - *buffer = dev->IsReachable() ? 1 : 0; - } - else if ((attributeId == NodeLabel::Id) && (maxReadLength == 32)) - { - MutableByteSpan zclNameSpan(buffer, maxReadLength); - MakeZclCharString(zclNameSpan, dev->GetName()); - } - else if ((attributeId == ClusterRevision::Id) && (maxReadLength == 2)) - { - uint16_t rev = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION; + uint16_t rev = ZCL_ADMINISTRATOR_COMMISSIONING_CLUSTER_REVISION; memcpy(buffer, &rev, sizeof(rev)); + return Protocols::InteractionModel::Status::Success; } - else if ((attributeId == FeatureMap::Id) && (maxReadLength == 4)) - { - uint32_t featureMap = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP; - memcpy(buffer, &featureMap, sizeof(featureMap)); - } - else - { - return Protocols::InteractionModel::Status::Failure; - } - return Protocols::InteractionModel::Status::Success; } return Protocols::InteractionModel::Status::Failure; } - -// External attribute write callback function -Protocols::InteractionModel::Status emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, - const EmberAfAttributeMetadata * attributeMetadata, - uint8_t * buffer) -{ - uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); - Protocols::InteractionModel::Status ret = Protocols::InteractionModel::Status::Failure; - - BridgedDevice * dev = BridgeDeviceMgr().GetDevice(endpointIndex); - if (dev != nullptr && dev->IsReachable()) - { - ChipLogProgress(NotSpecified, "emberAfExternalAttributeWriteCallback: ep=%d, clusterId=%d", endpoint, clusterId); - ret = Protocols::InteractionModel::Status::Success; - } - - return ret; -} diff --git a/examples/fabric-bridge-app/linux/RpcServer.cpp b/examples/fabric-bridge-app/linux/RpcServer.cpp index 76fe8f84653d39..a4053bea9f6a56 100644 --- a/examples/fabric-bridge-app/linux/RpcServer.cpp +++ b/examples/fabric-bridge-app/linux/RpcServer.cpp @@ -20,6 +20,7 @@ #include "pw_rpc_system_server/rpc_server.h" #include "pw_rpc_system_server/socket.h" +#include #include #include @@ -62,6 +63,10 @@ pw::Status FabricBridge::AddSynchronizedDevice(const chip_rpc_SynchronizedDevice return pw::Status::Unknown(); } + CHIP_ERROR err = EcosystemInformation::EcosystemInformationServer::Instance().AddEcosystemInformationClusterToEndpoint( + device->GetEndpointId()); + VerifyOrDie(err == CHIP_NO_ERROR); + return pw::OkStatus(); } diff --git a/examples/fabric-bridge-app/linux/main.cpp b/examples/fabric-bridge-app/linux/main.cpp index c708b256727520..4f227b1f4a0436 100644 --- a/examples/fabric-bridge-app/linux/main.cpp +++ b/examples/fabric-bridge-app/linux/main.cpp @@ -19,11 +19,13 @@ #include #include "BridgedDevice.h" +#include "BridgedDeviceBasicInformationImpl.h" #include "BridgedDeviceManager.h" #include "CommissionableInit.h" #include #include +#include #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE #include "RpcClient.h" @@ -34,8 +36,15 @@ #include #include -using namespace chip; +// This is declared here and not in a header because zap/embr assumes all clusters +// are defined in a static endpoint in the .zap file. From there, the codegen will +// automatically use PluginApplicationCallbacksHeader.jinja to declare and call +// the respective Init callbacks. However, because EcosystemInformation cluster is only +// ever on a dynamic endpoint, this doesn't get declared and called for us, so we +// need to declare and call it ourselves where the application is initialized. +void MatterEcosystemInformationPluginServerInitCallback(); +using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::AdministratorCommissioning; @@ -48,6 +57,8 @@ constexpr uint16_t kPollIntervalMs = 100; constexpr uint16_t kRetryIntervalS = 3; #endif +BridgedDeviceBasicInformationImpl gBridgedDeviceBasicInformationAttributes; + bool KeyboardHit() { int bytesWaiting; @@ -174,7 +185,9 @@ void ApplicationInit() { ChipLogDetail(NotSpecified, "Fabric-Bridge: ApplicationInit()"); + MatterEcosystemInformationPluginServerInitCallback(); CommandHandlerInterfaceRegistry::RegisterCommandHandler(&gAdministratorCommissioningCommandHandler); + registerAttributeAccessOverride(&gBridgedDeviceBasicInformationAttributes); #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE InitRpcServer(kFabricBridgeServerPort); diff --git a/examples/lighting-app/infineon/psoc6/README.md b/examples/lighting-app/infineon/psoc6/README.md index e5b5854fc275e5..773a05af1f3899 100644 --- a/examples/lighting-app/infineon/psoc6/README.md +++ b/examples/lighting-app/infineon/psoc6/README.md @@ -31,10 +31,11 @@ will then join the network. ## Building -- [Modustoolbox Software](https://www.cypress.com/products/modustoolbox) +- Download and install + [Modustoolbox Software v3.2](https://www.infineon.com/modustoolbox) - Refer to `integrations/docker/images/chip-build-infineon/Dockerfile` or - `scripts/examples/gn_psoc6_example.sh` for downloading the Software and + Refer to `integrations/docker/images/stage-2/chip-build-infineon/Dockerfile` + or `scripts/examples/gn_psoc6_example.sh` for downloading the Software and related tools. - Install some additional tools (likely already present for Matter @@ -43,7 +44,7 @@ will then join the network. python3-pip - Supported hardware: - [CY8CKIT-062S2-43012](https://www.cypress.com/CY8CKIT-062S2-43012) + [CY8CKIT-062S2-43012](https://www.infineon.com/CY8CKIT-062S2-43012) * Build the example application: @@ -59,11 +60,12 @@ will then join the network. - Put CY8CKIT-062S2-43012 board on KitProg3 CMSIS-DAP Mode by pressing the `MODE SELECT` button. `KITPROG3 STATUS` LED is ON confirms board is in - proper mode. + proper mode. (Modustoolbox Software needs to be installed) - On the command line: $ cd ~/connectedhomeip + $ export CY_TOOLS_PATHS=/tools_3.2 $ python3 out/infineon-psoc6-light/chip-psoc6-lighting-example.flash.py ## Commissioning and cluster control diff --git a/examples/lock-app/infineon/psoc6/README.md b/examples/lock-app/infineon/psoc6/README.md index b1e1e1a0e9ac6b..0c8e8eb1e8c781 100644 --- a/examples/lock-app/infineon/psoc6/README.md +++ b/examples/lock-app/infineon/psoc6/README.md @@ -33,10 +33,11 @@ will then join the network. ## Building -- [Modustoolbox Software](https://www.cypress.com/products/modustoolbox) +- Download and install + [Modustoolbox Software v3.2](https://www.infineon.com/modustoolbox) - Refer to `integrations/docker/images/chip-build-infineon/Dockerfile` or - `scripts/examples/gn_psoc6_example.sh` for downloading the Software and + Refer to `integrations/docker/images/stage-2/chip-build-infineon/Dockerfile` + or `scripts/examples/gn_psoc6_example.sh` for downloading the Software and related tools. - Install some additional tools (likely already present for Matter @@ -45,7 +46,7 @@ will then join the network. python3-pip - Supported hardware: - [CY8CKIT-062S2-43012](https://www.cypress.com/CY8CKIT-062S2-43012) + [CY8CKIT-062S2-43012](https://www.infineon.com/CY8CKIT-062S2-43012) * Build the example application: @@ -66,11 +67,12 @@ more instructions_ - Put CY8CKIT-062S2-43012 board on KitProg3 CMSIS-DAP Mode by pressing the `MODE SELECT` button. `KITPROG3 STATUS` LED is ON confirms board is in - proper mode. + proper mode. (Modustoolbox Software needs to be installed) - On the command line: $ cd ~/connectedhomeip + $ export CY_TOOLS_PATHS=/tools_3.2 $ python3 out/infineon-psoc6-lock/chip-psoc6-lock-example.flash.py ## Commissioning and cluster control @@ -116,12 +118,12 @@ commands. These power cycle the BlueTooth hardware and disable BR/EDR mode. ### Cluster control -- After successful commissioning, use the OnOff cluster command to toggle - device between On or Off states. +- After successful commissioning, use the doorlock cluster command to toggle + device between lock or Unlock states. - `$ ./out/debug/chip-tool onoff on 1234 1` + `$ ./out/debug/chip-tool doorlock lock-door 1234 1 --timedInteractionTimeoutMs 100` - `$ ./out/debug/chip-tool onoff off 1234 1` + `$ ./out/debug/chip-tool doorlock unlock-door 1234 1 --timedInteractionTimeoutMs 100` - Cluster OnOff can also be done using the `USER_BTN1` button on the board. This button is configured with `APP_LOCK_BUTTON` in `include/AppConfig.h`. diff --git a/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h b/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h index 6fa4c82568eb70..1ef9862a272938 100644 --- a/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h @@ -18,7 +18,7 @@ #pragma once -#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 0xFFF10010 // TODO: ID-TBD +#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 144 // 0x0090 Network Infrastructure Manager #define CHIP_DEVICE_CONFIG_DEVICE_NAME "Network Infrastructure Manager" // Inherit defaults from config/standalone/CHIPProjectConfig.h diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter index bbd20d46b43d94..3f24e4be5744c6 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.matter +++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter @@ -1518,6 +1518,7 @@ provisional cluster ThreadBorderRouterManagement = 1106 { provisional readonly attribute int16u threadVersion = 2; provisional readonly attribute boolean interfaceEnabled = 3; provisional readonly attribute nullable int64u activeDatasetTimestamp = 4; + provisional readonly attribute nullable int64u pendingDatasetTimestamp = 5; readonly attribute command_id generatedCommandList[] = 65528; readonly attribute command_id acceptedCommandList[] = 65529; readonly attribute event_id eventList[] = 65530; @@ -1879,6 +1880,7 @@ endpoint 1 { callback attribute threadVersion; callback attribute interfaceEnabled; callback attribute activeDatasetTimestamp; + callback attribute pendingDatasetTimestamp; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap index 0a14bcb26d7b03..7c1445ac46c190 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.zap +++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap @@ -3420,7 +3420,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3436,7 +3436,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3452,7 +3452,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3468,7 +3468,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3484,6 +3484,22 @@ "storageOption": "External", "singleton": 0, "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PendingDatasetTimestamp", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, "defaultValue": "", "reportable": 1, "minInterval": 1, @@ -3500,7 +3516,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3516,7 +3532,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3532,7 +3548,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3548,7 +3564,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3564,7 +3580,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 410b1a189245d2..1fcee183f131b3 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -93,7 +93,7 @@ source_set("app-main") { "${chip_root}/src/controller:controller", "${chip_root}/src/controller:gen_check_chip_controller_headers", "${chip_root}/src/lib", - "${chip_root}/src/platform/logging:stdio", + "${chip_root}/src/platform/logging:default", ] deps = [ ":ota-test-event-trigger", diff --git a/examples/rvc-app/rvc-common/include/rvc-device.h b/examples/rvc-app/rvc-common/include/rvc-device.h index 092ded91f76272..da03422ffbb34a 100644 --- a/examples/rvc-app/rvc-common/include/rvc-device.h +++ b/examples/rvc-app/rvc-common/include/rvc-device.h @@ -44,7 +44,8 @@ class RvcDevice mRunModeDelegate(), mRunModeInstance(&mRunModeDelegate, aRvcClustersEndpoint, RvcRunMode::Id, 0), mCleanModeDelegate(), mCleanModeInstance(&mCleanModeDelegate, aRvcClustersEndpoint, RvcCleanMode::Id, 0), mOperationalStateDelegate(), mOperationalStateInstance(&mOperationalStateDelegate, aRvcClustersEndpoint), mServiceAreaDelegate(), - mServiceAreaInstance(&mServiceAreaDelegate, aRvcClustersEndpoint, BitMask(0)) + mServiceAreaInstance(&mServiceAreaDelegate, aRvcClustersEndpoint, + BitMask(ServiceArea::Feature::kMaps, ServiceArea::Feature::kProgressReporting)) { // set the current-mode at start-up mRunModeInstance.UpdateCurrentMode(RvcRunMode::ModeIdle); diff --git a/examples/rvc-app/rvc-common/include/rvc-service-area-delegate.h b/examples/rvc-app/rvc-common/include/rvc-service-area-delegate.h index 40f312ffb78c2f..759c22a4e609cb 100644 --- a/examples/rvc-app/rvc-common/include/rvc-service-area-delegate.h +++ b/examples/rvc-app/rvc-common/include/rvc-service-area-delegate.h @@ -49,7 +49,7 @@ class RvcServiceAreaDelegate : public Delegate bool IsValidSelectAreasSet(const ServiceArea::Commands::SelectAreas::DecodableType & req, ServiceArea::SelectAreasStatus & locationStatus, MutableCharSpan statusText) override; - bool HandleSkipCurrentArea(MutableCharSpan skipStatusText) override; + bool HandleSkipCurrentArea(uint32_t skippedArea, MutableCharSpan skipStatusText) override; //************************************************************************* // Supported Locations accessors @@ -78,7 +78,7 @@ class RvcServiceAreaDelegate : public Delegate bool GetSupportedMapByIndex(uint32_t listIndex, ServiceArea::MapStructureWrapper & supportedMap) override; - bool GetSupportedMapById(uint8_t aMapId, uint32_t & listIndex, ServiceArea::MapStructureWrapper & supportedMap) override; + bool GetSupportedMapById(uint32_t aMapId, uint32_t & listIndex, ServiceArea::MapStructureWrapper & supportedMap) override; bool AddSupportedMap(const ServiceArea::MapStructureWrapper & newMap, uint32_t & listIndex) override; diff --git a/examples/rvc-app/rvc-common/rvc-app.matter b/examples/rvc-app/rvc-common/rvc-app.matter index 7a87e9e83dbe90..4467c170acc6eb 100644 --- a/examples/rvc-app/rvc-common/rvc-app.matter +++ b/examples/rvc-app/rvc-common/rvc-app.matter @@ -1424,9 +1424,9 @@ cluster RvcOperationalState = 97 { command GoHome(): OperationalCommandResponse = 128; } -/** The Service Area cluster provides an interface for controlling the locations where a device should operate, and for querying the current location. */ +/** The Service Area cluster provides an interface for controlling the areas where a device should operate, and for querying the current area being serviced. */ provisional cluster ServiceArea = 336 { - revision 1; // NOTE: Default/not specifically set + revision 1; enum OperationalStatusEnum : enum8 { kPending = 0; @@ -1447,28 +1447,33 @@ provisional cluster ServiceArea = 336 { kSuccess = 0; kInvalidAreaList = 1; kInvalidInMode = 2; + kInvalidSkippedArea = 3; } bitmap Feature : bitmap32 { - kListOrder = 0x1; - kSelectWhileRunning = 0x2; + kSelectWhileRunning = 0x1; + kProgressReporting = 0x2; + kMaps = 0x4; + } + + struct LandmarkInfoStruct { + LandmarkTag landmarkTag = 0; + nullable RelativePositionTag positionTag = 1; } struct AreaInfoStruct { nullable LocationDescriptorStruct locationInfo = 0; - nullable LandmarkTag landmarkTag = 1; - nullable PositionTag positionTag = 2; - nullable FloorSurfaceTag surfaceTag = 3; + nullable LandmarkInfoStruct landmarkInfo = 1; } struct AreaStruct { int32u areaID = 0; - nullable int8u mapID = 1; + nullable int32u mapID = 1; AreaInfoStruct areaDesc = 2; } struct MapStruct { - int8u mapID = 0; + int32u mapID = 0; char_string<64> name = 1; } @@ -1480,7 +1485,7 @@ provisional cluster ServiceArea = 336 { } readonly attribute AreaStruct supportedAreas[] = 0; - readonly attribute MapStruct supportedMaps[] = 1; + readonly attribute optional MapStruct supportedMaps[] = 1; readonly attribute int32u selectedAreas[] = 2; readonly attribute optional nullable int32u currentArea = 3; readonly attribute optional nullable epoch_s estimatedEndTime = 4; @@ -1498,18 +1503,22 @@ provisional cluster ServiceArea = 336 { response struct SelectAreasResponse = 1 { SelectAreasStatus status = 0; - optional char_string<256> statusText = 1; + char_string<256> statusText = 1; + } + + request struct SkipAreaRequest { + int32u skippedArea = 0; } response struct SkipAreaResponse = 3 { SkipAreaStatus status = 0; - optional char_string<256> statusText = 1; + char_string<256> statusText = 1; } /** Command used to select a set of device areas, where the device is to operate. */ command SelectAreas(SelectAreasRequest): SelectAreasResponse = 0; /** This command is used to skip an area where the device operates. */ - command SkipArea(): SkipAreaResponse = 2; + command SkipArea(SkipAreaRequest): SkipAreaResponse = 2; } endpoint 0 { diff --git a/examples/rvc-app/rvc-common/src/rvc-service-area-delegate.cpp b/examples/rvc-app/rvc-common/src/rvc-service-area-delegate.cpp index e46d3eaa22dff7..aca96da836151d 100644 --- a/examples/rvc-app/rvc-common/src/rvc-service-area-delegate.cpp +++ b/examples/rvc-app/rvc-common/src/rvc-service-area-delegate.cpp @@ -17,6 +17,7 @@ */ #include #include +#include using namespace chip; using namespace chip::app::Clusters; @@ -25,8 +26,8 @@ using namespace chip::app::Clusters::ServiceArea; CHIP_ERROR RvcServiceAreaDelegate::Init() { // hardcoded fill of SUPPORTED MAPS for prototyping - uint8_t supportedMapId_XX = 3; - uint8_t supportedMapId_YY = 245; + uint32_t supportedMapId_XX = 3; + uint32_t supportedMapId_YY = 245; GetInstance()->AddSupportedMap(supportedMapId_XX, "My Map XX"_span); GetInstance()->AddSupportedMap(supportedMapId_YY, "My Map YY"_span); @@ -38,32 +39,29 @@ CHIP_ERROR RvcServiceAreaDelegate::Init() uint32_t supportedAreaID_D = 0x88888888; // Location A has name, floor number, uses map XX - GetInstance()->AddSupportedLocation( - supportedAreaID_A, DataModel::Nullable(supportedMapId_XX), "My Location A"_span, - DataModel::Nullable(4), DataModel::Nullable(), DataModel::Nullable(), - DataModel::Nullable(), DataModel::Nullable()); + GetInstance()->AddSupportedLocation(supportedAreaID_A, DataModel::Nullable(supportedMapId_XX), "My Location A"_span, + DataModel::Nullable(4), DataModel::Nullable(), + DataModel::Nullable(), + DataModel::Nullable()); // Location B has name, uses map XX - GetInstance()->AddSupportedLocation( - supportedAreaID_B, DataModel::Nullable(supportedMapId_XX), "My Location B"_span, - DataModel::Nullable(), DataModel::Nullable(), DataModel::Nullable(), - DataModel::Nullable(), DataModel::Nullable()); + GetInstance()->AddSupportedLocation(supportedAreaID_B, DataModel::Nullable(supportedMapId_XX), "My Location B"_span, + DataModel::Nullable(), DataModel::Nullable(), + DataModel::Nullable(), + DataModel::Nullable()); // Location C has full SemData, no name, Map YY - GetInstance()->AddSupportedLocation(supportedAreaID_C, DataModel::Nullable(supportedMapId_YY), CharSpan(), + GetInstance()->AddSupportedLocation(supportedAreaID_C, DataModel::Nullable(supportedMapId_YY), CharSpan(), DataModel::Nullable(-1), DataModel::Nullable(Globals::AreaTypeTag::kPlayRoom), DataModel::Nullable(Globals::LandmarkTag::kBackDoor), - DataModel::Nullable(Globals::PositionTag::kLeft), - DataModel::Nullable(Globals::FloorSurfaceTag::kConcrete)); + DataModel::Nullable(Globals::RelativePositionTag::kNextTo)); // Location D has null values for all HomeLocationStruct fields, Map YY - GetInstance()->AddSupportedLocation(supportedAreaID_D, DataModel::Nullable(supportedMapId_YY), - "My Location D"_span, DataModel::Nullable(), - DataModel::Nullable(), + GetInstance()->AddSupportedLocation(supportedAreaID_D, DataModel::Nullable(supportedMapId_YY), "My Location D"_span, + DataModel::Nullable(), DataModel::Nullable(), DataModel::Nullable(Globals::LandmarkTag::kCouch), - DataModel::Nullable(Globals::PositionTag::kLeft), - DataModel::Nullable(Globals::FloorSurfaceTag::kHardwood)); + DataModel::Nullable(Globals::RelativePositionTag::kNextTo)); GetInstance()->SetCurrentArea(supportedAreaID_C); @@ -86,7 +84,7 @@ bool RvcServiceAreaDelegate::IsValidSelectAreasSet(const Commands::SelectAreas:: return true; }; -bool RvcServiceAreaDelegate::HandleSkipCurrentArea(MutableCharSpan skipStatusText) +bool RvcServiceAreaDelegate::HandleSkipCurrentArea(uint32_t skippedArea, MutableCharSpan skipStatusText) { // TODO IMPLEMENT return true; @@ -213,7 +211,7 @@ bool RvcServiceAreaDelegate::GetSupportedMapByIndex(uint32_t listIndex, MapStruc return false; }; -bool RvcServiceAreaDelegate::GetSupportedMapById(uint8_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap) +bool RvcServiceAreaDelegate::GetSupportedMapById(uint32_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap) { // We do not need to reimplement this method as it's already done by the SDK. // We are reimplementing this method, still using linear search, but with some optimization on the SDK implementation diff --git a/examples/temperature-measurement-app/esp32/main/DeviceCallbacks.cpp b/examples/temperature-measurement-app/esp32/main/DeviceCallbacks.cpp index e0d7decc849616..88fee24b7535b6 100644 --- a/examples/temperature-measurement-app/esp32/main/DeviceCallbacks.cpp +++ b/examples/temperature-measurement-app/esp32/main/DeviceCallbacks.cpp @@ -23,6 +23,7 @@ * **/ #include "DeviceCallbacks.h" +#include static const char TAG[] = "echo-devicecallbacks"; diff --git a/integrations/docker/images/base/chip-build/version b/integrations/docker/images/base/chip-build/version index c797fa2a19186b..885f3e39127977 100644 --- a/integrations/docker/images/base/chip-build/version +++ b/integrations/docker/images/base/chip-build/version @@ -1 +1 @@ -68 : [Bouffalo Lab] Update gcc toolchain and flash tool +69 : [Infineon] Update ModusToolbox version to 3.2 diff --git a/integrations/docker/images/stage-2/chip-build-infineon/Dockerfile b/integrations/docker/images/stage-2/chip-build-infineon/Dockerfile index 5578b27ce50988..a6470044ff3080 100644 --- a/integrations/docker/images/stage-2/chip-build-infineon/Dockerfile +++ b/integrations/docker/images/stage-2/chip-build-infineon/Dockerfile @@ -13,25 +13,27 @@ RUN set -x \ file \ libglib2.0-0 \ libusb-1.0-0 sudo \ + libxcb-xinerama0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxkbcommon-x11-0 \ && rm -rf /var/lib/apt/lists/ \ && : # last line # ------------------------------------------------------------------------------ -# Download and extract ModusToolbox 2.3 -RUN curl --fail --location --show-error \ - https://itoolspriv.infineon.com/itbhs/api/packages/com.ifx.tb.tool.modustoolbox/Versions/2.4.0.5972-public/artifacts/ModusToolbox_2.4.0.5972-linux-install.tar.gz/download?noredirect \ - -o /tmp/ModusToolbox_2.4.0.5972-linux-install.tar.gz \ - && tar -C /opt -zxf /tmp/ModusToolbox_2.4.0.5972-linux-install.tar.gz \ - && rm /tmp/ModusToolbox_2.4.0.5972-linux-install.tar.gz +# Download ModusToolbox 3.2 +RUN curl --fail --location --silent --show-error https://itoolspriv.infineon.com/itbhs/api/packages/com.ifx.tb.tool.modustoolbox/Versions/3.2.0.16028-public/artifacts/ModusToolbox_3.2.0.16028-linux-install.deb/download?noredirect -o /tmp/ModusToolbox_3.2.0.16028-linux-install.deb # ------------------------------------------------------------------------------ -# Execute post-build scripts -RUN /opt/ModusToolbox/tools_2.4/modus-shell/postinstall +# Install ModusToolbox 3.2 +RUN apt install /tmp/ModusToolbox_3.2.0.16028-linux-install.deb -# NOTE: udev rules are NOT installed: -# /opt/ModusToolbox/tools_2.4/fw-loader/udev_rules/install_rules.sh -# because docker containers do not support udev +# ------------------------------------------------------------------------------ +# Remove ModusToolbox deb file +RUN rm /tmp/ModusToolbox_3.2.0.16028-linux-install.deb # ------------------------------------------------------------------------------ -# Set environment variable required by ModusToolbox application makefiles -ENV CY_TOOLS_PATHS="/opt/ModusToolbox/tools_2.4" +# Run below command to Initialize the CY_TOOLS_PATHS environment variable defined in the /etc/profile.d/modustoolbox_3.2.sh file +RUN bash --login diff --git a/integrations/docker/images/vscode/chip-build-vscode/Dockerfile b/integrations/docker/images/vscode/chip-build-vscode/Dockerfile index 6bde4b8cb8431f..4dda050b9c496a 100644 --- a/integrations/docker/images/vscode/chip-build-vscode/Dockerfile +++ b/integrations/docker/images/vscode/chip-build-vscode/Dockerfile @@ -5,7 +5,7 @@ FROM ghcr.io/project-chip/chip-build-efr32:${VERSION} AS efr32 FROM ghcr.io/project-chip/chip-build-android:${VERSION} AS android FROM ghcr.io/project-chip/chip-build-esp32-qemu:${VERSION} as esp32 FROM ghcr.io/project-chip/chip-build-telink:${VERSION} AS telink -FROM ghcr.io/project-chip/chip-build-infineon:${VERSION} AS p6 +FROM ghcr.io/project-chip/chip-build-infineon:${VERSION} AS psoc6 FROM ghcr.io/project-chip/chip-build-tizen:${VERSION} AS tizen FROM ghcr.io/project-chip/chip-build-crosscompile:${VERSION} AS crosscompile FROM ghcr.io/project-chip/chip-build-ameba:${VERSION} AS ameba @@ -36,7 +36,7 @@ COPY --from=android /opt/android/sdk /opt/android/sdk COPY --from=android /opt/android/android-ndk-r23c /opt/android/android-ndk-r23c COPY --from=android /usr/lib/kotlinc /usr/lib/kotlinc -COPY --from=p6 /opt/ModusToolbox /opt/ModusToolbox +COPY --from=psoc6 /opt/Tools/ModusToolbox /opt/Tools/ModusToolbox COPY --from=telink /opt/telink/zephyrproject /opt/telink/zephyrproject COPY --from=telink /opt/telink/zephyr-sdk-0.16.1 /opt/telink/zephyr-sdk-0.16.1 @@ -111,7 +111,7 @@ ENV PATH $PATH:/usr/lib/kotlinc/bin ENV AMEBA_PATH=/opt/ameba/ambd_sdk_with_chip_non_NDA ENV ANDROID_HOME=/opt/android/sdk ENV ANDROID_NDK_HOME=/opt/android/android-ndk-r23c -ENV CY_TOOLS_PATHS="/opt/ModusToolbox/tools_2.4" +ENV CY_TOOLS_PATHS="/opt/Tools/ModusToolbox/tools_3.2" ENV SILABS_BOARD=BRD4186C # Keep GSDK_ROOT name until rename transition to SISDK is completed ENV GSDK_ROOT=/opt/silabs/simplicity_sdk/ diff --git a/scripts/build/builders/tizen.py b/scripts/build/builders/tizen.py index 97124328c5dd1e..b9b9c59661cf74 100644 --- a/scripts/build/builders/tizen.py +++ b/scripts/build/builders/tizen.py @@ -117,6 +117,10 @@ def __init__(self, if app == TizenApp.TESTS: self.extra_gn_options.append('chip_build_tests=true') + # Tizen test driver creates ISO image with all unit test files. So, + # it uses twice as much space as regular build. Due to CI storage + # limitations, we need to strip debug symbols from executables. + self.extra_gn_options.append('strip_symbols=true') self.build_command = 'check' if not enable_ble: diff --git a/scripts/build/gn_gen_cirque.sh b/scripts/build/gn_gen_cirque.sh index d6f6bd86905a0e..1d89d802e7ad44 100755 --- a/scripts/build/gn_gen_cirque.sh +++ b/scripts/build/gn_gen_cirque.sh @@ -36,7 +36,7 @@ echo "Setup build environment" source "./scripts/activate.sh" echo "Build: GN configure" -gn --root="$CHIP_ROOT" gen --check --fail-on-unused-args out/debug --args='target_os="all"'"chip_build_tests=false chip_enable_wifi=false chip_im_force_fabric_quota_check=true enable_default_builds=false enable_host_gcc_build=true enable_standalone_chip_tool_build=true enable_linux_all_clusters_app_build=true enable_linux_lighting_app_build=true enable_linux_lit_icd_app_build=true" +gn --root="$CHIP_ROOT" gen --check --fail-on-unused-args out/debug --args='target_os="all" chip_logging_backend="stdio" chip_build_tests=false chip_enable_wifi=false chip_im_force_fabric_quota_check=true enable_default_builds=false enable_host_gcc_build=true enable_standalone_chip_tool_build=true enable_linux_all_clusters_app_build=true enable_linux_lighting_app_build=true enable_linux_lit_icd_app_build=true' echo "Build: Ninja build" time ninja -C out/debug all check diff --git a/scripts/examples/gn_psoc6_example.sh b/scripts/examples/gn_psoc6_example.sh index e3ae875b8e27a4..6cf6e054973656 100755 --- a/scripts/examples/gn_psoc6_example.sh +++ b/scripts/examples/gn_psoc6_example.sh @@ -16,26 +16,17 @@ # limitations under the License. # +# Install required software +if [[ -z "${CY_TOOLS_PATHS}" ]]; then + echo "*****************************************************************************************************" + echo "Install ModusToolbox Software v3.2 from https://www.infineon.com/modustoolbox and set CY_TOOLS_PATHS" + echo "*****************************************************************************************************" +fi + set -e # Build script for GN PSOC6 examples GitHub workflow. source "$(dirname "$0")/../../scripts/activate.sh" -# Install required software -if [ -d "/opt/ModusToolbox" ]; then - export CY_TOOLS_PATHS="/opt/ModusToolbox/tools_2.4" -elif [ -d "$HOME/ModusToolbox" ]; then - # Set CY TOOLS PATH - export CY_TOOLS_PATHS="$HOME/ModusToolbox/tools_2.4" -else - # Install Modustoolbox - curl --fail --location --silent --show-error https://itoolspriv.infineon.com/itbhs/api/packages/com.ifx.tb.tool.modustoolbox/Versions/2.4.0.5972-public/artifacts/ModusToolbox_2.4.0.5972-linux-install.tar.gz/download?noredirect -o /tmp/ModusToolbox_2.4.0.5972-linux-install.tar.gz && - tar -C "$HOME" -zxf /tmp/ModusToolbox_2.4.0.5972-linux-install.tar.gz && - rm /tmp/ModusToolbox_2.4.0.5972-linux-install.tar.gz - - # Set CY TOOLS PATH - export CY_TOOLS_PATHS="$HOME/ModusToolbox/tools_2.4" -fi - set -x env diff --git a/src/app/MessageDef/StatusIB.cpp b/src/app/MessageDef/StatusIB.cpp index d612f549aeeae1..63012cc64f7f1f 100644 --- a/src/app/MessageDef/StatusIB.cpp +++ b/src/app/MessageDef/StatusIB.cpp @@ -24,6 +24,7 @@ #include "StatusIB.h" #include "MessageDefHelper.h" +#include "protocols/interaction_model/StatusCode.h" #include #include @@ -149,31 +150,6 @@ CHIP_ERROR StatusIB::ToChipError() const return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(mStatus)); } -StatusIB::StatusIB(CHIP_ERROR aError) -{ - if (aError.IsPart(ChipError::SdkPart::kIMClusterStatus)) - { - mStatus = Status::Failure; - mClusterStatus = MakeOptional(aError.GetSdkCode()); - return; - } - - mClusterStatus = NullOptional; - if (aError == CHIP_NO_ERROR) - { - mStatus = Status::Success; - return; - } - - if (aError.IsPart(ChipError::SdkPart::kIMGlobalStatus)) - { - mStatus = static_cast(aError.GetSdkCode()); - return; - } - - mStatus = Status::Failure; -} - namespace { bool FormatStatusIBError(char * buf, uint16_t bufSize, CHIP_ERROR err) { diff --git a/src/app/MessageDef/StatusIB.h b/src/app/MessageDef/StatusIB.h index 291a68c7fd295d..a0087f4849201d 100644 --- a/src/app/MessageDef/StatusIB.h +++ b/src/app/MessageDef/StatusIB.h @@ -60,7 +60,7 @@ struct StatusIB } } - explicit StatusIB(CHIP_ERROR error); + explicit StatusIB(CHIP_ERROR error) : StatusIB(Protocols::InteractionModel::ClusterStatusCode(error)) {} enum class Tag : uint8_t { diff --git a/src/app/clusters/color-control-server/color-control-server.cpp b/src/app/clusters/color-control-server/color-control-server.cpp index c4de9f9d9461ef..4a4fc69b4efe07 100644 --- a/src/app/clusters/color-control-server/color-control-server.cpp +++ b/src/app/clusters/color-control-server/color-control-server.cpp @@ -3113,15 +3113,15 @@ void ColorControlServer::levelControlColorTempChangeCommand(EndpointId endpoint) * - When it changes from null to any other value and vice versa. (Implicit to the QuieterReportingAttribute class) * * The QuietReportAttribute class is updated with the new value and when the report conditions are met, - * this function will return MarkAttributeDirty::kIfChanged. + * this function will return MarkAttributeDirty::kYes. * It is expected that the user will use this return value to trigger a reporting mechanism for the attribute with the new value * (Which was updated in the quietReporter) * * @param quietReporter: The QuieterReportingAttribute object for the attribute to update. * @param newValue: Value to update the attribute with * @param isStartOrEndOfTransition: Boolean that indicatse whether the update is occurring at the start or end of a level transition - * @return MarkAttributeDirty::kIfChanged when the attribute must be maredk dirty and be reported. MarkAttributeDirty::kNo when it - * when it no report is needed. + * @return MarkAttributeDirty::kYes when the attribute must be marked dirty and be reported. MarkAttributeDirty::kNo when + * no report is needed. */ template MarkAttributeDirty ColorControlServer::SetQuietReportAttribute(QuieterReportingAttribute & quietReporter, V newValue, @@ -3132,7 +3132,7 @@ MarkAttributeDirty ColorControlServer::SetQuietReportAttribute(QuieterReportingA if (isStartOrEndOfTransition) { - // At the start or end of the movement/transition we must report + // At the start or end of the movement/transition we must report if the value changed auto predicate = [](const typename QuieterReportingAttribute::SufficientChangePredicateCandidate &) -> bool { return true; }; @@ -3155,7 +3155,7 @@ MarkAttributeDirty ColorControlServer::SetQuietReportAttribute(QuieterReportingA dirtyState = quietReporter.SetValue(newValue, now, predicate); } - return (dirtyState == AttributeDirtyState::kMustReport) ? MarkAttributeDirty::kIfChanged : MarkAttributeDirty::kNo; + return (dirtyState == AttributeDirtyState::kMustReport) ? MarkAttributeDirty::kYes : MarkAttributeDirty::kNo; } /* @@ -3180,7 +3180,7 @@ Status ColorControlServer::SetQuietReportRemainingTime(EndpointId endpoint, uint // - kMarkDirtyOnIncrement : When the value increases. if (quietRemainingTime[epIndex].SetValue(newRemainingTime, now) == AttributeDirtyState::kMustReport) { - markDirty = MarkAttributeDirty::kIfChanged; + markDirty = MarkAttributeDirty::kYes; } return Attributes::RemainingTime::Set(endpoint, quietRemainingTime[epIndex].value().Value(), markDirty); diff --git a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp index 74a27f21779ff9..9d82f7064f0f1b 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp @@ -243,6 +243,17 @@ EcosystemInformationServer & EcosystemInformationServer::Instance() return mInstance; } +CHIP_ERROR EcosystemInformationServer::AddEcosystemInformationClusterToEndpoint(EndpointId aEndpoint) +{ + VerifyOrReturnError((aEndpoint != kRootEndpointId && aEndpoint != kInvalidEndpointId), CHIP_ERROR_INVALID_ARGUMENT); + auto it = mDevicesMap.find(aEndpoint); + // We expect that the device has not been previously added. + VerifyOrReturnError((it == mDevicesMap.end()), CHIP_ERROR_INCORRECT_STATE); + // This create an empty DeviceInfo in mDevicesMap. + mDevicesMap[aEndpoint] = DeviceInfo(); + return CHIP_NO_ERROR; +} + CHIP_ERROR EcosystemInformationServer::AddDeviceInfo(EndpointId aEndpoint, std::unique_ptr aDevice) { VerifyOrReturnError(aDevice, CHIP_ERROR_INVALID_ARGUMENT); @@ -282,11 +293,11 @@ CHIP_ERROR EcosystemInformationServer::ReadAttribute(const ConcreteReadAttribute switch (aPath.mAttributeId) { case Attributes::RemovedOn::Id: - return EcosystemInformationServer::Instance().EncodeRemovedOnAttribute(aPath.mEndpointId, aEncoder); + return EncodeRemovedOnAttribute(aPath.mEndpointId, aEncoder); case Attributes::DeviceDirectory::Id: - return EcosystemInformationServer::Instance().EncodeDeviceDirectoryAttribute(aPath.mEndpointId, aEncoder); + return EncodeDeviceDirectoryAttribute(aPath.mEndpointId, aEncoder); case Attributes::LocationDirectory::Id: - return EcosystemInformationServer::Instance().EncodeLocationStructAttribute(aPath.mEndpointId, aEncoder); + return EncodeLocationStructAttribute(aPath.mEndpointId, aEncoder); case Attributes::ClusterRevision::Id: { uint16_t rev = ZCL_ECOSYSTEM_INFORMATION_CLUSTER_REVISION; return aEncoder.Encode(rev); diff --git a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h index daa6f0124d9e39..dce12e745bf14e 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h @@ -150,6 +150,24 @@ class EcosystemInformationServer public: static EcosystemInformationServer & Instance(); + /** + * @brief Add EcosystemInformation Cluster to endpoint so we respond appropriately on endpoint + * + * EcosystemInformation cluster is only ever on dynamic bridge endpoint. If cluster is added + * to a new endpoint, but does not contain any ecosystem information presently, + * this is called to let ECOINFO cluster code know it is supposed to provide blank attribute + * information on this endpoint. + * + * This approach was intentionally taken instead of relying on emberAfDeviceTypeListFromEndpoint + * to keep this cluster more unit testable. This does add burden to application but is worth + * the trade-off. + * + * @param[in] aEndpoint Which endpoint is the device being added to the device directory. + * @return #CHIP_NO_ERROR on success. + * @return Other CHIP_ERROR associated with issue. + */ + CHIP_ERROR AddEcosystemInformationClusterToEndpoint(EndpointId aEndpoint); + /** * @brief Adds device as entry to DeviceDirectory list Attribute. * @@ -187,7 +205,7 @@ class EcosystemInformationServer private: struct DeviceInfo { - Optional mRemovedOn; + Optional mRemovedOn = NullOptional; std::vector> mDeviceDirectory; // Map key is using the UniqueLocationId std::map> mLocationDirectory; diff --git a/src/app/clusters/level-control/level-control.cpp b/src/app/clusters/level-control/level-control.cpp index 00d8a2e496518f..231a542c2ef1b4 100644 --- a/src/app/clusters/level-control/level-control.cpp +++ b/src/app/clusters/level-control/level-control.cpp @@ -545,7 +545,7 @@ static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs) markDirty = MarkAttributeDirty::kYes; } - Attributes::RemainingTime::Set(endpoint, state->quietRemainingTime.value().ValueOr(0), markDirty); + Attributes::RemainingTime::Set(endpoint, state->quietRemainingTime.value().Value(), markDirty); } #endif // IGNORE_LEVEL_CONTROL_CLUSTER_LEVEL_CONTROL_REMAINING_TIME } diff --git a/src/app/clusters/service-area-server/service-area-cluster-objects.h b/src/app/clusters/service-area-server/service-area-cluster-objects.h index 4436a21e284a0f..2d2694e20dbd78 100644 --- a/src/app/clusters/service-area-server/service-area-cluster-objects.h +++ b/src/app/clusters/service-area-server/service-area-cluster-objects.h @@ -39,8 +39,7 @@ struct AreaStructureWrapper : public chip::app::Clusters::ServiceArea::Structs:: AreaStructureWrapper() { Set(0, 0, CharSpan(), DataModel::Nullable(), DataModel::Nullable(), - DataModel::Nullable(), DataModel::Nullable(), - DataModel::Nullable()); + DataModel::Nullable(), DataModel::Nullable()); } /** @@ -52,22 +51,20 @@ struct AreaStructureWrapper : public chip::app::Clusters::ServiceArea::Structs:: * @param[in] aAreaTypeTag A common namespace Area tag - indicates an association of the location with an indoor or outdoor area * of a home. * @param[in] aLandmarkTag A common namespace Landmark tag - indicates an association of the location with a home landmark. - * @param[in] aPositionTag A common namespace Position tag - indicates the position of the location with respect to the - * landmark. - * @param[in] aSurfaceTag A common namespace Floor Surface tag - indicates an association of the location with a surface type. + * @param[in] aRelativePositionTag A common namespace Relative Position tag - indicates the position of the location with + * respect to the landmark. * * @note Requirements regarding what combinations of fields and values are valid are not checked by this class. * @note If aLocationName is larger than kLocationNameMaxSize, it will be truncated. * @note If aLocationName is an empty string and aFloorNumber and aAreaTypeTag are null, locationInfo will be set to null. */ - AreaStructureWrapper(uint32_t aAreaID, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, + AreaStructureWrapper(uint32_t aAreaID, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, const DataModel::Nullable & aFloorNumber, const DataModel::Nullable & aAreaTypeTag, const DataModel::Nullable & aLandmarkTag, - const DataModel::Nullable & aPositionTag, - const DataModel::Nullable & aSurfaceTag) + const DataModel::Nullable & aRelativePositionTag) { - Set(aAreaID, aMapId, aLocationName, aFloorNumber, aAreaTypeTag, aLandmarkTag, aPositionTag, aSurfaceTag); + Set(aAreaID, aMapId, aLocationName, aFloorNumber, aAreaTypeTag, aLandmarkTag, aRelativePositionTag); } /** @@ -88,16 +85,43 @@ struct AreaStructureWrapper : public chip::app::Clusters::ServiceArea::Structs:: */ AreaStructureWrapper & operator=(const AreaStructureWrapper & aOther) { + areaID = aOther.areaID; + mapID = aOther.mapID; + if (aOther.areaDesc.locationInfo.IsNull()) { - Set(aOther.areaID, aOther.mapID, CharSpan(), NullOptional, NullOptional, aOther.areaDesc.landmarkTag, - aOther.areaDesc.positionTag, aOther.areaDesc.surfaceTag); + areaDesc.locationInfo.SetNull(); } else { - Set(aOther.areaID, aOther.mapID, aOther.areaDesc.locationInfo.Value().locationName, - aOther.areaDesc.locationInfo.Value().floorNumber, aOther.areaDesc.locationInfo.Value().areaType, - aOther.areaDesc.landmarkTag, aOther.areaDesc.positionTag, aOther.areaDesc.surfaceTag); + areaDesc.locationInfo.SetNonNull(); + + // deep copy the name. + auto sizeToCopy = std::min(sizeof(mLocationNameBuffer), aOther.areaDesc.locationInfo.Value().locationName.size()); + memcpy(mLocationNameBuffer, aOther.areaDesc.locationInfo.Value().locationName.data(), sizeToCopy); + areaDesc.locationInfo.Value().locationName = CharSpan(mLocationNameBuffer, sizeToCopy); + + areaDesc.locationInfo.Value().floorNumber = aOther.areaDesc.locationInfo.Value().floorNumber; + areaDesc.locationInfo.Value().areaType = aOther.areaDesc.locationInfo.Value().areaType; + } + + if (aOther.areaDesc.landmarkInfo.IsNull()) + { + areaDesc.landmarkInfo.SetNull(); + } + else + { + areaDesc.landmarkInfo.SetNonNull(); + areaDesc.landmarkInfo.Value().landmarkTag = aOther.areaDesc.landmarkInfo.Value().landmarkTag; + if (aOther.areaDesc.landmarkInfo.Value().positionTag.IsNull()) + { + areaDesc.landmarkInfo.Value().positionTag.SetNull(); + } + else + { + areaDesc.landmarkInfo.Value().positionTag.SetNonNull(); + areaDesc.landmarkInfo.Value().positionTag.Value() = aOther.areaDesc.landmarkInfo.Value().positionTag.Value(); + } } return *this; @@ -112,19 +136,17 @@ struct AreaStructureWrapper : public chip::app::Clusters::ServiceArea::Structs:: * @param[in] aAreaTypeTag A common namespace Area tag - indicates an association of the location with an indoor or outdoor area * of a home. * @param[in] aLandmarkTag A common namespace Landmark tag - indicates an association of the location with a home landmark. - * @param[in] aPositionTag A common namespace Position tag - indicates the position of the location with respect to the - * landmark. - * @param[in] aSurfaceTag A common namespace Floor Surface tag - indicates an association of the location with a surface type. + * @param[in] aRelativePositionTag A common namespace Relative Position tag - indicates the position of the location with + * respect to the landmark. * * @note Requirements regarding what combinations of fields and values are valid are not checked by this class. * @note If aLocationName is larger than kLocationNameMaxSize, it will be truncated. * @note If aLocationName is an empty string and aFloorNumber and aAreaTypeTag are null, locationInfo will be set to null. */ - void Set(uint32_t aAreaID, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, + void Set(uint32_t aAreaID, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, const DataModel::Nullable & aFloorNumber, const DataModel::Nullable & aAreaType, const DataModel::Nullable & aLandmarkTag, - const DataModel::Nullable & aPositionTag, - const DataModel::Nullable & aSurfaceTag) + const DataModel::Nullable & aRelativePositionTag) { areaID = aAreaID; mapID = aMapId; @@ -143,30 +165,33 @@ struct AreaStructureWrapper : public chip::app::Clusters::ServiceArea::Structs:: areaDesc.locationInfo.SetNull(); } - areaDesc.landmarkTag = aLandmarkTag; - areaDesc.positionTag = aPositionTag; - areaDesc.surfaceTag = aSurfaceTag; - - // this assumes areaDesc structure was created above, if appropriate - if (!areaDesc.locationInfo.IsNull()) + // todo improve this when addressing issue https://github.com/project-chip/connectedhomeip/issues/34519 + if (aLandmarkTag.IsNull()) { - if (aLocationName.empty()) - { - areaDesc.locationInfo.Value().locationName = CharSpan(mLocationNameBuffer, 0); - } - else if (aLocationName.size() > sizeof(mLocationNameBuffer)) + areaDesc.landmarkInfo.SetNull(); + } + else + { + areaDesc.landmarkInfo.SetNonNull(); + areaDesc.landmarkInfo.Value().landmarkTag = aLandmarkTag.Value(); + if (aRelativePositionTag.IsNull()) { - // Save the truncated name that fits into available size. - memcpy(mLocationNameBuffer, aLocationName.data(), sizeof(mLocationNameBuffer)); - areaDesc.locationInfo.Value().locationName = CharSpan(mLocationNameBuffer, sizeof(mLocationNameBuffer)); + areaDesc.landmarkInfo.Value().positionTag.SetNull(); } else { - // Save full name. - memcpy(mLocationNameBuffer, aLocationName.data(), aLocationName.size()); - areaDesc.locationInfo.Value().locationName = CharSpan(mLocationNameBuffer, aLocationName.size()); + areaDesc.landmarkInfo.Value().positionTag.SetNonNull(); + areaDesc.landmarkInfo.Value().positionTag.Value() = aRelativePositionTag.Value(); } } + + // this assumes areaDesc structure was created above, if appropriate + if (!areaDesc.locationInfo.IsNull()) + { + auto sizeToCopy = std::min(sizeof(mLocationNameBuffer), aLocationName.size()); + memcpy(mLocationNameBuffer, aLocationName.data(), sizeToCopy); + areaDesc.locationInfo.Value().locationName = CharSpan(mLocationNameBuffer, sizeToCopy); + } } /** @@ -238,19 +263,22 @@ struct AreaStructureWrapper : public chip::app::Clusters::ServiceArea::Structs:: } } - if (areaDesc.landmarkTag != aOther.areaDesc.landmarkTag) + if (areaDesc.landmarkInfo.IsNull() != aOther.areaDesc.landmarkInfo.IsNull()) { return false; } - if (areaDesc.positionTag != aOther.areaDesc.positionTag) + if (!areaDesc.landmarkInfo.IsNull()) { - return false; - } + if (areaDesc.landmarkInfo.Value().landmarkTag != aOther.areaDesc.landmarkInfo.Value().landmarkTag) + { + return false; + } - if (areaDesc.surfaceTag != aOther.areaDesc.surfaceTag) - { - return false; + if (areaDesc.landmarkInfo.Value().positionTag != aOther.areaDesc.landmarkInfo.Value().positionTag) + { + return false; + } } return true; @@ -288,7 +316,7 @@ struct MapStructureWrapper : public chip::app::Clusters::ServiceArea::Structs::M * @note Requirements regarding what combinations of fields and values are 'valid' are not checked by this class. * @note If aMapName is larger than kMapNameMaxSize, it will be truncated. */ - MapStructureWrapper(uint8_t aMapId, const CharSpan & aMapName) { Set(aMapId, aMapName); } + MapStructureWrapper(uint32_t aMapId, const CharSpan & aMapName) { Set(aMapId, aMapName); } /** * @brief This is a copy constructor that initializes the map object with the values from another map object. All values are @@ -316,7 +344,7 @@ struct MapStructureWrapper : public chip::app::Clusters::ServiceArea::Structs::M * @note Requirements regarding what combinations of fields and values are 'valid' are not checked by this class. * @note if aMapName is larger than kMapNameMaxSize, it will be truncated. */ - void Set(uint8_t aMapId, const CharSpan & aMapName) + void Set(uint32_t aMapId, const CharSpan & aMapName) { mapID = aMapId; diff --git a/src/app/clusters/service-area-server/service-area-delegate.cpp b/src/app/clusters/service-area-server/service-area-delegate.cpp index 0d8c98a16f5d24..262fe047d22a1b 100644 --- a/src/app/clusters/service-area-server/service-area-delegate.cpp +++ b/src/app/clusters/service-area-server/service-area-delegate.cpp @@ -28,7 +28,7 @@ void Delegate::HandleSupportedAreasUpdated() mInstance->ClearProgress(); } -bool Delegate::GetSupportedMapById(uint8_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap) +bool Delegate::GetSupportedMapById(uint32_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap) { listIndex = 0; @@ -88,3 +88,6 @@ bool Delegate::IsProgressElement(uint32_t aAreaId) return GetProgressElementById(aAreaId, index, progressElement); } + +// todo: Should we add default implementations for the accessor methods of the non-mandatory attributes? +// This is so that devices that do not support these attributes are not forced to provide an implementation. diff --git a/src/app/clusters/service-area-server/service-area-delegate.h b/src/app/clusters/service-area-server/service-area-delegate.h index 1c324fe2c70bc7..823eaf6b537f19 100644 --- a/src/app/clusters/service-area-server/service-area-delegate.h +++ b/src/app/clusters/service-area-server/service-area-delegate.h @@ -49,7 +49,8 @@ class Delegate virtual ~Delegate() = default; /** - * Stop this class objects from being copied. + * Due to the coupling between the Delegate and Instance classes via the references they have to each other, + * copying a Delegate object might make things confusing. */ Delegate(const Delegate &) = delete; Delegate & operator=(const Delegate &) = delete; @@ -96,6 +97,7 @@ class Delegate /** * @brief The server instance ensures that the SelectedAreas and CurrentArea attributes are not null before * calling this method. + * @param[in] skippedArea the area ID to skip. * @param[out] skipStatusText text describing why current location cannot be skipped. * @return true if command is successful, false if the received skip request cannot be handled due to the current mode of the * device. @@ -119,7 +121,7 @@ class Delegate * InvalidInMode, the StatusText field SHOULD indicate why the request is not allowed, given the current mode of the device, * which may involve other clusters. */ - virtual bool HandleSkipCurrentArea(MutableCharSpan skipStatusText) + virtual bool HandleSkipCurrentArea(uint32_t skippedArea, MutableCharSpan skipStatusText) { // device support of this command is optional CopyCharSpanToMutableCharSpan("Skip Current Location command not supported by device"_span, skipStatusText); @@ -244,7 +246,7 @@ class Delegate * * @note may be overloaded in device implementation for optimization, if desired. */ - virtual bool GetSupportedMapById(uint8_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap); + virtual bool GetSupportedMapById(uint32_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap); /** * This method is called by the server instance to add a new map to the list. diff --git a/src/app/clusters/service-area-server/service-area-server.cpp b/src/app/clusters/service-area-server/service-area-server.cpp index fa5d626a5ab06c..17b55d12aa9352 100644 --- a/src/app/clusters/service-area-server/service-area-server.cpp +++ b/src/app/clusters/service-area-server/service-area-server.cpp @@ -123,7 +123,7 @@ void Instance::InvokeCommand(HandlerContext & handlerContext) case Commands::SkipArea::Id: return CommandHandlerInterface::HandleCommand( - handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleSkipCurrentAreaCmd(ctx); }); + handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleSkipCurrentAreaCmd(ctx, req); }); } } @@ -217,7 +217,7 @@ void Instance::HandleSelectAreasCmd(HandlerContext & ctx, const Commands::Select auto exitResponse = [ctx](SelectAreasStatus status, CharSpan statusText) { Commands::SelectAreasResponse::Type response{ .status = status, - .statusText = Optional(statusText), + .statusText = statusText, }; ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); }; @@ -358,7 +358,7 @@ void Instance::HandleSelectAreasCmd(HandlerContext & ctx, const Commands::Select exitResponse(SelectAreasStatus::kSuccess, ""_span); } -void Instance::HandleSkipCurrentAreaCmd(HandlerContext & ctx) +void Instance::HandleSkipCurrentAreaCmd(HandlerContext & ctx, const Commands::SkipArea::DecodableType & req) { ChipLogDetail(Zcl, "Service Area: HandleSkipCurrentArea"); @@ -366,7 +366,7 @@ void Instance::HandleSkipCurrentAreaCmd(HandlerContext & ctx) auto exitResponse = [ctx](SkipAreaStatus status, CharSpan statusText) { Commands::SkipAreaResponse::Type response{ .status = status, - .statusText = Optional(statusText), + .statusText = statusText, }; ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); }; @@ -396,7 +396,7 @@ void Instance::HandleSkipCurrentAreaCmd(HandlerContext & ctx) char skipStatusBuffer[kMaxSizeStatusText]; MutableCharSpan skipStatusText(skipStatusBuffer); - if (!mDelegate->HandleSkipCurrentArea(skipStatusText)) + if (!mDelegate->HandleSkipCurrentArea(req.skippedArea, skipStatusText)) { exitResponse(SkipAreaStatus::kInvalidInMode, skipStatusText); return; @@ -449,39 +449,45 @@ bool Instance::IsSupportedLocation(uint32_t aAreaId) bool Instance::IsValidSupportedLocation(const AreaStructureWrapper & aLocation) { - // If the HomeLocationInfo field is null, the LandmarkTag field SHALL NOT be null. - // If the LandmarkTag field is null, the HomeLocationInfo field SHALL NOT be null. - if (aLocation.areaDesc.locationInfo.IsNull() && aLocation.areaDesc.landmarkTag.IsNull()) + // If the LocationInfo field is null, the LandmarkInfo field SHALL NOT be null. + // If the LandmarkInfo field is null, the LocationInfo field SHALL NOT be null. + if (aLocation.areaDesc.locationInfo.IsNull() && aLocation.areaDesc.landmarkInfo.IsNull()) { - ChipLogDetail(Zcl, "IsValidAsSupportedLocation %u - must have locationInfo and/or LandmarkTag", aLocation.areaID); + ChipLogDetail(Zcl, "IsValidAsSupportedLocation %u - must have locationInfo and/or LandmarkInfo", aLocation.areaID); return false; } - // If HomeLocationInfo is not null, and its LocationName field is an empty string, at least one of the following SHALL NOT - // be null: HomeLocationInfo's FloorNumber field, HomeLocationInfo's AreaType field, the LandmarkTag field + // If LocationInfo is not null, and its LocationName field is an empty string, at least one of the following SHALL NOT + // be null: LocationInfo's FloorNumber field, LocationInfo's AreaType field, the LandmarkInfo if (!aLocation.areaDesc.locationInfo.IsNull()) { if (aLocation.areaDesc.locationInfo.Value().locationName.empty() && aLocation.areaDesc.locationInfo.Value().floorNumber.IsNull() && - aLocation.areaDesc.locationInfo.Value().areaType.IsNull() && aLocation.areaDesc.landmarkTag.IsNull()) + aLocation.areaDesc.locationInfo.Value().areaType.IsNull() && aLocation.areaDesc.landmarkInfo.IsNull()) { ChipLogDetail( - Zcl, "IsValidAsSupportedLocation %u - LocationName is empty string, FloorNumber, AreaType, LandmarkTag are null", + Zcl, "IsValidAsSupportedLocation %u - LocationName is empty string, FloorNumber, AreaType, LandmarkInfo are null", aLocation.areaID); return false; } } - // If the LandmarkTag field is null, the PositionTag field SHALL be null. - if (aLocation.areaDesc.landmarkTag.IsNull() && !aLocation.areaDesc.positionTag.IsNull()) + // The mapID field SHALL be null if SupportedMaps is not supported or SupportedMaps is an empty list. + bool shouldMapsBeNull = false; + if (mFeature.Has(Feature::kMaps)) { - ChipLogDetail(Zcl, "IsValidAsSupportedLocation %u - PositionTag with no LandmarkTag", aLocation.areaID); - return false; + if (mDelegate->GetNumberOfSupportedMaps() == 0) + { + shouldMapsBeNull = true; + } + } + else + { + shouldMapsBeNull = true; } - if (mDelegate->GetNumberOfSupportedMaps() == 0) + if (shouldMapsBeNull) { - // If the SupportedMaps attribute is null, mapid SHALL be null. if (!aLocation.mapID.IsNull()) { ChipLogDetail(Zcl, "IsValidSupportedLocation %u - map Id %u is not in empty supported map list", aLocation.areaID, @@ -570,15 +576,14 @@ bool Instance::ReportEstimatedEndTimeChange(const DataModel::Nullable return (aEstimatedEndTime.Value() < mEstimatedEndTime.Value()); } -bool Instance::AddSupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, +bool Instance::AddSupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, const DataModel::Nullable & aFloorNumber, const DataModel::Nullable & aAreaType, const DataModel::Nullable & aLandmarkTag, - const DataModel::Nullable & aPositionTag, - const DataModel::Nullable & aSurfaceTag) + const DataModel::Nullable & aRelativePositionTag) { // Create location object for validation. - AreaStructureWrapper aNewArea(aAreaId, aMapId, aLocationName, aFloorNumber, aAreaType, aLandmarkTag, aPositionTag, aSurfaceTag); + AreaStructureWrapper aNewArea(aAreaId, aMapId, aLocationName, aFloorNumber, aAreaType, aLandmarkTag, aRelativePositionTag); // Does device mode allow this attribute to be updated? if (!mDelegate->IsSupportedAreasChangeAllowed()) @@ -621,12 +626,11 @@ bool Instance::AddSupportedLocation(uint32_t aAreaId, const DataModel::Nullable< return true; } -bool Instance::ModifySupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, +bool Instance::ModifySupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, const DataModel::Nullable & aFloorNumber, const DataModel::Nullable & aAreaType, const DataModel::Nullable & aLandmarkTag, - const DataModel::Nullable & aPositionTag, - const DataModel::Nullable & aSurfaceTag) + const DataModel::Nullable & aRelativePositionTag) { bool mapIDChanged = false; uint32_t listIndex; @@ -653,8 +657,7 @@ bool Instance::ModifySupportedLocation(uint32_t aAreaId, const DataModel::Nullab } // create new location object for validation - AreaStructureWrapper aNewArea(aAreaId, aMapId, aLocationName, aFloorNumber, aAreaType, aLandmarkTag, aPositionTag, - aSurfaceTag); + AreaStructureWrapper aNewArea(aAreaId, aMapId, aLocationName, aFloorNumber, aAreaType, aLandmarkTag, aRelativePositionTag); // verify cluster requirements concerning valid fields and field relationships if (!IsValidSupportedLocation(aNewArea)) @@ -708,7 +711,7 @@ bool Instance::ClearSupportedAreas() //************************************************************************* // Supported Maps manipulators -bool Instance::IsSupportedMap(uint8_t aMapId) +bool Instance::IsSupportedMap(uint32_t aMapId) { uint32_t ignoredIndex; MapStructureWrapper ignoredMap; @@ -716,7 +719,7 @@ bool Instance::IsSupportedMap(uint8_t aMapId) return mDelegate->GetSupportedMapById(aMapId, ignoredIndex, ignoredMap); } -bool Instance::AddSupportedMap(uint8_t aMapId, const CharSpan & aMapName) +bool Instance::AddSupportedMap(uint32_t aMapId, const CharSpan & aMapName) { // check max# of list entries if (mDelegate->GetNumberOfSupportedMaps() >= kMaxNumSupportedMaps) @@ -769,7 +772,7 @@ bool Instance::AddSupportedMap(uint8_t aMapId, const CharSpan & aMapName) return true; } -bool Instance::RenameSupportedMap(uint8_t aMapId, const CharSpan & newMapName) +bool Instance::RenameSupportedMap(uint32_t aMapId, const CharSpan & newMapName) { uint32_t modifiedIndex; MapStructureWrapper modifiedMap; diff --git a/src/app/clusters/service-area-server/service-area-server.h b/src/app/clusters/service-area-server/service-area-server.h index 90cf4275cf3871..815ff2ed0eac72 100644 --- a/src/app/clusters/service-area-server/service-area-server.h +++ b/src/app/clusters/service-area-server/service-area-server.h @@ -115,15 +115,16 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface /** * @param[in, out] ctx Returns the Interaction Model status code which was user determined in the business logic. * If the input value is invalid, returns the Interaction Model status code of INVALID_COMMAND. - * @param[in] req the command parameters + * @param[in] req the command parameters. */ void HandleSelectAreasCmd(HandlerContext & ctx, const Commands::SelectAreas::DecodableType & req); /** * @param[in, out] ctx Returns the Interaction Model status code which was user determined in the business logic. * If the input value is invalid, returns the Interaction Model status code of INVALID_COMMAND. + * @param[in] req the command parameters. */ - void HandleSkipCurrentAreaCmd(HandlerContext & ctx); + void HandleSkipCurrentAreaCmd(HandlerContext & ctx, const Commands::SkipArea::DecodableType & req); //************************************************************************* // attribute notifications @@ -176,18 +177,17 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface * @param[in] aAreaType common namespace Area tag - indicates an association of the location with an indoor or outdoor area of a * home. * @param[in] aLandmarkTag common namespace Landmark tag - indicates an association of the location with a home landmark. - * @param[in] aPositionTag common namespace Position tag - indicates the position of the location with respect to the landmark. - * @param[in] aSurfaceTag common namespace Floor Surface tag - indicates an association of the location with a surface type. + * @param[in] aRelativePositionTag common namespace Relative Position tag - indicates the position of the location with respect + * to the landmark. * @return true if the new location passed validation checks and was successfully added to the list. * * @note if aLocationName is larger than kLocationNameMaxSize, it will be truncated. */ - bool AddSupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, + bool AddSupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, const DataModel::Nullable & aFloorNumber, const DataModel::Nullable & aAreaType, const DataModel::Nullable & aLandmarkTag, - const DataModel::Nullable & aPositionTag, - const DataModel::Nullable & aSurfaceTag); + const DataModel::Nullable & aRelativePositionTag); /** * @brief Modify/replace an existing location in the supported locations list. @@ -198,20 +198,19 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface * @param[in] aAreaType common namespace Area tag - indicates an association of the location with an indoor or outdoor area of a * home. * @param[in] aLandmarkTag common namespace Landmark tag - indicates an association of the location with a home landmark. - * @param[in] aPositionTag common namespace Position tag - indicates the position of the location with respect to the landmark. - * @param[in] aSurfaceTag common namespace Floor Surface tag - indicates an association of the location with a surface type. + * @param[in] aRelativePositionTag common namespace Relative Position tag - indicates the position of the location with respect + * to the landmark. * @return true if the location is a member of supported locations, the modifications pass all validation checks and the * location was modified. * * @note if aLocationName is larger than kLocationNameMaxSize, it will be truncated. * @note if mapID is changed, the delegate's HandleSupportedAreasUpdated method is called. */ - bool ModifySupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, + bool ModifySupportedLocation(uint32_t aAreaId, const DataModel::Nullable & aMapId, const CharSpan & aLocationName, const DataModel::Nullable & aFloorNumber, const DataModel::Nullable & aAreaType, const DataModel::Nullable & aLandmarkTag, - const DataModel::Nullable & aPositionTag, - const DataModel::Nullable & aSurfaceTag); + const DataModel::Nullable & aRelativePositionTag); /** * @return true if the SupportedAreas attribute was not already null. @@ -226,7 +225,7 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface /** * @return true if a map with the aMapId ID exists in the supported maps attribute. False otherwise. */ - bool IsSupportedMap(uint8_t aMapId); + bool IsSupportedMap(uint32_t aMapId); /** * @brief Add a new map to the supported maps list. @@ -234,7 +233,7 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface * @param[in] aMapName The name of the map to be added. This cannot be an empty string. * @return true if the new map passed validation checks and was successfully added to the list. */ - bool AddSupportedMap(uint8_t aMapId, const CharSpan & aMapName); + bool AddSupportedMap(uint32_t aMapId, const CharSpan & aMapName); /** * @brief Rename an existing map in the supported maps list. @@ -244,7 +243,7 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface * * @note if the specified map is not a member of the supported maps list, returns false with no action taken. */ - bool RenameSupportedMap(uint8_t aMapId, const CharSpan & newMapName); + bool RenameSupportedMap(uint32_t aMapId, const CharSpan & newMapName); /** * @return true if the SupportedMaps attribute was not already null. diff --git a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp index 5592da410100c2..4956f3f1fcc0be 100644 --- a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp +++ b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp @@ -38,6 +38,7 @@ #include "platform/CHIPDeviceEvent.h" #include "platform/PlatformManager.h" #include "protocols/interaction_model/StatusCode.h" +#include namespace chip { namespace app { @@ -46,21 +47,24 @@ namespace ThreadBorderRouterManagement { using Protocols::InteractionModel::Status; -static bool IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx) +bool ServerInstance::IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx) { +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST + if (mSkipCASESessionCheck) + { + return true; + } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST Messaging::ExchangeContext * exchangeCtx = ctx.mCommandHandler.GetExchangeContext(); return exchangeCtx && exchangeCtx->HasSessionHandle() && exchangeCtx->GetSessionHandle()->IsSecureSession() && exchangeCtx->GetSessionHandle()->AsSecureSession()->GetSecureSessionType() == Transport::SecureSession::Type::kCASE; } -Status ServerInstance::HandleGetDatasetRequest(bool isOverCASESession, Delegate::DatasetType type, +Status ServerInstance::HandleGetDatasetRequest(CommandHandlerInterface::HandlerContext & ctx, Delegate::DatasetType type, Thread::OperationalDataset & dataset) { VerifyOrDie(mDelegate); - if (!isOverCASESession) - { - return Status::UnsupportedAccess; - } + VerifyOrReturnValue(IsCommandOverCASESession(ctx), Status::UnsupportedAccess); CHIP_ERROR err = mDelegate->GetDataset(dataset, type); if (err != CHIP_NO_ERROR) @@ -70,7 +74,7 @@ Status ServerInstance::HandleGetDatasetRequest(bool isOverCASESession, Delegate: return Status::Success; } -Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHandler, +Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandlerInterface::HandlerContext & ctx, const Commands::SetActiveDatasetRequest::DecodableType & req) { // The SetActiveDatasetRequest command SHALL be FailSafeArmed. Upon receiving this command, the Thread BR will set its @@ -80,7 +84,8 @@ Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHan // reverted. If the FailSafe timer expires before the Thread BR responds, the Thread BR will respond with a timeout status and // the active dataset should also be reverted. VerifyOrDie(mDelegate); - VerifyOrReturnValue(mFailsafeContext.IsFailSafeArmed(commandHandler->GetAccessingFabricIndex()), Status::FailsafeRequired); + VerifyOrReturnValue(IsCommandOverCASESession(ctx), Status::UnsupportedAccess); + VerifyOrReturnValue(mFailsafeContext.IsFailSafeArmed(ctx.mCommandHandler.GetAccessingFabricIndex()), Status::FailsafeRequired); Thread::OperationalDataset activeDataset; Thread::OperationalDataset currentActiveDataset; @@ -101,17 +106,19 @@ Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHan { return Status::Busy; } - commandHandler->FlushAcksRightAwayOnSlowCommand(); - mAsyncCommandHandle = CommandHandler::Handle(commandHandler); + ctx.mCommandHandler.FlushAcksRightAwayOnSlowCommand(); + mAsyncCommandHandle = CommandHandler::Handle(&ctx.mCommandHandler); mBreadcrumb = req.breadcrumb; mSetActiveDatasetSequenceNumber++; mDelegate->SetActiveDataset(activeDataset, mSetActiveDatasetSequenceNumber, this); return Status::Success; } -Status ServerInstance::HandleSetPendingDatasetRequest(const Commands::SetPendingDatasetRequest::DecodableType & req) +Status ServerInstance::HandleSetPendingDatasetRequest(CommandHandlerInterface::HandlerContext & ctx, + const Commands::SetPendingDatasetRequest::DecodableType & req) { VerifyOrDie(mDelegate); + VerifyOrReturnValue(IsCommandOverCASESession(ctx), Status::UnsupportedAccess); if (!mDelegate->GetPanChangeSupported()) { return Status::UnsupportedCommand; @@ -143,21 +150,21 @@ void ServerInstance::InvokeCommand(HandlerContext & ctxt) case Commands::GetActiveDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { Thread::OperationalDataset dataset; - Status status = HandleGetActiveDatasetRequest(IsCommandOverCASESession(ctx), dataset); + Status status = HandleGetActiveDatasetRequest(ctx, dataset); AddDatasetResponse(ctx, status, dataset); }); break; case Commands::GetPendingDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { Thread::OperationalDataset dataset; - Status status = HandleGetPendingDatasetRequest(IsCommandOverCASESession(ctx), dataset); + Status status = HandleGetPendingDatasetRequest(ctx, dataset); AddDatasetResponse(ctx, status, dataset); }); break; case Commands::SetActiveDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { mPath = ctx.mRequestPath; - Status status = HandleSetActiveDatasetRequest(&ctx.mCommandHandler, req); + Status status = HandleSetActiveDatasetRequest(ctx, req); if (status != Status::Success) { // If status is not Success, we should immediately report the status. Otherwise the async work will report the @@ -168,7 +175,8 @@ void ServerInstance::InvokeCommand(HandlerContext & ctxt) break; case Commands::SetPendingDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { - ctx.mCommandHandler.AddStatus(ctx.mRequestPath, HandleSetPendingDatasetRequest(req)); + Status status = HandleSetPendingDatasetRequest(ctx, req); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); }); break; default: @@ -199,16 +207,28 @@ CHIP_ERROR ServerInstance::ReadBorderAgentID(MutableByteSpan & outBorderAgentId) return CHIP_NO_ERROR; } -Optional ServerInstance::ReadActiveDatasetTimestamp() +std::optional ServerInstance::ReadActiveDatasetTimestamp() { uint64_t activeDatasetTimestampValue = 0; Thread::OperationalDataset activeDataset; if ((mDelegate->GetDataset(activeDataset, Delegate::DatasetType::kActive) == CHIP_NO_ERROR) && (activeDataset.GetActiveTimestamp(activeDatasetTimestampValue) == CHIP_NO_ERROR)) { - return MakeOptional(activeDatasetTimestampValue); + return std::make_optional(activeDatasetTimestampValue); + } + return std::nullopt; +} + +std::optional ServerInstance::ReadPendingDatasetTimestamp() +{ + uint64_t pendingDatasetTimestampValue = 0; + Thread::OperationalDataset pendingDataset; + if ((mDelegate->GetDataset(pendingDataset, Delegate::DatasetType::kPending) == CHIP_NO_ERROR) && + (pendingDataset.GetActiveTimestamp(pendingDatasetTimestampValue) == CHIP_NO_ERROR)) + { + return std::make_optional(pendingDatasetTimestampValue); } - return NullOptional; + return std::nullopt; } CHIP_ERROR ServerInstance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) @@ -259,9 +279,13 @@ CHIP_ERROR ServerInstance::Read(const ConcreteReadAttributePath & aPath, Attribu break; } case Attributes::ActiveDatasetTimestamp::Id: { - Optional activeDatasetTimestamp = ReadActiveDatasetTimestamp(); - status = activeDatasetTimestamp.HasValue() ? aEncoder.Encode(DataModel::MakeNullable(activeDatasetTimestamp.Value())) - : aEncoder.EncodeNull(); + std::optional activeDatasetTimestamp = ReadActiveDatasetTimestamp(); + status = activeDatasetTimestamp.has_value() ? aEncoder.Encode(activeDatasetTimestamp.value()) : aEncoder.EncodeNull(); + break; + } + case Attributes::PendingDatasetTimestamp::Id: { + std::optional pendingDatasetTimestamp = ReadPendingDatasetTimestamp(); + status = pendingDatasetTimestamp.has_value() ? aEncoder.Encode(pendingDatasetTimestamp.value()) : aEncoder.EncodeNull(); break; } default: diff --git a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h index 639e7b13e77ac0..ae8f65665abb62 100644 --- a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h +++ b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h @@ -64,25 +64,30 @@ class ServerInstance : public CommandHandlerInterface, // TODO: Split the business logic from the unit test class friend class TestThreadBorderRouterManagementCluster; // Command Handlers - Status HandleGetActiveDatasetRequest(bool isOverCASESession, Thread::OperationalDataset & dataset) + Status HandleGetActiveDatasetRequest(HandlerContext & ctx, Thread::OperationalDataset & dataset) { - return HandleGetDatasetRequest(isOverCASESession, Delegate::DatasetType::kActive, dataset); + return HandleGetDatasetRequest(ctx, Delegate::DatasetType::kActive, dataset); } - Status HandleGetPendingDatasetRequest(bool isOverCASESession, Thread::OperationalDataset & dataset) + Status HandleGetPendingDatasetRequest(HandlerContext & ctx, Thread::OperationalDataset & dataset) { - return HandleGetDatasetRequest(isOverCASESession, Delegate::DatasetType::kPending, dataset); + return HandleGetDatasetRequest(ctx, Delegate::DatasetType::kPending, dataset); } - Status HandleSetActiveDatasetRequest(CommandHandler * commandHandler, - const Commands::SetActiveDatasetRequest::DecodableType & req); - Status HandleSetPendingDatasetRequest(const Commands::SetPendingDatasetRequest::DecodableType & req); - Status HandleGetDatasetRequest(bool isOverCASESession, Delegate::DatasetType type, Thread::OperationalDataset & dataset); + Status HandleSetActiveDatasetRequest(HandlerContext & ctx, const Commands::SetActiveDatasetRequest::DecodableType & req); + Status HandleSetPendingDatasetRequest(HandlerContext & ctx, const Commands::SetPendingDatasetRequest::DecodableType & req); + Status HandleGetDatasetRequest(HandlerContext & ctx, Delegate::DatasetType type, Thread::OperationalDataset & dataset); // Attribute Read handlers void ReadFeatureMap(BitFlags & feature); - Optional ReadActiveDatasetTimestamp(); + std::optional ReadActiveDatasetTimestamp(); + std::optional ReadPendingDatasetTimestamp(); CHIP_ERROR ReadBorderRouterName(MutableCharSpan & borderRouterName); CHIP_ERROR ReadBorderAgentID(MutableByteSpan & borderAgentId); +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST + void SetSkipCASESessionCheck(bool skipCheck) { mSkipCASESessionCheck = skipCheck; } + bool mSkipCASESessionCheck; +#endif + bool IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx); static void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg); void OnFailSafeTimerExpired(); void CommitSavedBreadcrumb(); diff --git a/src/app/data-model-provider/ActionReturnStatus.cpp b/src/app/data-model-provider/ActionReturnStatus.cpp index 7857246a0e861f..cbf501072c1f69 100644 --- a/src/app/data-model-provider/ActionReturnStatus.cpp +++ b/src/app/data-model-provider/ActionReturnStatus.cpp @@ -149,45 +149,42 @@ bool ActionReturnStatus::IsOutOfSpaceEncodingResponse() const return false; } -const char * ActionReturnStatus::c_str() const +const char * ActionReturnStatus::c_str(ActionReturnStatus::StringStorage & storage) const { - - // Generally size should be sufficient. - // len("Status<123>, Code 255") == 21 (and then 22 for null terminator. We have slack.) - static chip::StringBuilder<32> sFormatBuffer; - if (const CHIP_ERROR * err = std::get_if(&mReturnStatus)) { #if CHIP_CONFIG_ERROR_FORMAT_AS_STRING return err->Format(); // any length #else - sFormatBuffer.Reset().AddFormat("%" CHIP_ERROR_FORMAT, err->Format()); - return sFormatBuffer.c_str(); + storage.formatBuffer.Reset().AddFormat("%" CHIP_ERROR_FORMAT, err->Format()); + return storage.formatBuffer.c_str(); #endif } if (const ClusterStatusCode * status = std::get_if(&mReturnStatus)) { + storage.formatBuffer.Reset(); + #if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT - sFormatBuffer.AddFormat("%s(%d)", Protocols::InteractionModel::StatusName(status->GetStatus()), - static_cast(status->GetStatus())); + storage.formatBuffer.AddFormat("%s(%d)", Protocols::InteractionModel::StatusName(status->GetStatus()), + static_cast(status->GetStatus())); #else if (status->IsSuccess()) { - sFormatBuffer.Add("Success"); + storage.formatBuffer.Add("Success"); } else { - sFormatBuffer.AddFormat("Status<%d>", static_cast(status->GetStatus())); + storage.formatBuffer.AddFormat("Status<%d>", static_cast(status->GetStatus())); } #endif chip::Optional clusterCode = status->GetClusterSpecificCode(); if (clusterCode.HasValue()) { - sFormatBuffer.AddFormat(", Code %d", static_cast(clusterCode.Value())); + storage.formatBuffer.AddFormat(", Code %d", static_cast(clusterCode.Value())); } - return sFormatBuffer.c_str(); + return storage.formatBuffer.c_str(); } // all std::variant cases exhausted diff --git a/src/app/data-model-provider/ActionReturnStatus.h b/src/app/data-model-provider/ActionReturnStatus.h index 516541483ad068..f24d0bf2099e42 100644 --- a/src/app/data-model-provider/ActionReturnStatus.h +++ b/src/app/data-model-provider/ActionReturnStatus.h @@ -44,6 +44,21 @@ namespace DataModel { class ActionReturnStatus { public: + /// Provides storage for the c_str() call for the action status. + struct StringStorage + { + // Generally size should be sufficient. + // The longest status code from StatusCodeList is NO_UPSTREAM_SUBSCRIPTION(197) + // so we need space for one of: + // "NO_UPSTREAM_SUBSCRIPTION(197)\0" = 30 // explicit verbose status code + // "FAILURE(1), Code 255\0") // cluster failure, verbose + // "SUCCESS(0), Code 255\0") // cluster success, verbose + // "Status<197>, Code 255\0") // Cluster failure, non-verbose + // + // CHIP_ERROR has its own (global/static!) storage + chip::StringBuilder<32> formatBuffer; + }; + ActionReturnStatus(CHIP_ERROR error) : mReturnStatus(error) {} ActionReturnStatus(Protocols::InteractionModel::Status status) : mReturnStatus(Protocols::InteractionModel::ClusterStatusCode(status)) @@ -81,12 +96,8 @@ class ActionReturnStatus /// Get the formatted string of this status. /// - /// NOTE: this is NOT thread safe in the general case, however the safety guarantees - /// are similar to chip::ErrorStr which also assumes a static buffer. - /// - /// Use this in the chip main event loop (and since that is a single thread, - /// there should be no races) - const char * c_str() const; + /// May use `storage` for storying the actual underlying character string. + const char * c_str(StringStorage & storage) const; private: std::variant mReturnStatus; diff --git a/src/app/data-model-provider/StringBuilderAdapters.cpp b/src/app/data-model-provider/StringBuilderAdapters.cpp index 93ad9863fc9762..5b8f6db5eab31c 100644 --- a/src/app/data-model-provider/StringBuilderAdapters.cpp +++ b/src/app/data-model-provider/StringBuilderAdapters.cpp @@ -23,7 +23,8 @@ template <> StatusWithSize ToString(const chip::app::DataModel::ActionReturnStatus & status, pw::span buffer) { - return pw::string::Format(buffer, "ActionReturnStatus<%s>", status.c_str()); + chip::app::DataModel::ActionReturnStatus::StringStorage storage; + return pw::string::Format(buffer, "ActionReturnStatus<%s>", status.c_str(storage)); } } // namespace pw diff --git a/src/app/data-model-provider/tests/TestActionReturnStatus.cpp b/src/app/data-model-provider/tests/TestActionReturnStatus.cpp index a194c0838d7d12..8ca1993d149e1a 100644 --- a/src/app/data-model-provider/tests/TestActionReturnStatus.cpp +++ b/src/app/data-model-provider/tests/TestActionReturnStatus.cpp @@ -111,3 +111,45 @@ TEST(TestActionReturnStatus, TestStatusCode) ASSERT_EQ(ActionReturnStatus(CHIP_IM_CLUSTER_STATUS(0x12)).GetStatusCode(), ClusterStatusCode::ClusterSpecificFailure(0x12)); ASSERT_EQ(ActionReturnStatus(CHIP_IM_GLOBAL_STATUS(Timeout)).GetStatusCode(), ClusterStatusCode(Status::Timeout)); } + +TEST(TestActionReturnStatus, TestCString) +{ + /// only tests the strings that we build and NOT the CHIP_ERROR ones which + /// are tested separately. for chip_error we just say it should not be empty. + ActionReturnStatus::StringStorage buffer; + ActionReturnStatus status(Status::Success); + + // chip-error returns something non-empty + status = CHIP_ERROR_NOT_FOUND; + ASSERT_STRNE(status.c_str(buffer), ""); + + status = CHIP_NO_ERROR; + ASSERT_STRNE(status.c_str(buffer), ""); + + // the items below we control +#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT + status = Status::Success; + ASSERT_STREQ(status.c_str(buffer), "SUCCESS(0)"); + + status = Status::UnsupportedCommand; + ASSERT_STREQ(status.c_str(buffer), "UNSUPPORTED_COMMAND(129)"); + + status = ClusterStatusCode::ClusterSpecificSuccess(31); + ASSERT_STREQ(status.c_str(buffer), "SUCCESS(0), Code 31"); + + status = ClusterStatusCode::ClusterSpecificFailure(32); + ASSERT_STREQ(status.c_str(buffer), "FAILURE(1), Code 32"); +#else + status = Status::Success; + ASSERT_STREQ(status.c_str(buffer), "Success"); + + status = Status::UnsupportedCommand; + ASSERT_STREQ(status.c_str(buffer), "Status<129>"); + + status = ClusterStatusCode::ClusterSpecificSuccess(31); + ASSERT_STREQ(status.c_str(buffer), "Success, Code 31"); + + status = ClusterStatusCode::ClusterSpecificFailure(32); + ASSERT_STREQ(status.c_str(buffer), "Status<1>, Code 32"); +#endif +} diff --git a/src/app/reporting/Read-Checked.cpp b/src/app/reporting/Read-Checked.cpp index cd821bc67ae4f6..2bb969aac094d2 100644 --- a/src/app/reporting/Read-Checked.cpp +++ b/src/app/reporting/Read-Checked.cpp @@ -82,12 +82,13 @@ ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, const Ac if (statusEmber != statusDm) { - StringBuilder<128> buffer; + ActionReturnStatus::StringStorage buffer; + // Note log + chipDie instead of VerifyOrDie so that breakpoints (and usage of rr) // is easier to debug. ChipLogError(Test, "Different return codes between ember and DM"); - ChipLogError(Test, " Ember status: %s", statusEmber.c_str()); - ChipLogError(Test, " DM status: %s", statusDm.c_str()); + ChipLogError(Test, " Ember status: %s", statusEmber.c_str(buffer)); + ChipLogError(Test, " DM status: %s", statusDm.c_str(buffer)); // For time-dependent data, we may have size differences here: one data fitting in buffer // while another not, resulting in different errors (success vs out of space). diff --git a/src/app/reporting/Read-DataModel.cpp b/src/app/reporting/Read-DataModel.cpp index 7df19e2c89ecfc..b84d6586d2e2a0 100644 --- a/src/app/reporting/Read-DataModel.cpp +++ b/src/app/reporting/Read-DataModel.cpp @@ -95,7 +95,8 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode // and will be sent to the client as well). if (!status.IsOutOfSpaceEncodingResponse()) { - ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str()); + DataModel::ActionReturnStatus::StringStorage storage; + ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str(storage)); } return status; } diff --git a/src/app/tests/TestThreadBorderRouterManagementCluster.cpp b/src/app/tests/TestThreadBorderRouterManagementCluster.cpp index a6915824fb2ea2..92018cf4900cc8 100644 --- a/src/app/tests/TestThreadBorderRouterManagementCluster.cpp +++ b/src/app/tests/TestThreadBorderRouterManagementCluster.cpp @@ -17,18 +17,23 @@ #include #include +#include +#include #include #include #include #include #include #include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -153,10 +158,10 @@ static FailSafeContext sTestFailsafeContext; static TestDelegate sTestDelegate; static ServerInstance sTestSeverInstance(kTestEndpointId, &sTestDelegate, sTestFailsafeContext); -class TestSetActiveDatasetCommandHandler : public CommandHandler +class TestCommandHandler : public CommandHandler { public: - TestSetActiveDatasetCommandHandler() : mClusterStatus(Protocols::InteractionModel::Status::Success) {} + TestCommandHandler() : mClusterStatus(Protocols::InteractionModel::Status::Success) {} CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::ClusterStatusCode & aStatus, const char * context = nullptr) { @@ -197,7 +202,7 @@ class TestSetActiveDatasetCommandHandler : public CommandHandler Protocols::InteractionModel::ClusterStatusCode mClusterStatus; }; -TestSetActiveDatasetCommandHandler sTestCommandHandler; +TestCommandHandler sTestCommandHandler; class TestThreadBorderRouterManagementCluster : public ::testing::Test { @@ -256,25 +261,31 @@ TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestAttributeRead) EXPECT_TRUE(agentIdSpan.data_equal(ByteSpan(sTestDelegate.kTestBorderAgentId))); // ActiveDatasetTimestamp attribute // The active dataset timestamp should be null when no active dataset is configured - Optional timestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); - EXPECT_FALSE(timestamp.HasValue()); + std::optional timestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); + EXPECT_FALSE(timestamp.has_value()); } TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestCommandHandle) { // Test GetActiveDatasetRequest and GetPendingDatasetRequest commands Thread::OperationalDataset dataset; + ThreadBorderRouterManagement::Commands::SetActiveDatasetRequest::DecodableType req1; + Commands::SetPendingDatasetRequest::DecodableType req2; using DatasetType = Delegate::DatasetType; using Status = Protocols::InteractionModel::Status; - // The GetDataset requests should over CASE session. - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(false /* isOverCASESession */, DatasetType::kActive, dataset), - Status::UnsupportedAccess); - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(false, DatasetType::kPending, dataset), Status::UnsupportedAccess); + ConcreteCommandPath testPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); + TLV::TLVReader testTLVReader; + CommandHandlerInterface::HandlerContext ctx(sTestCommandHandler, testPath, testTLVReader); + // All the command should be over CASE session. + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kActive, dataset), Status::UnsupportedAccess); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kPending, dataset), Status::UnsupportedAccess); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::UnsupportedAccess); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::UnsupportedAccess); + sTestSeverInstance.SetSkipCASESessionCheck(true); // The GetDataset should return NotFound when no dataset is configured. - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kActive, dataset), Status::NotFound); - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kPending, dataset), Status::NotFound); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kActive, dataset), Status::NotFound); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kPending, dataset), Status::NotFound); // Test SetActiveDatasetRequest - ThreadBorderRouterManagement::Commands::SetActiveDatasetRequest::DecodableType req1; uint8_t invalidDataset[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; uint8_t validDataset[] = { 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0b, 0x35, 0x06, 0x00, 0x04, 0x00, 0x1f, 0xff, 0xe0, 0x02, 0x08, 0xde, 0xaa, 0x00, 0xbe, 0xef, 0x00, 0xca, 0xef, 0x07, @@ -282,19 +293,19 @@ TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestCommandHandle) 0xc5, 0x25, 0x7f, 0x68, 0x4c, 0x54, 0x9d, 0x6a, 0x57, 0x5e, 0x03, 0x0a, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x01, 0x02, 0xc1, 0x15, 0x04, 0x10, 0xcb, 0x13, 0x47, 0xeb, 0x0c, 0xd4, 0xb3, 0x5c, 0xd1, 0x42, 0xda, 0x5e, 0x6d, 0xf1, 0x8b, 0x88, 0x0c, 0x04, 0x02, 0xa0, 0xf7, 0xf8 }; - Optional activeDatasetTimestamp = chip::NullOptional; - activeDatasetTimestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); - EXPECT_FALSE(activeDatasetTimestamp.HasValue()); + std::optional activeDatasetTimestamp = std::nullopt; + activeDatasetTimestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); + EXPECT_FALSE(activeDatasetTimestamp.has_value()); req1.activeDataset = ByteSpan(invalidDataset); // SetActiveDatasetRequest is FailsafeRequired. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::FailsafeRequired); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::FailsafeRequired); EXPECT_EQ(sTestFailsafeContext.ArmFailSafe(kTestAccessingFabricIndex, System::Clock::Seconds16(1)), CHIP_NO_ERROR); // SetActiveDatasetRequest should return InvalidCommand when dataset is invalid. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::InvalidCommand); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::InvalidCommand); req1.activeDataset = ByteSpan(validDataset); - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::Success); // When the Server is handling a SetActiveDatasetRequest command, it should return Busy after receiving another one. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::Busy); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::Busy); EXPECT_FALSE(sTestDelegate.mInterfaceEnabled); EXPECT_EQ(sTestDelegate.mSetActiveDatasetCommandSequenceNum, static_cast(1)); // Activate the dataset. @@ -303,30 +314,29 @@ TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestCommandHandle) Protocols::InteractionModel::ClusterStatusCode(Protocols::InteractionModel::Status::Success)); sTestFailsafeContext.DisarmFailSafe(); // The Dataset should be updated. - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kActive, dataset), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kActive, dataset), Status::Success); EXPECT_TRUE(dataset.AsByteSpan().data_equal(ByteSpan(validDataset))); EXPECT_TRUE(sTestDelegate.mInterfaceEnabled); activeDatasetTimestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); // activeDatasetTimestamp should have value. - EXPECT_TRUE(activeDatasetTimestamp.HasValue()); + EXPECT_TRUE(activeDatasetTimestamp.has_value()); EXPECT_EQ(sTestFailsafeContext.ArmFailSafe(kTestAccessingFabricIndex, System::Clock::Seconds16(1)), CHIP_NO_ERROR); // When ActiveDatasetTimestamp is not null, the set active dataset request should return InvalidInState. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::InvalidInState); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::InvalidInState); sTestFailsafeContext.DisarmFailSafe(); // Test SetPendingDatasetRequest command - Commands::SetPendingDatasetRequest::DecodableType req2; sTestDelegate.mPanChangeSupported = false; req2.pendingDataset = ByteSpan(validDataset); // SetPendingDatasetRequest is supported when PANChange feature is enabled. - EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(req2), Status::UnsupportedCommand); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::UnsupportedCommand); sTestDelegate.mPanChangeSupported = true; req2.pendingDataset = ByteSpan(invalidDataset); // SetPendingDatasetRequest should return InvalidCommand when dataset is invalid. - EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(req2), Status::InvalidCommand); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::InvalidCommand); req2.pendingDataset = ByteSpan(validDataset); // Success SetPendingDatasetRequest - EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(req2), Status::Success); - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kPending, dataset), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kPending, dataset), Status::Success); EXPECT_TRUE(dataset.AsByteSpan().data_equal(ByteSpan(validDataset))); } diff --git a/src/app/tests/suites/certification/Test_TC_PWRTL_1_1.yaml b/src/app/tests/suites/certification/Test_TC_PWRTL_1_1.yaml index da8b19b8bf7e1b..ad4fb3aaa031ca 100644 --- a/src/app/tests/suites/certification/Test_TC_PWRTL_1_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_PWRTL_1_1.yaml @@ -88,7 +88,7 @@ tests: hasMasksSet: [0x4, 0x8] - label: "Step 4a: TH reads AttributeList from DUT" - PICS: "!PICS_SF_SET && !PICS_SF_DYPF" + PICS: "!PWRTL.S.F02 && !PWRTL.S.F03" command: "readAttribute" attribute: "AttributeList" response: @@ -99,7 +99,7 @@ tests: - label: "Step 4b: TH reads feature dependent attribute(AvailableEndpoints) AttributeList from DUT" - PICS: "PICS_SF_SET && !PICS_SF_DYPF" + PICS: "PWRTL.S.F02 && !PWRTL.S.F03" command: "readAttribute" attribute: "AttributeList" response: @@ -110,7 +110,7 @@ tests: - label: "Step 4c: TH reads feature dependent attribute(ActiveEndpoints) AttributeList from DUT" - PICS: "PICS_SF_SET && PICS_SF_DYPF" + PICS: "PWRTL.S.F02 && PWRTL.S.F03" command: "readAttribute" attribute: "AttributeList" response: diff --git a/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml b/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml index dd58b93543dfc7..7ee1e74fb0123d 100644 --- a/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml @@ -74,10 +74,10 @@ tests: response: constraints: type: int64u - # TODO: Attribute missing from cluster XML - # - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" - # command: readAttribute - # attribute: PendingDatasetTimestamp - # response: - # constraints: - # type: int64u + + - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: PendingDatasetTimestamp + response: + constraints: + type: int64u diff --git a/src/app/zap-templates/zcl/data-model/chip/service-area-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/service-area-cluster.xml index e7b190456a2ef8..e2f0199209d4eb 100644 --- a/src/app/zap-templates/zcl/data-model/chip/service-area-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/service-area-cluster.xml @@ -18,35 +18,31 @@ limitations under the License. Data types + + + + + + - - - - + + - - + + - - - + + + - - - - - - - - @@ -55,6 +51,14 @@ limitations under the License. + + + + + + + + @@ -69,27 +73,32 @@ limitations under the License. + General Service Area - The Service Area cluster provides an interface for controlling the locations where a device should operate, and for querying the current location. + The Service Area cluster provides an interface for controlling the areas where a device should operate, and for querying the current area being serviced. 0x0150 SERVICE_AREA_CLUSTER + - + + + + - + SupportedAreas - SupportedMaps + SupportedMaps SelectedAreas CurrentArea EstimatedEndTime @@ -107,22 +116,23 @@ limitations under the License. This command is sent by the device on receipt of the SelectAreas command. - - + + This command is used to skip an area where the device operates. + This command is sent by the device on receipt of the SkipArea command. - - + + diff --git a/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml index d7565e0262c9da..d05ce70c34aca3 100644 --- a/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml @@ -43,6 +43,8 @@ limitations under the License. ActiveDatasetTimestamp + PendingDatasetTimestamp + Command to request the active operational dataset of the Thread network to which the border router is connected. This command must be sent over a valid CASE session diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index 4de0247c17d86a..12733488366d1b 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -320,6 +320,7 @@ "ThreadVersion", "InterfaceEnabled", "ActiveDatasetTimestamp", + "PendingDatasetTimestamp", "FeatureMap" ], "Thread Network Diagnostics": [ diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index 3a17b76c4723b9..f4979fbe49c72d 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -318,6 +318,7 @@ "ThreadVersion", "InterfaceEnabled", "ActiveDatasetTimestamp", + "PendingDatasetTimestamp", "FeatureMap" ], "Thread Network Diagnostics": [ diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp index 7f13a4323d2f5c..09a22e03e0bcd2 100644 --- a/src/controller/CommissionerDiscoveryController.cpp +++ b/src/controller/CommissionerDiscoveryController.cpp @@ -589,12 +589,36 @@ void CommissionerDiscoveryController::Cancel() return; } UDCClientState * client = mUdcServer->GetUDCClients().FindUDCClientState(mCurrentInstance); - if (client == nullptr || client->GetUDCClientProcessingState() != UDCClientProcessingState::kPromptingUser) + + if (client == nullptr) + { + ChipLogError(AppServer, "UX Cancel: client not found"); + return; + } + + auto state = client->GetUDCClientProcessingState(); + + bool isCancelableState = + (state == UDCClientProcessingState::kPromptingUser || state == UDCClientProcessingState::kObtainingOnboardingPayload || + state == UDCClientProcessingState::kWaitingForCommissionerPasscodeReady); + + if (!isCancelableState) { - ChipLogError(AppServer, "UX Cancel: invalid state for cancel"); + ChipLogError(AppServer, "UX Cancel: invalid state for cancel, state: %hhu", static_cast(state)); return; } + client->SetUDCClientProcessingState(UDCClientProcessingState::kUserDeclined); + + if (state == UDCClientProcessingState::kObtainingOnboardingPayload || + state == UDCClientProcessingState::kWaitingForCommissionerPasscodeReady) + { + ChipLogDetail(AppServer, "UX Cancel: user cancelled entering PIN code, sending CDC"); + CommissionerDeclaration cd; + cd.SetCancelPasscode(true); + mUdcServer->SendCDCMessage(cd, Transport::PeerAddress::UDP(client->GetPeerAddress().GetIPAddress(), client->GetCdPort())); + } + mPendingConsent = false; ResetState(); } diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index 810f82819c1a19..158ba6d35781f4 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -6446,9 +6446,9 @@ deprecated cluster BarrierControl = 259 { command BarrierControlStop(): DefaultSuccess = 1; } -/** The Service Area cluster provides an interface for controlling the locations where a device should operate, and for querying the current location. */ +/** The Service Area cluster provides an interface for controlling the areas where a device should operate, and for querying the current area being serviced. */ provisional cluster ServiceArea = 336 { - revision 1; // NOTE: Default/not specifically set + revision 1; enum OperationalStatusEnum : enum8 { kPending = 0; @@ -6469,28 +6469,33 @@ provisional cluster ServiceArea = 336 { kSuccess = 0; kInvalidAreaList = 1; kInvalidInMode = 2; + kInvalidSkippedArea = 3; } bitmap Feature : bitmap32 { - kListOrder = 0x1; - kSelectWhileRunning = 0x2; + kSelectWhileRunning = 0x1; + kProgressReporting = 0x2; + kMaps = 0x4; + } + + struct LandmarkInfoStruct { + LandmarkTag landmarkTag = 0; + nullable RelativePositionTag positionTag = 1; } struct AreaInfoStruct { nullable LocationDescriptorStruct locationInfo = 0; - nullable LandmarkTag landmarkTag = 1; - nullable PositionTag positionTag = 2; - nullable FloorSurfaceTag surfaceTag = 3; + nullable LandmarkInfoStruct landmarkInfo = 1; } struct AreaStruct { int32u areaID = 0; - nullable int8u mapID = 1; + nullable int32u mapID = 1; AreaInfoStruct areaDesc = 2; } struct MapStruct { - int8u mapID = 0; + int32u mapID = 0; char_string<64> name = 1; } @@ -6502,7 +6507,7 @@ provisional cluster ServiceArea = 336 { } readonly attribute AreaStruct supportedAreas[] = 0; - readonly attribute MapStruct supportedMaps[] = 1; + readonly attribute optional MapStruct supportedMaps[] = 1; readonly attribute int32u selectedAreas[] = 2; readonly attribute optional nullable int32u currentArea = 3; readonly attribute optional nullable epoch_s estimatedEndTime = 4; @@ -6520,18 +6525,22 @@ provisional cluster ServiceArea = 336 { response struct SelectAreasResponse = 1 { SelectAreasStatus status = 0; - optional char_string<256> statusText = 1; + char_string<256> statusText = 1; + } + + request struct SkipAreaRequest { + int32u skippedArea = 0; } response struct SkipAreaResponse = 3 { SkipAreaStatus status = 0; - optional char_string<256> statusText = 1; + char_string<256> statusText = 1; } /** Command used to select a set of device areas, where the device is to operate. */ command SelectAreas(SelectAreasRequest): SelectAreasResponse = 0; /** This command is used to skip an area where the device operates. */ - command SkipArea(): SkipAreaResponse = 2; + command SkipArea(SkipAreaRequest): SkipAreaResponse = 2; } /** An interface for configuring and controlling pumps. */ @@ -8243,6 +8252,7 @@ provisional cluster ThreadBorderRouterManagement = 1106 { provisional readonly attribute int16u threadVersion = 2; provisional readonly attribute boolean interfaceEnabled = 3; provisional readonly attribute nullable int64u activeDatasetTimestamp = 4; + provisional readonly attribute nullable int64u pendingDatasetTimestamp = 5; readonly attribute command_id generatedCommandList[] = 65528; readonly attribute command_id acceptedCommandList[] = 65529; readonly attribute event_id eventList[] = 65530; diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java index 10863976c661a8..5944e05356d9c5 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java @@ -39092,7 +39092,7 @@ public void onResponse(StructType invokeStructValue) { final long statusFieldID = 0L; Integer status = null; final long statusTextFieldID = 1L; - Optional statusText = Optional.empty(); + String statusText = null; for (StructElement element: invokeStructValue.value()) { if (element.contextTagNum() == statusFieldID) { if (element.value(BaseTLVType.class).type() == TLVType.UInt) { @@ -39102,7 +39102,7 @@ public void onResponse(StructType invokeStructValue) { } else if (element.contextTagNum() == statusTextFieldID) { if (element.value(BaseTLVType.class).type() == TLVType.String) { StringType castingValue = element.value(StringType.class); - statusText = Optional.of(castingValue.value(String.class)); + statusText = castingValue.value(String.class); } } } @@ -39110,14 +39110,18 @@ public void onResponse(StructType invokeStructValue) { }}, commandId, commandArgs, timedInvokeTimeoutMs); } - public void skipArea(SkipAreaResponseCallback callback) { - skipArea(callback, 0); + public void skipArea(SkipAreaResponseCallback callback, Long skippedArea) { + skipArea(callback, skippedArea, 0); } - public void skipArea(SkipAreaResponseCallback callback, int timedInvokeTimeoutMs) { + public void skipArea(SkipAreaResponseCallback callback, Long skippedArea, int timedInvokeTimeoutMs) { final long commandId = 2L; ArrayList elements = new ArrayList<>(); + final long skippedAreaFieldID = 0L; + BaseTLVType skippedAreatlvValue = new UIntType(skippedArea); + elements.add(new StructElement(skippedAreaFieldID, skippedAreatlvValue)); + StructType commandArgs = new StructType(elements); invoke(new InvokeCallbackImpl(callback) { @Override @@ -39125,7 +39129,7 @@ public void onResponse(StructType invokeStructValue) { final long statusFieldID = 0L; Integer status = null; final long statusTextFieldID = 1L; - Optional statusText = Optional.empty(); + String statusText = null; for (StructElement element: invokeStructValue.value()) { if (element.contextTagNum() == statusFieldID) { if (element.value(BaseTLVType.class).type() == TLVType.UInt) { @@ -39135,7 +39139,7 @@ public void onResponse(StructType invokeStructValue) { } else if (element.contextTagNum() == statusTextFieldID) { if (element.value(BaseTLVType.class).type() == TLVType.String) { StringType castingValue = element.value(StringType.class); - statusText = Optional.of(castingValue.value(String.class)); + statusText = castingValue.value(String.class); } } } @@ -39144,11 +39148,11 @@ public void onResponse(StructType invokeStructValue) { } public interface SelectAreasResponseCallback extends BaseClusterCallback { - void onSuccess(Integer status, Optional statusText); + void onSuccess(Integer status, String statusText); } public interface SkipAreaResponseCallback extends BaseClusterCallback { - void onSuccess(Integer status, Optional statusText); + void onSuccess(Integer status, String statusText); } public interface SupportedAreasAttributeCallback extends BaseAttributeCallback { @@ -54619,6 +54623,7 @@ public static class ThreadBorderRouterManagementCluster extends BaseChipCluster private static final long THREAD_VERSION_ATTRIBUTE_ID = 2L; private static final long INTERFACE_ENABLED_ATTRIBUTE_ID = 3L; private static final long ACTIVE_DATASET_TIMESTAMP_ATTRIBUTE_ID = 4L; + private static final long PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID = 5L; private static final long GENERATED_COMMAND_LIST_ATTRIBUTE_ID = 65528L; private static final long ACCEPTED_COMMAND_LIST_ATTRIBUTE_ID = 65529L; private static final long EVENT_LIST_ATTRIBUTE_ID = 65530L; @@ -54740,6 +54745,10 @@ public interface ActiveDatasetTimestampAttributeCallback extends BaseAttributeCa void onSuccess(@Nullable Long value); } + public interface PendingDatasetTimestampAttributeCallback extends BaseAttributeCallback { + void onSuccess(@Nullable Long value); + } + public interface GeneratedCommandListAttributeCallback extends BaseAttributeCallback { void onSuccess(List value); } @@ -54886,6 +54895,32 @@ public void onSuccess(byte[] tlv) { }, ACTIVE_DATASET_TIMESTAMP_ATTRIBUTE_ID, minInterval, maxInterval); } + public void readPendingDatasetTimestampAttribute( + PendingDatasetTimestampAttributeCallback callback) { + ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID); + + readAttribute(new ReportCallbackImpl(callback, path) { + @Override + public void onSuccess(byte[] tlv) { + @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv); + callback.onSuccess(value); + } + }, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID, true); + } + + public void subscribePendingDatasetTimestampAttribute( + PendingDatasetTimestampAttributeCallback callback, int minInterval, int maxInterval) { + ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID); + + subscribeAttribute(new ReportCallbackImpl(callback, path) { + @Override + public void onSuccess(byte[] tlv) { + @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv); + callback.onSuccess(value); + } + }, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID, minInterval, maxInterval); + } + public void readGeneratedCommandListAttribute( GeneratedCommandListAttributeCallback callback) { ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, GENERATED_COMMAND_LIST_ATTRIBUTE_ID); diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java b/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java index 9a410a6a8c8d59..4e07b6d86182d9 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java @@ -9171,53 +9171,36 @@ public String toString() { return output.toString(); } } -public static class ServiceAreaClusterAreaInfoStruct { - public @Nullable ChipStructs.ServiceAreaClusterLocationDescriptorStruct locationInfo; - public @Nullable Integer landmarkTag; +public static class ServiceAreaClusterLandmarkInfoStruct { + public Integer landmarkTag; public @Nullable Integer positionTag; - public @Nullable Integer surfaceTag; - private static final long LOCATION_INFO_ID = 0L; - private static final long LANDMARK_TAG_ID = 1L; - private static final long POSITION_TAG_ID = 2L; - private static final long SURFACE_TAG_ID = 3L; + private static final long LANDMARK_TAG_ID = 0L; + private static final long POSITION_TAG_ID = 1L; - public ServiceAreaClusterAreaInfoStruct( - @Nullable ChipStructs.ServiceAreaClusterLocationDescriptorStruct locationInfo, - @Nullable Integer landmarkTag, - @Nullable Integer positionTag, - @Nullable Integer surfaceTag + public ServiceAreaClusterLandmarkInfoStruct( + Integer landmarkTag, + @Nullable Integer positionTag ) { - this.locationInfo = locationInfo; this.landmarkTag = landmarkTag; this.positionTag = positionTag; - this.surfaceTag = surfaceTag; } public StructType encodeTlv() { ArrayList values = new ArrayList<>(); - values.add(new StructElement(LOCATION_INFO_ID, locationInfo != null ? locationInfo.encodeTlv() : new NullType())); - values.add(new StructElement(LANDMARK_TAG_ID, landmarkTag != null ? new UIntType(landmarkTag) : new NullType())); + values.add(new StructElement(LANDMARK_TAG_ID, new UIntType(landmarkTag))); values.add(new StructElement(POSITION_TAG_ID, positionTag != null ? new UIntType(positionTag) : new NullType())); - values.add(new StructElement(SURFACE_TAG_ID, surfaceTag != null ? new UIntType(surfaceTag) : new NullType())); return new StructType(values); } - public static ServiceAreaClusterAreaInfoStruct decodeTlv(BaseTLVType tlvValue) { + public static ServiceAreaClusterLandmarkInfoStruct decodeTlv(BaseTLVType tlvValue) { if (tlvValue == null || tlvValue.type() != TLVType.Struct) { return null; } - @Nullable ChipStructs.ServiceAreaClusterLocationDescriptorStruct locationInfo = null; - @Nullable Integer landmarkTag = null; + Integer landmarkTag = null; @Nullable Integer positionTag = null; - @Nullable Integer surfaceTag = null; for (StructElement element: ((StructType)tlvValue).value()) { - if (element.contextTagNum() == LOCATION_INFO_ID) { - if (element.value(BaseTLVType.class).type() == TLVType.Struct) { - StructType castingValue = element.value(StructType.class); - locationInfo = ChipStructs.ServiceAreaClusterLocationDescriptorStruct.decodeTlv(castingValue); - } - } else if (element.contextTagNum() == LANDMARK_TAG_ID) { + if (element.contextTagNum() == LANDMARK_TAG_ID) { if (element.value(BaseTLVType.class).type() == TLVType.UInt) { UIntType castingValue = element.value(UIntType.class); landmarkTag = castingValue.value(Integer.class); @@ -9227,36 +9210,84 @@ public static ServiceAreaClusterAreaInfoStruct decodeTlv(BaseTLVType tlvValue) { UIntType castingValue = element.value(UIntType.class); positionTag = castingValue.value(Integer.class); } - } else if (element.contextTagNum() == SURFACE_TAG_ID) { - if (element.value(BaseTLVType.class).type() == TLVType.UInt) { - UIntType castingValue = element.value(UIntType.class); - surfaceTag = castingValue.value(Integer.class); - } } } - return new ServiceAreaClusterAreaInfoStruct( - locationInfo, + return new ServiceAreaClusterLandmarkInfoStruct( landmarkTag, - positionTag, - surfaceTag + positionTag ); } @Override public String toString() { StringBuilder output = new StringBuilder(); - output.append("ServiceAreaClusterAreaInfoStruct {\n"); - output.append("\tlocationInfo: "); - output.append(locationInfo); - output.append("\n"); + output.append("ServiceAreaClusterLandmarkInfoStruct {\n"); output.append("\tlandmarkTag: "); output.append(landmarkTag); output.append("\n"); output.append("\tpositionTag: "); output.append(positionTag); output.append("\n"); - output.append("\tsurfaceTag: "); - output.append(surfaceTag); + output.append("}\n"); + return output.toString(); + } +} +public static class ServiceAreaClusterAreaInfoStruct { + public @Nullable ChipStructs.ServiceAreaClusterLocationDescriptorStruct locationInfo; + public @Nullable ChipStructs.ServiceAreaClusterLandmarkInfoStruct landmarkInfo; + private static final long LOCATION_INFO_ID = 0L; + private static final long LANDMARK_INFO_ID = 1L; + + public ServiceAreaClusterAreaInfoStruct( + @Nullable ChipStructs.ServiceAreaClusterLocationDescriptorStruct locationInfo, + @Nullable ChipStructs.ServiceAreaClusterLandmarkInfoStruct landmarkInfo + ) { + this.locationInfo = locationInfo; + this.landmarkInfo = landmarkInfo; + } + + public StructType encodeTlv() { + ArrayList values = new ArrayList<>(); + values.add(new StructElement(LOCATION_INFO_ID, locationInfo != null ? locationInfo.encodeTlv() : new NullType())); + values.add(new StructElement(LANDMARK_INFO_ID, landmarkInfo != null ? landmarkInfo.encodeTlv() : new NullType())); + + return new StructType(values); + } + + public static ServiceAreaClusterAreaInfoStruct decodeTlv(BaseTLVType tlvValue) { + if (tlvValue == null || tlvValue.type() != TLVType.Struct) { + return null; + } + @Nullable ChipStructs.ServiceAreaClusterLocationDescriptorStruct locationInfo = null; + @Nullable ChipStructs.ServiceAreaClusterLandmarkInfoStruct landmarkInfo = null; + for (StructElement element: ((StructType)tlvValue).value()) { + if (element.contextTagNum() == LOCATION_INFO_ID) { + if (element.value(BaseTLVType.class).type() == TLVType.Struct) { + StructType castingValue = element.value(StructType.class); + locationInfo = ChipStructs.ServiceAreaClusterLocationDescriptorStruct.decodeTlv(castingValue); + } + } else if (element.contextTagNum() == LANDMARK_INFO_ID) { + if (element.value(BaseTLVType.class).type() == TLVType.Struct) { + StructType castingValue = element.value(StructType.class); + landmarkInfo = ChipStructs.ServiceAreaClusterLandmarkInfoStruct.decodeTlv(castingValue); + } + } + } + return new ServiceAreaClusterAreaInfoStruct( + locationInfo, + landmarkInfo + ); + } + + @Override + public String toString() { + StringBuilder output = new StringBuilder(); + output.append("ServiceAreaClusterAreaInfoStruct {\n"); + output.append("\tlocationInfo: "); + output.append(locationInfo); + output.append("\n"); + output.append("\tlandmarkInfo: "); + output.append(landmarkInfo); output.append("\n"); output.append("}\n"); return output.toString(); @@ -9264,7 +9295,7 @@ public String toString() { } public static class ServiceAreaClusterAreaStruct { public Long areaID; - public @Nullable Integer mapID; + public @Nullable Long mapID; public ChipStructs.ServiceAreaClusterAreaInfoStruct areaDesc; private static final long AREA_I_D_ID = 0L; private static final long MAP_I_D_ID = 1L; @@ -9272,7 +9303,7 @@ public static class ServiceAreaClusterAreaStruct { public ServiceAreaClusterAreaStruct( Long areaID, - @Nullable Integer mapID, + @Nullable Long mapID, ChipStructs.ServiceAreaClusterAreaInfoStruct areaDesc ) { this.areaID = areaID; @@ -9294,7 +9325,7 @@ public static ServiceAreaClusterAreaStruct decodeTlv(BaseTLVType tlvValue) { return null; } Long areaID = null; - @Nullable Integer mapID = null; + @Nullable Long mapID = null; ChipStructs.ServiceAreaClusterAreaInfoStruct areaDesc = null; for (StructElement element: ((StructType)tlvValue).value()) { if (element.contextTagNum() == AREA_I_D_ID) { @@ -9305,7 +9336,7 @@ public static ServiceAreaClusterAreaStruct decodeTlv(BaseTLVType tlvValue) { } else if (element.contextTagNum() == MAP_I_D_ID) { if (element.value(BaseTLVType.class).type() == TLVType.UInt) { UIntType castingValue = element.value(UIntType.class); - mapID = castingValue.value(Integer.class); + mapID = castingValue.value(Long.class); } } else if (element.contextTagNum() == AREA_DESC_ID) { if (element.value(BaseTLVType.class).type() == TLVType.Struct) { @@ -9339,13 +9370,13 @@ public String toString() { } } public static class ServiceAreaClusterMapStruct { - public Integer mapID; + public Long mapID; public String name; private static final long MAP_I_D_ID = 0L; private static final long NAME_ID = 1L; public ServiceAreaClusterMapStruct( - Integer mapID, + Long mapID, String name ) { this.mapID = mapID; @@ -9364,13 +9395,13 @@ public static ServiceAreaClusterMapStruct decodeTlv(BaseTLVType tlvValue) { if (tlvValue == null || tlvValue.type() != TLVType.Struct) { return null; } - Integer mapID = null; + Long mapID = null; String name = null; for (StructElement element: ((StructType)tlvValue).value()) { if (element.contextTagNum() == MAP_I_D_ID) { if (element.value(BaseTLVType.class).type() == TLVType.UInt) { UIntType castingValue = element.value(UIntType.class); - mapID = castingValue.value(Integer.class); + mapID = castingValue.value(Long.class); } } else if (element.contextTagNum() == NAME_ID) { if (element.value(BaseTLVType.class).type() == TLVType.String) { diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java index 9a5fd5658ea2dd..c6677b6aa93f47 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java @@ -11722,6 +11722,23 @@ public static SelectAreasCommandField value(int id) throws NoSuchFieldError { } throw new NoSuchFieldError(); } + }public enum SkipAreaCommandField {SkippedArea(0),; + private final int id; + SkipAreaCommandField(int id) { + this.id = id; + } + + public int getID() { + return id; + } + public static SkipAreaCommandField value(int id) throws NoSuchFieldError { + for (SkipAreaCommandField field : SkipAreaCommandField.values()) { + if (field.getID() == id) { + return field; + } + } + throw new NoSuchFieldError(); + } }@Override public String getAttributeName(long id) throws NoSuchFieldError { return Attribute.value(id).toString(); @@ -14867,6 +14884,7 @@ public enum Attribute { ThreadVersion(2L), InterfaceEnabled(3L), ActiveDatasetTimestamp(4L), + PendingDatasetTimestamp(5L), GeneratedCommandList(65528L), AcceptedCommandList(65529L), EventList(65530L), diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java index d2703a0146de5a..986d59f3635af1 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java @@ -13125,12 +13125,12 @@ public void setCallbackDelegate(ClusterCommandCallback callback) { } @Override - public void onSuccess(Integer status, Optional statusText) { + public void onSuccess(Integer status, String statusText) { Map responseValues = new LinkedHashMap<>(); CommandResponseInfo statusResponseValue = new CommandResponseInfo("status", "Integer"); responseValues.put(statusResponseValue, status); - CommandResponseInfo statusTextResponseValue = new CommandResponseInfo("statusText", "Optional"); + CommandResponseInfo statusTextResponseValue = new CommandResponseInfo("statusText", "String"); responseValues.put(statusTextResponseValue, statusText); callback.onSuccess(responseValues); } @@ -13149,12 +13149,12 @@ public void setCallbackDelegate(ClusterCommandCallback callback) { } @Override - public void onSuccess(Integer status, Optional statusText) { + public void onSuccess(Integer status, String statusText) { Map responseValues = new LinkedHashMap<>(); CommandResponseInfo statusResponseValue = new CommandResponseInfo("status", "Integer"); responseValues.put(statusResponseValue, status); - CommandResponseInfo statusTextResponseValue = new CommandResponseInfo("statusText", "Optional"); + CommandResponseInfo statusTextResponseValue = new CommandResponseInfo("statusText", "String"); responseValues.put(statusTextResponseValue, statusText); callback.onSuccess(responseValues); } @@ -18094,6 +18094,27 @@ public void onError(Exception ex) { } } + public static class DelegatedThreadBorderRouterManagementClusterPendingDatasetTimestampAttributeCallback implements ChipClusters.ThreadBorderRouterManagementCluster.PendingDatasetTimestampAttributeCallback, DelegatedClusterCallback { + private ClusterCommandCallback callback; + @Override + public void setCallbackDelegate(ClusterCommandCallback callback) { + this.callback = callback; + } + + @Override + public void onSuccess(@Nullable Long value) { + Map responseValues = new LinkedHashMap<>(); + CommandResponseInfo commandResponseInfo = new CommandResponseInfo("value", "Long"); + responseValues.put(commandResponseInfo, value); + callback.onSuccess(responseValues); + } + + @Override + public void onError(Exception ex) { + callback.onFailure(ex); + } + } + public static class DelegatedThreadBorderRouterManagementClusterGeneratedCommandListAttributeCallback implements ChipClusters.ThreadBorderRouterManagementCluster.GeneratedCommandListAttributeCallback, DelegatedClusterCallback { private ClusterCommandCallback callback; @Override @@ -26964,10 +26985,16 @@ public Map> getCommandMap() { serviceAreaClusterInteractionInfoMap.put("selectAreas", serviceAreaselectAreasInteractionInfo); Map serviceAreaskipAreaCommandParams = new LinkedHashMap(); + + CommandParameterInfo serviceAreaskipAreaskippedAreaCommandParameterInfo = new CommandParameterInfo("skippedArea", Long.class, Long.class); + serviceAreaskipAreaCommandParams.put("skippedArea",serviceAreaskipAreaskippedAreaCommandParameterInfo); InteractionInfo serviceAreaskipAreaInteractionInfo = new InteractionInfo( (cluster, callback, commandArguments) -> { ((ChipClusters.ServiceAreaCluster) cluster) .skipArea((ChipClusters.ServiceAreaCluster.SkipAreaResponseCallback) callback + , (Long) + commandArguments.get("skippedArea") + ); }, () -> new DelegatedServiceAreaClusterSkipAreaResponseCallback(), diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java index 7aa5a710554223..c96de698113955 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java @@ -17129,6 +17129,17 @@ private static Map readThreadBorderRouterManagementInte readThreadBorderRouterManagementActiveDatasetTimestampCommandParams ); result.put("readActiveDatasetTimestampAttribute", readThreadBorderRouterManagementActiveDatasetTimestampAttributeInteractionInfo); + Map readThreadBorderRouterManagementPendingDatasetTimestampCommandParams = new LinkedHashMap(); + InteractionInfo readThreadBorderRouterManagementPendingDatasetTimestampAttributeInteractionInfo = new InteractionInfo( + (cluster, callback, commandArguments) -> { + ((ChipClusters.ThreadBorderRouterManagementCluster) cluster).readPendingDatasetTimestampAttribute( + (ChipClusters.ThreadBorderRouterManagementCluster.PendingDatasetTimestampAttributeCallback) callback + ); + }, + () -> new ClusterInfoMapping.DelegatedThreadBorderRouterManagementClusterPendingDatasetTimestampAttributeCallback(), + readThreadBorderRouterManagementPendingDatasetTimestampCommandParams + ); + result.put("readPendingDatasetTimestampAttribute", readThreadBorderRouterManagementPendingDatasetTimestampAttributeInteractionInfo); Map readThreadBorderRouterManagementGeneratedCommandListCommandParams = new LinkedHashMap(); InteractionInfo readThreadBorderRouterManagementGeneratedCommandListAttributeInteractionInfo = new InteractionInfo( (cluster, callback, commandArguments) -> { diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni b/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni index 1b0bd261bb6c31..ca6223a2c1f96e 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni +++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni @@ -125,6 +125,7 @@ structs_sources = [ "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ScenesManagementClusterSceneInfoStruct.kt", "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt", "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaStruct.kt", + "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt", "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterLocationDescriptorStruct.kt", "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterMapStruct.kt", "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterProgressStruct.kt", diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt index e11d56110d37cf..f0c591f1345946 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt +++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt @@ -24,16 +24,12 @@ import matter.tlv.TlvWriter class ServiceAreaClusterAreaInfoStruct( val locationInfo: ServiceAreaClusterLocationDescriptorStruct?, - val landmarkTag: UInt?, - val positionTag: UInt?, - val surfaceTag: UInt?, + val landmarkInfo: ServiceAreaClusterLandmarkInfoStruct?, ) { override fun toString(): String = buildString { append("ServiceAreaClusterAreaInfoStruct {\n") append("\tlocationInfo : $locationInfo\n") - append("\tlandmarkTag : $landmarkTag\n") - append("\tpositionTag : $positionTag\n") - append("\tsurfaceTag : $surfaceTag\n") + append("\tlandmarkInfo : $landmarkInfo\n") append("}\n") } @@ -45,20 +41,10 @@ class ServiceAreaClusterAreaInfoStruct( } else { putNull(ContextSpecificTag(TAG_LOCATION_INFO)) } - if (landmarkTag != null) { - put(ContextSpecificTag(TAG_LANDMARK_TAG), landmarkTag) + if (landmarkInfo != null) { + landmarkInfo.toTlv(ContextSpecificTag(TAG_LANDMARK_INFO), this) } else { - putNull(ContextSpecificTag(TAG_LANDMARK_TAG)) - } - if (positionTag != null) { - put(ContextSpecificTag(TAG_POSITION_TAG), positionTag) - } else { - putNull(ContextSpecificTag(TAG_POSITION_TAG)) - } - if (surfaceTag != null) { - put(ContextSpecificTag(TAG_SURFACE_TAG), surfaceTag) - } else { - putNull(ContextSpecificTag(TAG_SURFACE_TAG)) + putNull(ContextSpecificTag(TAG_LANDMARK_INFO)) } endStructure() } @@ -66,9 +52,7 @@ class ServiceAreaClusterAreaInfoStruct( companion object { private const val TAG_LOCATION_INFO = 0 - private const val TAG_LANDMARK_TAG = 1 - private const val TAG_POSITION_TAG = 2 - private const val TAG_SURFACE_TAG = 3 + private const val TAG_LANDMARK_INFO = 1 fun fromTlv(tlvTag: Tag, tlvReader: TlvReader): ServiceAreaClusterAreaInfoStruct { tlvReader.enterStructure(tlvTag) @@ -82,31 +66,20 @@ class ServiceAreaClusterAreaInfoStruct( tlvReader.getNull(ContextSpecificTag(TAG_LOCATION_INFO)) null } - val landmarkTag = - if (!tlvReader.isNull()) { - tlvReader.getUInt(ContextSpecificTag(TAG_LANDMARK_TAG)) - } else { - tlvReader.getNull(ContextSpecificTag(TAG_LANDMARK_TAG)) - null - } - val positionTag = + val landmarkInfo = if (!tlvReader.isNull()) { - tlvReader.getUInt(ContextSpecificTag(TAG_POSITION_TAG)) - } else { - tlvReader.getNull(ContextSpecificTag(TAG_POSITION_TAG)) - null - } - val surfaceTag = - if (!tlvReader.isNull()) { - tlvReader.getUInt(ContextSpecificTag(TAG_SURFACE_TAG)) + ServiceAreaClusterLandmarkInfoStruct.fromTlv( + ContextSpecificTag(TAG_LANDMARK_INFO), + tlvReader, + ) } else { - tlvReader.getNull(ContextSpecificTag(TAG_SURFACE_TAG)) + tlvReader.getNull(ContextSpecificTag(TAG_LANDMARK_INFO)) null } tlvReader.exitContainer() - return ServiceAreaClusterAreaInfoStruct(locationInfo, landmarkTag, positionTag, surfaceTag) + return ServiceAreaClusterAreaInfoStruct(locationInfo, landmarkInfo) } } } diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaStruct.kt b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaStruct.kt index 74be0531c61dc5..3f8365d49b3b78 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaStruct.kt +++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterAreaStruct.kt @@ -24,7 +24,7 @@ import matter.tlv.TlvWriter class ServiceAreaClusterAreaStruct( val areaID: ULong, - val mapID: UInt?, + val mapID: ULong?, val areaDesc: ServiceAreaClusterAreaInfoStruct, ) { override fun toString(): String = buildString { @@ -59,7 +59,7 @@ class ServiceAreaClusterAreaStruct( val areaID = tlvReader.getULong(ContextSpecificTag(TAG_AREA_I_D)) val mapID = if (!tlvReader.isNull()) { - tlvReader.getUInt(ContextSpecificTag(TAG_MAP_I_D)) + tlvReader.getULong(ContextSpecificTag(TAG_MAP_I_D)) } else { tlvReader.getNull(ContextSpecificTag(TAG_MAP_I_D)) null diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt new file mode 100644 index 00000000000000..04970798a28433 --- /dev/null +++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2023 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. + */ +package chip.devicecontroller.cluster.structs + +import chip.devicecontroller.cluster.* +import matter.tlv.ContextSpecificTag +import matter.tlv.Tag +import matter.tlv.TlvReader +import matter.tlv.TlvWriter + +class ServiceAreaClusterLandmarkInfoStruct(val landmarkTag: UInt, val positionTag: UInt?) { + override fun toString(): String = buildString { + append("ServiceAreaClusterLandmarkInfoStruct {\n") + append("\tlandmarkTag : $landmarkTag\n") + append("\tpositionTag : $positionTag\n") + append("}\n") + } + + fun toTlv(tlvTag: Tag, tlvWriter: TlvWriter) { + tlvWriter.apply { + startStructure(tlvTag) + put(ContextSpecificTag(TAG_LANDMARK_TAG), landmarkTag) + if (positionTag != null) { + put(ContextSpecificTag(TAG_POSITION_TAG), positionTag) + } else { + putNull(ContextSpecificTag(TAG_POSITION_TAG)) + } + endStructure() + } + } + + companion object { + private const val TAG_LANDMARK_TAG = 0 + private const val TAG_POSITION_TAG = 1 + + fun fromTlv(tlvTag: Tag, tlvReader: TlvReader): ServiceAreaClusterLandmarkInfoStruct { + tlvReader.enterStructure(tlvTag) + val landmarkTag = tlvReader.getUInt(ContextSpecificTag(TAG_LANDMARK_TAG)) + val positionTag = + if (!tlvReader.isNull()) { + tlvReader.getUInt(ContextSpecificTag(TAG_POSITION_TAG)) + } else { + tlvReader.getNull(ContextSpecificTag(TAG_POSITION_TAG)) + null + } + + tlvReader.exitContainer() + + return ServiceAreaClusterLandmarkInfoStruct(landmarkTag, positionTag) + } + } +} diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterMapStruct.kt b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterMapStruct.kt index 377ab13f0e9130..f00877354d1506 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterMapStruct.kt +++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ServiceAreaClusterMapStruct.kt @@ -22,7 +22,7 @@ import matter.tlv.Tag import matter.tlv.TlvReader import matter.tlv.TlvWriter -class ServiceAreaClusterMapStruct(val mapID: UInt, val name: String) { +class ServiceAreaClusterMapStruct(val mapID: ULong, val name: String) { override fun toString(): String = buildString { append("ServiceAreaClusterMapStruct {\n") append("\tmapID : $mapID\n") @@ -45,7 +45,7 @@ class ServiceAreaClusterMapStruct(val mapID: UInt, val name: String) { fun fromTlv(tlvTag: Tag, tlvReader: TlvReader): ServiceAreaClusterMapStruct { tlvReader.enterStructure(tlvTag) - val mapID = tlvReader.getUInt(ContextSpecificTag(TAG_MAP_I_D)) + val mapID = tlvReader.getULong(ContextSpecificTag(TAG_MAP_I_D)) val name = tlvReader.getString(ContextSpecificTag(TAG_NAME)) tlvReader.exitContainer() diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/ServiceAreaCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/ServiceAreaCluster.kt index 08376006435cf1..b526c0ea4c38b9 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/clusters/ServiceAreaCluster.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/ServiceAreaCluster.kt @@ -40,9 +40,9 @@ import matter.tlv.TlvReader import matter.tlv.TlvWriter class ServiceAreaCluster(private val controller: MatterController, private val endpointId: UShort) { - class SelectAreasResponse(val status: UByte, val statusText: String?) + class SelectAreasResponse(val status: UByte, val statusText: String) - class SkipAreaResponse(val status: UByte, val statusText: String?) + class SkipAreaResponse(val status: UByte, val statusText: String) class SupportedAreasAttribute(val value: List) @@ -55,10 +55,10 @@ class ServiceAreaCluster(private val controller: MatterController, private val e object SubscriptionEstablished : SupportedAreasAttributeSubscriptionState() } - class SupportedMapsAttribute(val value: List) + class SupportedMapsAttribute(val value: List?) sealed class SupportedMapsAttributeSubscriptionState { - data class Success(val value: List) : + data class Success(val value: List?) : SupportedMapsAttributeSubscriptionState() data class Error(val exception: Exception) : SupportedMapsAttributeSubscriptionState() @@ -190,17 +190,7 @@ class ServiceAreaCluster(private val controller: MatterController, private val e } if (tag == ContextSpecificTag(TAG_STATUS_TEXT)) { - statusText_decoded = - if (tlvReader.isNull()) { - tlvReader.getNull(tag) - null - } else { - if (tlvReader.isNextTag(tag)) { - tlvReader.getString(tag) - } else { - null - } - } + statusText_decoded = tlvReader.getString(tag) } else { tlvReader.skipElement() } @@ -210,16 +200,23 @@ class ServiceAreaCluster(private val controller: MatterController, private val e throw IllegalStateException("status not found in TLV") } + if (statusText_decoded == null) { + throw IllegalStateException("statusText not found in TLV") + } + tlvReader.exitContainer() return SelectAreasResponse(status_decoded, statusText_decoded) } - suspend fun skipArea(timedInvokeTimeout: Duration? = null): SkipAreaResponse { + suspend fun skipArea(skippedArea: UInt, timedInvokeTimeout: Duration? = null): SkipAreaResponse { val commandId: UInt = 2u val tlvWriter = TlvWriter() tlvWriter.startStructure(AnonymousTag) + + val TAG_SKIPPED_AREA_REQ: Int = 0 + tlvWriter.put(ContextSpecificTag(TAG_SKIPPED_AREA_REQ), skippedArea) tlvWriter.endStructure() val request: InvokeRequest = @@ -248,17 +245,7 @@ class ServiceAreaCluster(private val controller: MatterController, private val e } if (tag == ContextSpecificTag(TAG_STATUS_TEXT)) { - statusText_decoded = - if (tlvReader.isNull()) { - tlvReader.getNull(tag) - null - } else { - if (tlvReader.isNextTag(tag)) { - tlvReader.getString(tag) - } else { - null - } - } + statusText_decoded = tlvReader.getString(tag) } else { tlvReader.skipElement() } @@ -268,6 +255,10 @@ class ServiceAreaCluster(private val controller: MatterController, private val e throw IllegalStateException("status not found in TLV") } + if (statusText_decoded == null) { + throw IllegalStateException("statusText not found in TLV") + } + tlvReader.exitContainer() return SkipAreaResponse(status_decoded, statusText_decoded) @@ -396,13 +387,17 @@ class ServiceAreaCluster(private val controller: MatterController, private val e // Decode the TLV data into the appropriate type val tlvReader = TlvReader(attributeData.data) - val decodedValue: List = - buildList { - tlvReader.enterArray(AnonymousTag) - while (!tlvReader.isEndOfContainer()) { - add(ServiceAreaClusterMapStruct.fromTlv(AnonymousTag, tlvReader)) + val decodedValue: List? = + if (tlvReader.isNextTag(AnonymousTag)) { + buildList { + tlvReader.enterArray(AnonymousTag) + while (!tlvReader.isEndOfContainer()) { + add(ServiceAreaClusterMapStruct.fromTlv(AnonymousTag, tlvReader)) + } + tlvReader.exitContainer() } - tlvReader.exitContainer() + } else { + null } return SupportedMapsAttribute(decodedValue) @@ -447,16 +442,20 @@ class ServiceAreaCluster(private val controller: MatterController, private val e // Decode the TLV data into the appropriate type val tlvReader = TlvReader(attributeData.data) - val decodedValue: List = - buildList { - tlvReader.enterArray(AnonymousTag) - while (!tlvReader.isEndOfContainer()) { - add(ServiceAreaClusterMapStruct.fromTlv(AnonymousTag, tlvReader)) + val decodedValue: List? = + if (tlvReader.isNextTag(AnonymousTag)) { + buildList { + tlvReader.enterArray(AnonymousTag) + while (!tlvReader.isEndOfContainer()) { + add(ServiceAreaClusterMapStruct.fromTlv(AnonymousTag, tlvReader)) + } + tlvReader.exitContainer() } - tlvReader.exitContainer() + } else { + null } - emit(SupportedMapsAttributeSubscriptionState.Success(decodedValue)) + decodedValue?.let { emit(SupportedMapsAttributeSubscriptionState.Success(it)) } } SubscriptionState.SubscriptionEstablished -> { emit(SupportedMapsAttributeSubscriptionState.SubscriptionEstablished) diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt index b212ff7904d2fc..efbb1b5baff7e4 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt @@ -58,6 +58,17 @@ class ThreadBorderRouterManagementCluster( object SubscriptionEstablished : ActiveDatasetTimestampAttributeSubscriptionState() } + class PendingDatasetTimestampAttribute(val value: ULong?) + + sealed class PendingDatasetTimestampAttributeSubscriptionState { + data class Success(val value: ULong?) : PendingDatasetTimestampAttributeSubscriptionState() + + data class Error(val exception: Exception) : + PendingDatasetTimestampAttributeSubscriptionState() + + object SubscriptionEstablished : PendingDatasetTimestampAttributeSubscriptionState() + } + class GeneratedCommandListAttribute(val value: List) sealed class GeneratedCommandListAttributeSubscriptionState { @@ -655,6 +666,101 @@ class ThreadBorderRouterManagementCluster( } } + suspend fun readPendingDatasetTimestampAttribute(): PendingDatasetTimestampAttribute { + val ATTRIBUTE_ID: UInt = 5u + + val attributePath = + AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) + + val readRequest = ReadRequest(eventPaths = emptyList(), attributePaths = listOf(attributePath)) + + val response = controller.read(readRequest) + + if (response.successes.isEmpty()) { + logger.log(Level.WARNING, "Read command failed") + throw IllegalStateException("Read command failed with failures: ${response.failures}") + } + + logger.log(Level.FINE, "Read command succeeded") + + val attributeData = + response.successes.filterIsInstance().firstOrNull { + it.path.attributeId == ATTRIBUTE_ID + } + + requireNotNull(attributeData) { "Pendingdatasettimestamp attribute not found in response" } + + // Decode the TLV data into the appropriate type + val tlvReader = TlvReader(attributeData.data) + val decodedValue: ULong? = + if (!tlvReader.isNull()) { + tlvReader.getULong(AnonymousTag) + } else { + tlvReader.getNull(AnonymousTag) + null + } + + return PendingDatasetTimestampAttribute(decodedValue) + } + + suspend fun subscribePendingDatasetTimestampAttribute( + minInterval: Int, + maxInterval: Int, + ): Flow { + val ATTRIBUTE_ID: UInt = 5u + val attributePaths = + listOf( + AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) + ) + + val subscribeRequest: SubscribeRequest = + SubscribeRequest( + eventPaths = emptyList(), + attributePaths = attributePaths, + minInterval = Duration.ofSeconds(minInterval.toLong()), + maxInterval = Duration.ofSeconds(maxInterval.toLong()), + ) + + return controller.subscribe(subscribeRequest).transform { subscriptionState -> + when (subscriptionState) { + is SubscriptionState.SubscriptionErrorNotification -> { + emit( + PendingDatasetTimestampAttributeSubscriptionState.Error( + Exception( + "Subscription terminated with error code: ${subscriptionState.terminationCause}" + ) + ) + ) + } + is SubscriptionState.NodeStateUpdate -> { + val attributeData = + subscriptionState.updateState.successes + .filterIsInstance() + .firstOrNull { it.path.attributeId == ATTRIBUTE_ID } + + requireNotNull(attributeData) { + "Pendingdatasettimestamp attribute not found in Node State update" + } + + // Decode the TLV data into the appropriate type + val tlvReader = TlvReader(attributeData.data) + val decodedValue: ULong? = + if (!tlvReader.isNull()) { + tlvReader.getULong(AnonymousTag) + } else { + tlvReader.getNull(AnonymousTag) + null + } + + decodedValue?.let { emit(PendingDatasetTimestampAttributeSubscriptionState.Success(it)) } + } + SubscriptionState.SubscriptionEstablished -> { + emit(PendingDatasetTimestampAttributeSubscriptionState.SubscriptionEstablished) + } + } + } + } + suspend fun readGeneratedCommandListAttribute(): GeneratedCommandListAttribute { val ATTRIBUTE_ID: UInt = 65528u diff --git a/src/controller/java/generated/java/matter/controller/cluster/files.gni b/src/controller/java/generated/java/matter/controller/cluster/files.gni index e848af60d78a98..844c6692f5b9ad 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/files.gni +++ b/src/controller/java/generated/java/matter/controller/cluster/files.gni @@ -125,6 +125,7 @@ matter_structs_sources = [ "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ScenesManagementClusterSceneInfoStruct.kt", "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt", "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaStruct.kt", + "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt", "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterLocationDescriptorStruct.kt", "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterMapStruct.kt", "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterProgressStruct.kt", diff --git a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt index a440d0b2ec4116..1c71c6bcd1ff68 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaInfoStruct.kt @@ -24,16 +24,12 @@ import matter.tlv.TlvWriter class ServiceAreaClusterAreaInfoStruct( val locationInfo: ServiceAreaClusterLocationDescriptorStruct?, - val landmarkTag: UByte?, - val positionTag: UByte?, - val surfaceTag: UByte?, + val landmarkInfo: ServiceAreaClusterLandmarkInfoStruct?, ) { override fun toString(): String = buildString { append("ServiceAreaClusterAreaInfoStruct {\n") append("\tlocationInfo : $locationInfo\n") - append("\tlandmarkTag : $landmarkTag\n") - append("\tpositionTag : $positionTag\n") - append("\tsurfaceTag : $surfaceTag\n") + append("\tlandmarkInfo : $landmarkInfo\n") append("}\n") } @@ -45,20 +41,10 @@ class ServiceAreaClusterAreaInfoStruct( } else { putNull(ContextSpecificTag(TAG_LOCATION_INFO)) } - if (landmarkTag != null) { - put(ContextSpecificTag(TAG_LANDMARK_TAG), landmarkTag) + if (landmarkInfo != null) { + landmarkInfo.toTlv(ContextSpecificTag(TAG_LANDMARK_INFO), this) } else { - putNull(ContextSpecificTag(TAG_LANDMARK_TAG)) - } - if (positionTag != null) { - put(ContextSpecificTag(TAG_POSITION_TAG), positionTag) - } else { - putNull(ContextSpecificTag(TAG_POSITION_TAG)) - } - if (surfaceTag != null) { - put(ContextSpecificTag(TAG_SURFACE_TAG), surfaceTag) - } else { - putNull(ContextSpecificTag(TAG_SURFACE_TAG)) + putNull(ContextSpecificTag(TAG_LANDMARK_INFO)) } endStructure() } @@ -66,9 +52,7 @@ class ServiceAreaClusterAreaInfoStruct( companion object { private const val TAG_LOCATION_INFO = 0 - private const val TAG_LANDMARK_TAG = 1 - private const val TAG_POSITION_TAG = 2 - private const val TAG_SURFACE_TAG = 3 + private const val TAG_LANDMARK_INFO = 1 fun fromTlv(tlvTag: Tag, tlvReader: TlvReader): ServiceAreaClusterAreaInfoStruct { tlvReader.enterStructure(tlvTag) @@ -82,31 +66,20 @@ class ServiceAreaClusterAreaInfoStruct( tlvReader.getNull(ContextSpecificTag(TAG_LOCATION_INFO)) null } - val landmarkTag = - if (!tlvReader.isNull()) { - tlvReader.getUByte(ContextSpecificTag(TAG_LANDMARK_TAG)) - } else { - tlvReader.getNull(ContextSpecificTag(TAG_LANDMARK_TAG)) - null - } - val positionTag = + val landmarkInfo = if (!tlvReader.isNull()) { - tlvReader.getUByte(ContextSpecificTag(TAG_POSITION_TAG)) - } else { - tlvReader.getNull(ContextSpecificTag(TAG_POSITION_TAG)) - null - } - val surfaceTag = - if (!tlvReader.isNull()) { - tlvReader.getUByte(ContextSpecificTag(TAG_SURFACE_TAG)) + ServiceAreaClusterLandmarkInfoStruct.fromTlv( + ContextSpecificTag(TAG_LANDMARK_INFO), + tlvReader, + ) } else { - tlvReader.getNull(ContextSpecificTag(TAG_SURFACE_TAG)) + tlvReader.getNull(ContextSpecificTag(TAG_LANDMARK_INFO)) null } tlvReader.exitContainer() - return ServiceAreaClusterAreaInfoStruct(locationInfo, landmarkTag, positionTag, surfaceTag) + return ServiceAreaClusterAreaInfoStruct(locationInfo, landmarkInfo) } } } diff --git a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaStruct.kt b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaStruct.kt index 0ffdb8c9416d01..adfe228d332c39 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaStruct.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterAreaStruct.kt @@ -24,7 +24,7 @@ import matter.tlv.TlvWriter class ServiceAreaClusterAreaStruct( val areaID: UInt, - val mapID: UByte?, + val mapID: UInt?, val areaDesc: ServiceAreaClusterAreaInfoStruct, ) { override fun toString(): String = buildString { @@ -59,7 +59,7 @@ class ServiceAreaClusterAreaStruct( val areaID = tlvReader.getUInt(ContextSpecificTag(TAG_AREA_I_D)) val mapID = if (!tlvReader.isNull()) { - tlvReader.getUByte(ContextSpecificTag(TAG_MAP_I_D)) + tlvReader.getUInt(ContextSpecificTag(TAG_MAP_I_D)) } else { tlvReader.getNull(ContextSpecificTag(TAG_MAP_I_D)) null diff --git a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt new file mode 100644 index 00000000000000..119667c339d88c --- /dev/null +++ b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterLandmarkInfoStruct.kt @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2023 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. + */ +package matter.controller.cluster.structs + +import matter.controller.cluster.* +import matter.tlv.ContextSpecificTag +import matter.tlv.Tag +import matter.tlv.TlvReader +import matter.tlv.TlvWriter + +class ServiceAreaClusterLandmarkInfoStruct(val landmarkTag: UByte, val positionTag: UByte?) { + override fun toString(): String = buildString { + append("ServiceAreaClusterLandmarkInfoStruct {\n") + append("\tlandmarkTag : $landmarkTag\n") + append("\tpositionTag : $positionTag\n") + append("}\n") + } + + fun toTlv(tlvTag: Tag, tlvWriter: TlvWriter) { + tlvWriter.apply { + startStructure(tlvTag) + put(ContextSpecificTag(TAG_LANDMARK_TAG), landmarkTag) + if (positionTag != null) { + put(ContextSpecificTag(TAG_POSITION_TAG), positionTag) + } else { + putNull(ContextSpecificTag(TAG_POSITION_TAG)) + } + endStructure() + } + } + + companion object { + private const val TAG_LANDMARK_TAG = 0 + private const val TAG_POSITION_TAG = 1 + + fun fromTlv(tlvTag: Tag, tlvReader: TlvReader): ServiceAreaClusterLandmarkInfoStruct { + tlvReader.enterStructure(tlvTag) + val landmarkTag = tlvReader.getUByte(ContextSpecificTag(TAG_LANDMARK_TAG)) + val positionTag = + if (!tlvReader.isNull()) { + tlvReader.getUByte(ContextSpecificTag(TAG_POSITION_TAG)) + } else { + tlvReader.getNull(ContextSpecificTag(TAG_POSITION_TAG)) + null + } + + tlvReader.exitContainer() + + return ServiceAreaClusterLandmarkInfoStruct(landmarkTag, positionTag) + } + } +} diff --git a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterMapStruct.kt b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterMapStruct.kt index 3109c9bcb82b76..528888247d791b 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterMapStruct.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/structs/ServiceAreaClusterMapStruct.kt @@ -22,7 +22,7 @@ import matter.tlv.Tag import matter.tlv.TlvReader import matter.tlv.TlvWriter -class ServiceAreaClusterMapStruct(val mapID: UByte, val name: String) { +class ServiceAreaClusterMapStruct(val mapID: UInt, val name: String) { override fun toString(): String = buildString { append("ServiceAreaClusterMapStruct {\n") append("\tmapID : $mapID\n") @@ -45,7 +45,7 @@ class ServiceAreaClusterMapStruct(val mapID: UByte, val name: String) { fun fromTlv(tlvTag: Tag, tlvReader: TlvReader): ServiceAreaClusterMapStruct { tlvReader.enterStructure(tlvTag) - val mapID = tlvReader.getUByte(ContextSpecificTag(TAG_MAP_I_D)) + val mapID = tlvReader.getUInt(ContextSpecificTag(TAG_MAP_I_D)) val name = tlvReader.getString(ContextSpecificTag(TAG_NAME)) tlvReader.exitContainer() diff --git a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp index ebcec56cd2b41a..82a4a6fe42e669 100644 --- a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp +++ b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp @@ -28640,12 +28640,12 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR } else { - std::string newElement_0_mapIDClassName = "java/lang/Integer"; - std::string newElement_0_mapIDCtorSignature = "(I)V"; - jint jninewElement_0_mapID = static_cast(entry_0.mapID.Value()); - chip::JniReferences::GetInstance().CreateBoxedObject(newElement_0_mapIDClassName.c_str(), - newElement_0_mapIDCtorSignature.c_str(), - jninewElement_0_mapID, newElement_0_mapID); + std::string newElement_0_mapIDClassName = "java/lang/Long"; + std::string newElement_0_mapIDCtorSignature = "(J)V"; + jlong jninewElement_0_mapID = static_cast(entry_0.mapID.Value()); + chip::JniReferences::GetInstance().CreateBoxedObject(newElement_0_mapIDClassName.c_str(), + newElement_0_mapIDCtorSignature.c_str(), + jninewElement_0_mapID, newElement_0_mapID); } jobject newElement_0_areaDesc; jobject newElement_0_areaDesc_locationInfo; @@ -28716,47 +28716,62 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR newElement_0_areaDesc_locationInfo_locationName, newElement_0_areaDesc_locationInfo_floorNumber, newElement_0_areaDesc_locationInfo_areaType); } - jobject newElement_0_areaDesc_landmarkTag; - if (entry_0.areaDesc.landmarkTag.IsNull()) + jobject newElement_0_areaDesc_landmarkInfo; + if (entry_0.areaDesc.landmarkInfo.IsNull()) { - newElement_0_areaDesc_landmarkTag = nullptr; + newElement_0_areaDesc_landmarkInfo = nullptr; } else { - std::string newElement_0_areaDesc_landmarkTagClassName = "java/lang/Integer"; - std::string newElement_0_areaDesc_landmarkTagCtorSignature = "(I)V"; - jint jninewElement_0_areaDesc_landmarkTag = static_cast(entry_0.areaDesc.landmarkTag.Value()); + jobject newElement_0_areaDesc_landmarkInfo_landmarkTag; + std::string newElement_0_areaDesc_landmarkInfo_landmarkTagClassName = "java/lang/Integer"; + std::string newElement_0_areaDesc_landmarkInfo_landmarkTagCtorSignature = "(I)V"; + jint jninewElement_0_areaDesc_landmarkInfo_landmarkTag = + static_cast(entry_0.areaDesc.landmarkInfo.Value().landmarkTag); chip::JniReferences::GetInstance().CreateBoxedObject( - newElement_0_areaDesc_landmarkTagClassName.c_str(), newElement_0_areaDesc_landmarkTagCtorSignature.c_str(), - jninewElement_0_areaDesc_landmarkTag, newElement_0_areaDesc_landmarkTag); - } - jobject newElement_0_areaDesc_positionTag; - if (entry_0.areaDesc.positionTag.IsNull()) - { - newElement_0_areaDesc_positionTag = nullptr; - } - else - { - std::string newElement_0_areaDesc_positionTagClassName = "java/lang/Integer"; - std::string newElement_0_areaDesc_positionTagCtorSignature = "(I)V"; - jint jninewElement_0_areaDesc_positionTag = static_cast(entry_0.areaDesc.positionTag.Value()); - chip::JniReferences::GetInstance().CreateBoxedObject( - newElement_0_areaDesc_positionTagClassName.c_str(), newElement_0_areaDesc_positionTagCtorSignature.c_str(), - jninewElement_0_areaDesc_positionTag, newElement_0_areaDesc_positionTag); - } - jobject newElement_0_areaDesc_surfaceTag; - if (entry_0.areaDesc.surfaceTag.IsNull()) - { - newElement_0_areaDesc_surfaceTag = nullptr; - } - else - { - std::string newElement_0_areaDesc_surfaceTagClassName = "java/lang/Integer"; - std::string newElement_0_areaDesc_surfaceTagCtorSignature = "(I)V"; - jint jninewElement_0_areaDesc_surfaceTag = static_cast(entry_0.areaDesc.surfaceTag.Value()); - chip::JniReferences::GetInstance().CreateBoxedObject( - newElement_0_areaDesc_surfaceTagClassName.c_str(), newElement_0_areaDesc_surfaceTagCtorSignature.c_str(), - jninewElement_0_areaDesc_surfaceTag, newElement_0_areaDesc_surfaceTag); + newElement_0_areaDesc_landmarkInfo_landmarkTagClassName.c_str(), + newElement_0_areaDesc_landmarkInfo_landmarkTagCtorSignature.c_str(), + jninewElement_0_areaDesc_landmarkInfo_landmarkTag, newElement_0_areaDesc_landmarkInfo_landmarkTag); + jobject newElement_0_areaDesc_landmarkInfo_positionTag; + if (entry_0.areaDesc.landmarkInfo.Value().positionTag.IsNull()) + { + newElement_0_areaDesc_landmarkInfo_positionTag = nullptr; + } + else + { + std::string newElement_0_areaDesc_landmarkInfo_positionTagClassName = "java/lang/Integer"; + std::string newElement_0_areaDesc_landmarkInfo_positionTagCtorSignature = "(I)V"; + jint jninewElement_0_areaDesc_landmarkInfo_positionTag = + static_cast(entry_0.areaDesc.landmarkInfo.Value().positionTag.Value()); + chip::JniReferences::GetInstance().CreateBoxedObject( + newElement_0_areaDesc_landmarkInfo_positionTagClassName.c_str(), + newElement_0_areaDesc_landmarkInfo_positionTagCtorSignature.c_str(), + jninewElement_0_areaDesc_landmarkInfo_positionTag, newElement_0_areaDesc_landmarkInfo_positionTag); + } + + jclass landmarkInfoStructStructClass_4; + err = chip::JniReferences::GetInstance().GetLocalClassRef( + env, "chip/devicecontroller/ChipStructs$ServiceAreaClusterLandmarkInfoStruct", + landmarkInfoStructStructClass_4); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Could not find class ChipStructs$ServiceAreaClusterLandmarkInfoStruct"); + return nullptr; + } + + jmethodID landmarkInfoStructStructCtor_4; + err = chip::JniReferences::GetInstance().FindMethod(env, landmarkInfoStructStructClass_4, "", + "(Ljava/lang/Integer;Ljava/lang/Integer;)V", + &landmarkInfoStructStructCtor_4); + if (err != CHIP_NO_ERROR || landmarkInfoStructStructCtor_4 == nullptr) + { + ChipLogError(Zcl, "Could not find ChipStructs$ServiceAreaClusterLandmarkInfoStruct constructor"); + return nullptr; + } + + newElement_0_areaDesc_landmarkInfo = env->NewObject( + landmarkInfoStructStructClass_4, landmarkInfoStructStructCtor_4, + newElement_0_areaDesc_landmarkInfo_landmarkTag, newElement_0_areaDesc_landmarkInfo_positionTag); } jclass areaInfoStructStructClass_2; @@ -28771,8 +28786,8 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR jmethodID areaInfoStructStructCtor_2; err = chip::JniReferences::GetInstance().FindMethod( env, areaInfoStructStructClass_2, "", - "(Lchip/devicecontroller/ChipStructs$ServiceAreaClusterLocationDescriptorStruct;Ljava/lang/Integer;Ljava/lang/" - "Integer;Ljava/lang/Integer;)V", + "(Lchip/devicecontroller/ChipStructs$ServiceAreaClusterLocationDescriptorStruct;Lchip/devicecontroller/" + "ChipStructs$ServiceAreaClusterLandmarkInfoStruct;)V", &areaInfoStructStructCtor_2); if (err != CHIP_NO_ERROR || areaInfoStructStructCtor_2 == nullptr) { @@ -28781,8 +28796,7 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR } newElement_0_areaDesc = env->NewObject(areaInfoStructStructClass_2, areaInfoStructStructCtor_2, - newElement_0_areaDesc_locationInfo, newElement_0_areaDesc_landmarkTag, - newElement_0_areaDesc_positionTag, newElement_0_areaDesc_surfaceTag); + newElement_0_areaDesc_locationInfo, newElement_0_areaDesc_landmarkInfo); jclass areaStructStructClass_1; err = chip::JniReferences::GetInstance().GetLocalClassRef( @@ -28796,7 +28810,7 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR jmethodID areaStructStructCtor_1; err = chip::JniReferences::GetInstance().FindMethod( env, areaStructStructClass_1, "", - "(Ljava/lang/Long;Ljava/lang/Integer;Lchip/devicecontroller/ChipStructs$ServiceAreaClusterAreaInfoStruct;)V", + "(Ljava/lang/Long;Ljava/lang/Long;Lchip/devicecontroller/ChipStructs$ServiceAreaClusterAreaInfoStruct;)V", &areaStructStructCtor_1); if (err != CHIP_NO_ERROR || areaStructStructCtor_1 == nullptr) { @@ -28827,12 +28841,12 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR auto & entry_0 = iter_value_0.GetValue(); jobject newElement_0; jobject newElement_0_mapID; - std::string newElement_0_mapIDClassName = "java/lang/Integer"; - std::string newElement_0_mapIDCtorSignature = "(I)V"; - jint jninewElement_0_mapID = static_cast(entry_0.mapID); - chip::JniReferences::GetInstance().CreateBoxedObject(newElement_0_mapIDClassName.c_str(), - newElement_0_mapIDCtorSignature.c_str(), - jninewElement_0_mapID, newElement_0_mapID); + std::string newElement_0_mapIDClassName = "java/lang/Long"; + std::string newElement_0_mapIDCtorSignature = "(J)V"; + jlong jninewElement_0_mapID = static_cast(entry_0.mapID); + chip::JniReferences::GetInstance().CreateBoxedObject(newElement_0_mapIDClassName.c_str(), + newElement_0_mapIDCtorSignature.c_str(), + jninewElement_0_mapID, newElement_0_mapID); jobject newElement_0_name; LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(entry_0.name, newElement_0_name)); @@ -28847,7 +28861,7 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR jmethodID mapStructStructCtor_1; err = chip::JniReferences::GetInstance().FindMethod( - env, mapStructStructClass_1, "", "(Ljava/lang/Integer;Ljava/lang/String;)V", &mapStructStructCtor_1); + env, mapStructStructClass_1, "", "(Ljava/lang/Long;Ljava/lang/String;)V", &mapStructStructCtor_1); if (err != CHIP_NO_ERROR || mapStructStructCtor_1 == nullptr) { ChipLogError(Zcl, "Could not find ChipStructs$ServiceAreaClusterMapStruct constructor"); @@ -38890,6 +38904,29 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR } return value; } + case Attributes::PendingDatasetTimestamp::Id: { + using TypeInfo = Attributes::PendingDatasetTimestamp::TypeInfo; + TypeInfo::DecodableType cppValue; + *aError = app::DataModel::Decode(aReader, cppValue); + if (*aError != CHIP_NO_ERROR) + { + return nullptr; + } + jobject value; + if (cppValue.IsNull()) + { + value = nullptr; + } + else + { + std::string valueClassName = "java/lang/Long"; + std::string valueCtorSignature = "(J)V"; + jlong jnivalue = static_cast(cppValue.Value()); + chip::JniReferences::GetInstance().CreateBoxedObject(valueClassName.c_str(), valueCtorSignature.c_str(), + jnivalue, value); + } + return value; + } case Attributes::GeneratedCommandList::Id: { using TypeInfo = Attributes::GeneratedCommandList::TypeInfo; TypeInfo::DecodableType cppValue; diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py index e8972863a72dca..c573d69dc78e6c 100644 --- a/src/controller/python/chip/clusters/CHIPClusters.py +++ b/src/controller/python/chip/clusters/CHIPClusters.py @@ -8571,6 +8571,7 @@ class ChipClusters: "commandId": 0x00000002, "commandName": "SkipArea", "args": { + "skippedArea": "int", }, }, }, @@ -11970,6 +11971,12 @@ class ChipClusters: "type": "int", "reportable": True, }, + 0x00000005: { + "attributeName": "PendingDatasetTimestamp", + "attributeId": 0x00000005, + "type": "int", + "reportable": True, + }, 0x0000FFF8: { "attributeName": "GeneratedCommandList", "attributeId": 0x0000FFF8, diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index 670c7109a5ccb8..774b76e886af4a 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -31144,7 +31144,7 @@ def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ ClusterObjectFieldDescriptor(Label="supportedAreas", Tag=0x00000000, Type=typing.List[ServiceArea.Structs.AreaStruct]), - ClusterObjectFieldDescriptor(Label="supportedMaps", Tag=0x00000001, Type=typing.List[ServiceArea.Structs.MapStruct]), + ClusterObjectFieldDescriptor(Label="supportedMaps", Tag=0x00000001, Type=typing.Optional[typing.List[ServiceArea.Structs.MapStruct]]), ClusterObjectFieldDescriptor(Label="selectedAreas", Tag=0x00000002, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="currentArea", Tag=0x00000003, Type=typing.Union[None, Nullable, uint]), ClusterObjectFieldDescriptor(Label="estimatedEndTime", Tag=0x00000004, Type=typing.Union[None, Nullable, uint]), @@ -31158,7 +31158,7 @@ def descriptor(cls) -> ClusterObjectDescriptor: ]) supportedAreas: 'typing.List[ServiceArea.Structs.AreaStruct]' = None - supportedMaps: 'typing.List[ServiceArea.Structs.MapStruct]' = None + supportedMaps: 'typing.Optional[typing.List[ServiceArea.Structs.MapStruct]]' = None selectedAreas: 'typing.List[uint]' = None currentArea: 'typing.Union[None, Nullable, uint]' = None estimatedEndTime: 'typing.Union[None, Nullable, uint]' = None @@ -31198,18 +31198,33 @@ class SkipAreaStatus(MatterIntEnum): kSuccess = 0x00 kInvalidAreaList = 0x01 kInvalidInMode = 0x02 + kInvalidSkippedArea = 0x03 # All received enum values that are not listed above will be mapped # to kUnknownEnumValue. This is a helper enum value that should only # be used by code to process how it handles receiving an unknown # enum value. This specific value should never be transmitted. - kUnknownEnumValue = 3, + kUnknownEnumValue = 4, class Bitmaps: class Feature(IntFlag): - kListOrder = 0x1 - kSelectWhileRunning = 0x2 + kSelectWhileRunning = 0x1 + kProgressReporting = 0x2 + kMaps = 0x4 class Structs: + @dataclass + class LandmarkInfoStruct(ClusterObject): + @ChipUtility.classproperty + def descriptor(cls) -> ClusterObjectDescriptor: + return ClusterObjectDescriptor( + Fields=[ + ClusterObjectFieldDescriptor(Label="landmarkTag", Tag=0, Type=Globals.Enums.LandmarkTag), + ClusterObjectFieldDescriptor(Label="positionTag", Tag=1, Type=typing.Union[Nullable, Globals.Enums.RelativePositionTag]), + ]) + + landmarkTag: 'Globals.Enums.LandmarkTag' = 0 + positionTag: 'typing.Union[Nullable, Globals.Enums.RelativePositionTag]' = NullValue + @dataclass class AreaInfoStruct(ClusterObject): @ChipUtility.classproperty @@ -31217,15 +31232,11 @@ def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ ClusterObjectFieldDescriptor(Label="locationInfo", Tag=0, Type=typing.Union[Nullable, Globals.Structs.LocationDescriptorStruct]), - ClusterObjectFieldDescriptor(Label="landmarkTag", Tag=1, Type=typing.Union[Nullable, Globals.Enums.LandmarkTag]), - ClusterObjectFieldDescriptor(Label="positionTag", Tag=2, Type=typing.Union[Nullable, Globals.Enums.PositionTag]), - ClusterObjectFieldDescriptor(Label="surfaceTag", Tag=3, Type=typing.Union[Nullable, Globals.Enums.FloorSurfaceTag]), + ClusterObjectFieldDescriptor(Label="landmarkInfo", Tag=1, Type=typing.Union[Nullable, ServiceArea.Structs.LandmarkInfoStruct]), ]) locationInfo: 'typing.Union[Nullable, Globals.Structs.LocationDescriptorStruct]' = NullValue - landmarkTag: 'typing.Union[Nullable, Globals.Enums.LandmarkTag]' = NullValue - positionTag: 'typing.Union[Nullable, Globals.Enums.PositionTag]' = NullValue - surfaceTag: 'typing.Union[Nullable, Globals.Enums.FloorSurfaceTag]' = NullValue + landmarkInfo: 'typing.Union[Nullable, ServiceArea.Structs.LandmarkInfoStruct]' = NullValue @dataclass class AreaStruct(ClusterObject): @@ -31301,11 +31312,11 @@ def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ ClusterObjectFieldDescriptor(Label="status", Tag=0, Type=ServiceArea.Enums.SelectAreasStatus), - ClusterObjectFieldDescriptor(Label="statusText", Tag=1, Type=typing.Optional[str]), + ClusterObjectFieldDescriptor(Label="statusText", Tag=1, Type=str), ]) status: 'ServiceArea.Enums.SelectAreasStatus' = 0 - statusText: 'typing.Optional[str]' = None + statusText: 'str' = "" @dataclass class SkipArea(ClusterCommand): @@ -31318,8 +31329,11 @@ class SkipArea(ClusterCommand): def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ + ClusterObjectFieldDescriptor(Label="skippedArea", Tag=0, Type=uint), ]) + skippedArea: 'uint' = 0 + @dataclass class SkipAreaResponse(ClusterCommand): cluster_id: typing.ClassVar[int] = 0x00000150 @@ -31332,11 +31346,11 @@ def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ ClusterObjectFieldDescriptor(Label="status", Tag=0, Type=ServiceArea.Enums.SkipAreaStatus), - ClusterObjectFieldDescriptor(Label="statusText", Tag=1, Type=typing.Optional[str]), + ClusterObjectFieldDescriptor(Label="statusText", Tag=1, Type=str), ]) status: 'ServiceArea.Enums.SkipAreaStatus' = 0 - statusText: 'typing.Optional[str]' = None + statusText: 'str' = "" class Attributes: @dataclass @@ -31367,9 +31381,9 @@ def attribute_id(cls) -> int: @ChipUtility.classproperty def attribute_type(cls) -> ClusterObjectFieldDescriptor: - return ClusterObjectFieldDescriptor(Type=typing.List[ServiceArea.Structs.MapStruct]) + return ClusterObjectFieldDescriptor(Type=typing.Optional[typing.List[ServiceArea.Structs.MapStruct]]) - value: 'typing.List[ServiceArea.Structs.MapStruct]' = field(default_factory=lambda: []) + value: 'typing.Optional[typing.List[ServiceArea.Structs.MapStruct]]' = None @dataclass class SelectedAreas(ClusterAttributeDescriptor): @@ -42067,6 +42081,7 @@ def descriptor(cls) -> ClusterObjectDescriptor: ClusterObjectFieldDescriptor(Label="threadVersion", Tag=0x00000002, Type=uint), ClusterObjectFieldDescriptor(Label="interfaceEnabled", Tag=0x00000003, Type=bool), ClusterObjectFieldDescriptor(Label="activeDatasetTimestamp", Tag=0x00000004, Type=typing.Union[Nullable, uint]), + ClusterObjectFieldDescriptor(Label="pendingDatasetTimestamp", Tag=0x00000005, Type=typing.Union[Nullable, uint]), ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="eventList", Tag=0x0000FFFA, Type=typing.List[uint]), @@ -42080,6 +42095,7 @@ def descriptor(cls) -> ClusterObjectDescriptor: threadVersion: 'uint' = None interfaceEnabled: 'bool' = None activeDatasetTimestamp: 'typing.Union[Nullable, uint]' = None + pendingDatasetTimestamp: 'typing.Union[Nullable, uint]' = None generatedCommandList: 'typing.List[uint]' = None acceptedCommandList: 'typing.List[uint]' = None eventList: 'typing.List[uint]' = None @@ -42249,6 +42265,22 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: value: 'typing.Union[Nullable, uint]' = NullValue + @dataclass + class PendingDatasetTimestamp(ClusterAttributeDescriptor): + @ChipUtility.classproperty + def cluster_id(cls) -> int: + return 0x00000452 + + @ChipUtility.classproperty + def attribute_id(cls) -> int: + return 0x00000005 + + @ChipUtility.classproperty + def attribute_type(cls) -> ClusterObjectFieldDescriptor: + return ClusterObjectFieldDescriptor(Type=typing.Union[Nullable, uint]) + + value: 'typing.Union[Nullable, uint]' = NullValue + @dataclass class GeneratedCommandList(ClusterAttributeDescriptor): @ChipUtility.classproperty diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index aa3efc54d578cb..d3a1b0833f11fd 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -164,6 +164,7 @@ typedef NS_ENUM(uint8_t, MTRTransportType) { * * nil is used to represent wildcards. */ +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)) @interface MTRAttributeRequestPath : NSObject @property (nonatomic, readonly, copy, nullable) NSNumber * endpoint MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); @@ -182,6 +183,7 @@ MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)) * * nil is used to represent wildcards. */ +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)) @interface MTREventRequestPath : NSObject @property (nonatomic, readonly, copy, nullable) NSNumber * endpoint MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); @@ -572,6 +574,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * A path indicating a specific cluster on a device (i.e. without any * wildcards). */ +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) @interface MTRClusterPath : NSObject @@ -588,6 +591,7 @@ MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) * A path indicating a specific attribute on a device (i.e. without any * wildcards). */ +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @interface MTRAttributePath : MTRClusterPath @@ -604,6 +608,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * (i.e. without any wildcards). There can be multiple instances of actual * events for a given event path. */ +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @interface MTREventPath : MTRClusterPath @@ -618,6 +623,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * A path indicating a specific command on a device (i.e. without any * wildcards). */ +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @interface MTRCommandPath : MTRClusterPath @@ -628,6 +634,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) commandID:(NSNumber *)commandID MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); @end +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @interface MTRAttributeReport : NSObject @@ -640,7 +647,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * * The attribute is nullable and the value of the attribute is null. * * If value is not nil, the actual type of value will depend on the - * schema-defined (typically defiend in the Matter specification) type of the + * schema-defined (typically defined in the Matter specification) type of the * attribute as follows: * * * list: NSArray of whatever type the list entries are. @@ -697,6 +704,7 @@ typedef NS_ENUM(NSUInteger, MTREventPriority) { MTREventPriorityCritical = 2 } MTR_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5)); +NS_SWIFT_SENDABLE MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @interface MTREventReport : NSObject diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index b8ef8694825e1f..9ce2e3d4026f68 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -142,16 +142,6 @@ - (BOOL)callDelegateSynchronouslyWithBlock:(void (^)(id))bloc #endif @end -NSNumber * MTRClampedNumber(NSNumber * aNumber, NSNumber * min, NSNumber * max) -{ - if ([aNumber compare:min] == NSOrderedAscending) { - return min; - } else if ([aNumber compare:max] == NSOrderedDescending) { - return max; - } - return aNumber; -} - /* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */ @interface NSObject (MatterPrivateForInternalDragonsDoNotFeed) @@ -252,10 +242,6 @@ @implementation MTRDeviceClusterData { NSMutableDictionary * _attributes; } -static NSString * const sDataVersionKey = @"dataVersion"; -static NSString * const sAttributesKey = @"attributes"; -static NSString * const sLastInitialSubscribeLatencyKey = @"lastInitialSubscribeLatency"; - - (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(NSNumber *)attribute { _attributes[attribute] = value; @@ -498,6 +484,15 @@ @implementation MTRDevice { NSMutableSet * _delegates; } +- (instancetype)initForSubclasses +{ + if (self = [super init]) { + // nothing, as superclass of MTRDevice is NSObject + } + + return self; +} + - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller { if (self = [super init]) { @@ -1193,23 +1188,21 @@ - (void)_handleSubscriptionEstablished { os_unfair_lock_lock(&self->_lock); - // We have completed the subscription work - remove from the subscription pool. - [self _clearSubscriptionPoolWork]; - - // reset subscription attempt wait time when subscription succeeds - _lastSubscriptionAttemptWait = 0; - if (HadSubscriptionEstablishedOnce(_internalDeviceState)) { - [self _changeInternalState:MTRInternalDeviceStateLaterSubscriptionEstablished]; - } else { - MATTER_LOG_METRIC_END(kMetricMTRDeviceInitialSubscriptionSetup, CHIP_NO_ERROR); - [self _changeInternalState:MTRInternalDeviceStateInitialSubscriptionEstablished]; + // If subscription had reset since this handler was scheduled, do not execute "established" logic below + if (!HaveSubscriptionEstablishedRightNow(_internalDeviceState)) { + MTR_LOG("%@ _handleSubscriptionEstablished run with internal state %lu - skipping subscription establishment logic", self, static_cast(_internalDeviceState)); + return; } - [self _changeState:MTRDeviceStateReachable]; + // We have completed the subscription work - remove from the subscription pool. + [self _clearSubscriptionPoolWork]; // No need to monitor connectivity after subscription establishment [self _stopConnectivityMonitoring]; + // reset subscription attempt wait time when subscription succeeds + _lastSubscriptionAttemptWait = 0; + auto initialSubscribeStart = _initialSubscribeStart; // We no longer need to track subscribe latency for this device. _initialSubscribeStart = nil; @@ -2476,6 +2469,19 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason }, ^(void) { MTR_LOG("%@ got subscription established", self); + std::lock_guard lock(self->_lock); + + // First synchronously change state + if (HadSubscriptionEstablishedOnce(self->_internalDeviceState)) { + [self _changeInternalState:MTRInternalDeviceStateLaterSubscriptionEstablished]; + } else { + MATTER_LOG_METRIC_END(kMetricMTRDeviceInitialSubscriptionSetup, CHIP_NO_ERROR); + [self _changeInternalState:MTRInternalDeviceStateInitialSubscriptionEstablished]; + } + + [self _changeState:MTRDeviceStateReachable]; + + // Then async work that shouldn't be performed on the matter queue dispatch_async(self.queue, ^{ // OnSubscriptionEstablished [self _handleSubscriptionEstablished]; diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 2e4bb6d4fb0400..6c312d510a93a7 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -31,6 +31,7 @@ #import "MTRDeviceControllerLocalTestStorage.h" #import "MTRDeviceControllerStartupParams.h" #import "MTRDeviceControllerStartupParams_Internal.h" +#import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" #import "MTRError_Internal.h" #import "MTRKeypair.h" @@ -988,7 +989,7 @@ - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(N { os_unfair_lock_assert_owner(&_deviceMapLock); - MTRDevice * deviceToReturn = [[MTRDevice alloc] initWithNodeID:nodeID controller:self]; + MTRDevice * deviceToReturn = [[MTRDevice_Concrete alloc] initWithNodeID:nodeID controller:self]; // If we're not running, don't add the device to our map. That would // create a cycle that nothing would break. Just return the device, // which will be in exactly the state it would be in if it were created diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.h b/src/darwin/Framework/CHIP/MTRDevice_Concrete.h new file mode 100644 index 00000000000000..6466a18c04abf6 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.h @@ -0,0 +1,27 @@ +/** + * + * Copyright (c) 2022-2023 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. + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTRDevice_Concrete : MTRDevice + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm new file mode 100644 index 00000000000000..a90b49d52b3139 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm @@ -0,0 +1,4231 @@ +/** + * + * Copyright (c) 2022-2023 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. + */ + +#import +#import + +#import "MTRAsyncWorkQueue.h" +#import "MTRAttributeSpecifiedCheck.h" +#import "MTRBaseClusters.h" +#import "MTRBaseDevice_Internal.h" +#import "MTRBaseSubscriptionCallback.h" +#import "MTRCluster.h" +#import "MTRClusterConstants.h" +#import "MTRCommandTimedCheck.h" +#import "MTRConversion.h" +#import "MTRDefines_Internal.h" +#import "MTRDeviceConnectivityMonitor.h" +#import "MTRDeviceControllerOverXPC.h" +#import "MTRDeviceController_Internal.h" +#import "MTRDevice_Concrete.h" +#import "MTRDevice_Internal.h" +#import "MTRError_Internal.h" +#import "MTREventTLVValueDecoder_Internal.h" +#import "MTRLogging_Internal.h" +#import "MTRMetricKeys.h" +#import "MTRMetricsCollector.h" +#import "MTRTimeUtils.h" +#import "MTRUnfairLock.h" +#import "MTRUtilities.h" +#import "zap-generated/MTRCommandPayloads_Internal.h" + +#import "lib/core/CHIPError.h" +#import "lib/core/DataModelTypes.h" +#import +#import + +#import +#import +#import +#import +#import +#import + +// allow readwrite access to superclass properties +@interface MTRDevice_Concrete () + +@property (nonatomic, readwrite, copy) NSNumber * nodeID; +@property (nonatomic, readwrite, nullable) MTRDeviceController * deviceController; +@property (nonatomic, readwrite) MTRAsyncWorkQueue * asyncWorkQueue; +@property (nonatomic, readwrite) MTRDeviceState state; +@property (nonatomic, readwrite, nullable) NSDate * estimatedStartTime; +@property (nonatomic, readwrite, nullable, copy) NSNumber * estimatedSubscriptionLatency; + +@end + +typedef void (^MTRDeviceAttributeReportHandler)(NSArray * _Nonnull); + +#define kSecondsToWaitBeforeMarkingUnreachableAfterSettingUpSubscription 10 + +// Disabling pending crashes +#define ENABLE_CONNECTIVITY_MONITORING 0 + +// Consider moving utility classes to their own file +#pragma mark - Utility Classes + +// container of MTRDevice delegate weak reference, its queue, and its interested paths for attribute reports +MTR_DIRECT_MEMBERS +@interface MTRDeviceDelegateInfo_ConcreteCopy : NSObject { +@private + void * _delegatePointerValue; + __weak id _delegate; + dispatch_queue_t _queue; + NSArray * _Nullable _interestedPathsForAttributes; + NSArray * _Nullable _interestedPathsForEvents; +} + +// Array of interested cluster paths, attribute paths, or endpointID, for attribute report filtering. +@property (readonly, nullable) NSArray * interestedPathsForAttributes; + +// Array of interested cluster paths, attribute paths, or endpointID, for event report filtering. +@property (readonly, nullable) NSArray * interestedPathsForEvents; + +// Expose delegate +@property (readonly) id delegate; + +// Pointer value for logging purpose only +@property (readonly) void * delegatePointerValue; + +- (instancetype)initWithDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents; + +// Returns YES if delegate and queue are both non-null, and the block is scheduled to run. +- (BOOL)callDelegateWithBlock:(void (^)(id))block; + +#ifdef DEBUG +// Only used for unit test purposes - normal delegate should not expect or handle being called back synchronously. +- (BOOL)callDelegateSynchronouslyWithBlock:(void (^)(id))block; +#endif +@end + +@implementation MTRDeviceDelegateInfo_ConcreteCopy +- (instancetype)initWithDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents +{ + if (self = [super init]) { + _delegate = delegate; + _delegatePointerValue = (__bridge void *) delegate; + _queue = queue; + _interestedPathsForAttributes = [interestedPathsForAttributes copy]; + _interestedPathsForEvents = [interestedPathsForEvents copy]; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", self, _delegatePointerValue, static_cast(_interestedPathsForAttributes.count), static_cast(_interestedPathsForEvents.count)]; +} + +- (BOOL)callDelegateWithBlock:(void (^)(id))block +{ + id strongDelegate = _delegate; + VerifyOrReturnValue(strongDelegate, NO); + dispatch_async(_queue, ^{ + block(strongDelegate); + }); + return YES; +} + +#ifdef DEBUG +- (BOOL)callDelegateSynchronouslyWithBlock:(void (^)(id))block +{ + id strongDelegate = _delegate; + VerifyOrReturnValue(strongDelegate, NO); + + block(strongDelegate); + + return YES; +} +#endif +@end + +/* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */ + +@interface NSObject (MatterPrivateForInternalDragonsDoNotFeed) +- (void)_deviceInternalStateChanged:(MTRDevice *)device; +@end + +/* END DRAGONS */ + +#pragma mark - SubscriptionCallback class declaration +using namespace chip; +using namespace chip::app; +using namespace chip::Protocols::InteractionModel; +using namespace chip::Tracing::DarwinFramework; + +typedef void (^FirstReportHandler)(void); + +namespace { + +class SubscriptionCallback final : public MTRBaseSubscriptionCallback { +public: + SubscriptionCallback(DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, + ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler resubscriptionCallback, + SubscriptionEstablishedHandler subscriptionEstablishedHandler, OnDoneHandler onDoneHandler, + UnsolicitedMessageFromPublisherHandler unsolicitedMessageFromPublisherHandler, ReportBeginHandler reportBeginHandler, + ReportEndHandler reportEndHandler) + : MTRBaseSubscriptionCallback(attributeReportCallback, eventReportCallback, errorCallback, resubscriptionCallback, + subscriptionEstablishedHandler, onDoneHandler, unsolicitedMessageFromPublisherHandler, reportBeginHandler, + reportEndHandler) + { + } + + // Used to reset Resubscription backoff on events that indicate likely availability of device to come back online + void ResetResubscriptionBackoff() { mResubscriptionNumRetries = 0; } + +private: + void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override; + + void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; + + CHIP_ERROR OnResubscriptionNeeded(chip::app::ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override; + + // Copied from ReadClient and customized for MTRDevice resubscription time reset + uint32_t ComputeTimeTillNextSubscription(); + uint32_t mResubscriptionNumRetries = 0; +}; + +} // anonymous namespace + +#pragma mark - MTRDevice + +// Utility methods for working with MTRInternalDeviceState, located near the +// enum so it's easier to notice that they need to stay in sync. +namespace { +bool HadSubscriptionEstablishedOnce(MTRInternalDeviceState state) +{ + return state >= MTRInternalDeviceStateInitialSubscriptionEstablished; +} + +bool NeedToStartSubscriptionSetup(MTRInternalDeviceState state) +{ + return state <= MTRInternalDeviceStateUnsubscribed; +} + +bool HaveSubscriptionEstablishedRightNow(MTRInternalDeviceState state) +{ + return state == MTRInternalDeviceStateInitialSubscriptionEstablished || state == MTRInternalDeviceStateLaterSubscriptionEstablished; +} +} // anonymous namespace + +typedef NS_ENUM(NSUInteger, MTRDeviceExpectedValueFieldIndex) { + MTRDeviceExpectedValueFieldExpirationTimeIndex = 0, + MTRDeviceExpectedValueFieldValueIndex = 1, + MTRDeviceExpectedValueFieldIDIndex = 2 +}; + +typedef NS_ENUM(NSUInteger, MTRDeviceReadRequestFieldIndex) { + MTRDeviceReadRequestFieldPathIndex = 0, + MTRDeviceReadRequestFieldParamsIndex = 1 +}; + +typedef NS_ENUM(NSUInteger, MTRDeviceWriteRequestFieldIndex) { + MTRDeviceWriteRequestFieldPathIndex = 0, + MTRDeviceWriteRequestFieldValueIndex = 1, + MTRDeviceWriteRequestFieldTimeoutIndex = 2, + MTRDeviceWriteRequestFieldExpectedValueIDIndex = 3, +}; + +typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemBatchingID) { + MTRDeviceWorkItemBatchingReadID = 1, + MTRDeviceWorkItemBatchingWriteID = 2, +}; + +typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemDuplicateTypeID) { + MTRDeviceWorkItemDuplicateReadTypeID = 1, +}; + +// Minimal time to wait since our last resubscribe failure before we will allow +// a read attempt to prod our subscription. +// +// TODO: Figure out a better value for this, but for now don't allow this to +// happen more often than once every 10 minutes. +#define MTRDEVICE_MIN_RESUBSCRIBE_DUE_TO_READ_INTERVAL_SECONDS (10 * 60) + +// Weight of new data in determining subscription latencies. To avoid random +// outliers causing too much noise in the value, treat an existing value (if +// any) as having 2/3 weight and the new value as having 1/3 weight. These +// weights are subject to change, if it's determined that different ones give +// better behavior. +#define MTRDEVICE_SUBSCRIPTION_LATENCY_NEW_VALUE_WEIGHT (1.0 / 3.0) + +@interface MTRDevice_Concrete () +@property (nonatomic, readonly) os_unfair_lock lock; // protects the caches and device state +// protects against concurrent time updates by guarding timeUpdateScheduled flag which manages time updates scheduling, +// and protects device calls to setUTCTime and setDSTOffset +@property (nonatomic, readonly) os_unfair_lock timeSyncLock; +@property (nonatomic) chip::FabricIndex fabricIndex; +@property (nonatomic) NSMutableArray *> * unreportedEvents; +@property (nonatomic) BOOL receivingReport; +@property (nonatomic) BOOL receivingPrimingReport; + +// TODO: instead of all the BOOL properties that are some facet of the state, move to internal state machine that has (at least): +// Actively receiving report +// Actively receiving priming report + +@property (nonatomic) MTRInternalDeviceState internalDeviceState; + +#define MTRDEVICE_SUBSCRIPTION_ATTEMPT_MIN_WAIT_SECONDS (1) +#define MTRDEVICE_SUBSCRIPTION_ATTEMPT_MAX_WAIT_SECONDS (3600) +@property (nonatomic) uint32_t lastSubscriptionAttemptWait; + +/** + * If reattemptingSubscription is true, that means that we have failed to get a + * CASE session for the publisher and are now waiting to try again. In this + * state we never have subscriptionActive true or a non-null currentReadClient. + */ +@property (nonatomic) BOOL reattemptingSubscription; + +// Expected value cache is attributePath => NSArray of [NSDate of expiration time, NSDictionary of value, expected value ID] +// - See MTRDeviceExpectedValueFieldIndex for the definitions of indices into this array. +// See MTRDeviceResponseHandler definition for value dictionary details. +@property (nonatomic) NSMutableDictionary * expectedValueCache; + +// This is a monotonically increasing value used when adding entries to expectedValueCache +// Currently used/updated only in _getAttributesToReportWithNewExpectedValues:expirationTime:expectedValueID: +@property (nonatomic) uint64_t expectedValueNextID; + +@property (nonatomic) BOOL expirationCheckScheduled; + +@property (nonatomic) BOOL timeUpdateScheduled; + +@property (nonatomic) NSDate * estimatedStartTimeFromGeneralDiagnosticsUpTime; + +@property (nonatomic) NSMutableDictionary * temporaryMetaDataCache; + +/** + * If currentReadClient is non-null, that means that we successfully + * called SendAutoResubscribeRequest on the ReadClient and have not yet gotten + * an OnDone for that ReadClient. + */ +@property (nonatomic) ReadClient * currentReadClient; +@property (nonatomic) SubscriptionCallback * currentSubscriptionCallback; // valid when and only when currentReadClient is valid + +@end + +// Declaring selector so compiler won't complain about testing and calling it in _handleReportEnd +#ifdef DEBUG +@protocol MTRDeviceUnitTestDelegate +- (void)unitTestReportEndForDevice:(MTRDevice *)device; +- (BOOL)unitTestShouldSetUpSubscriptionForDevice:(MTRDevice *)device; +- (BOOL)unitTestShouldSkipExpectedValuesForWrite:(MTRDevice *)device; +- (NSNumber *)unitTestMaxIntervalOverrideForSubscription:(MTRDevice *)device; +- (BOOL)unitTestForceAttributeReportsIfMatchingCache:(MTRDevice *)device; +- (BOOL)unitTestPretendThreadEnabled:(MTRDevice *)device; +- (void)unitTestSubscriptionPoolDequeue:(MTRDevice *)device; +- (void)unitTestSubscriptionPoolWorkComplete:(MTRDevice *)device; +- (void)unitTestClusterDataPersisted:(MTRDevice *)device; +- (BOOL)unitTestSuppressTimeBasedReachabilityChanges:(MTRDevice *)device; +@end +#endif + +@implementation MTRDevice_Concrete { +#ifdef DEBUG + NSUInteger _unitTestAttributesReportedSinceLastCheck; +#endif + + // _deviceCachePrimed is true if we have the data that comes from an initial + // subscription priming report (whether it came from storage or from our + // subscription). + BOOL _deviceCachePrimed; + + // _persistedClusterData stores data that we have already persisted (when we have + // cluster data persistence enabled). Nil when we have no persistence enabled. + NSCache * _Nullable _persistedClusterData; + // _clusterDataToPersist stores data that needs to be persisted. If we + // don't have persistence enabled, this is our only data store. Nil if we + // currently have nothing that could need persisting. + NSMutableDictionary * _Nullable _clusterDataToPersist; + // _persistedClusters stores the set of "valid" keys into _persistedClusterData. + // These are keys that could have values in _persistedClusterData even if they don't + // right now (because they have been evicted). + NSMutableSet * _persistedClusters; + + // When we last failed to subscribe to the device (either via + // _setupSubscriptionWithReason or via the auto-resubscribe behavior + // of the ReadClient). Nil if we have had no such failures. + NSDate * _Nullable _lastSubscriptionFailureTime; + MTRDeviceConnectivityMonitor * _connectivityMonitor; + + // This boolean keeps track of any device configuration changes received in an attribute report. + // If this is true when the report ends, we notify the delegate. + BOOL _deviceConfigurationChanged; + + // The completion block is set when the subscription / resubscription work is enqueued, and called / cleared when any of the following happen: + // 1. Subscription establishes + // 2. OnResubscriptionNeeded is called + // 3. Subscription reset (including when getSessionForNode fails) + MTRAsyncWorkCompletionBlock _subscriptionPoolWorkCompletionBlock; + + // Tracking of initial subscribe latency. When _initialSubscribeStart is + // nil, we are not tracking the latency. + NSDate * _Nullable _initialSubscribeStart; + + // Storage behavior configuration and variables to keep track of the logic + // _clusterDataPersistenceFirstScheduledTime is used to track the start time of the delay between + // report and persistence. + // _mostRecentReportTimes is a list of the most recent report timestamps used for calculating + // the running average time between reports. + // _deviceReportingExcessivelyStartTime tracks when a device starts reporting excessively. + // _reportToPersistenceDelayCurrentMultiplier is the current multiplier that is calculated when a + // report comes in. + MTRDeviceStorageBehaviorConfiguration * _storageBehaviorConfiguration; + NSDate * _Nullable _clusterDataPersistenceFirstScheduledTime; + NSMutableArray * _mostRecentReportTimes; + NSDate * _Nullable _deviceReportingExcessivelyStartTime; + double _reportToPersistenceDelayCurrentMultiplier; + + // System time change observer reference + id _systemTimeChangeObserverToken; + + NSMutableSet * _delegates; +} + +// synthesize superclass property readwrite accessors +@synthesize nodeID = _nodeID; +@synthesize deviceController = _deviceController; +@synthesize queue = _queue; +@synthesize asyncWorkQueue = _asyncWorkQueue; +@synthesize state = _state; +@synthesize estimatedStartTime = _estimatedStartTime; +@synthesize estimatedSubscriptionLatency = _estimatedSubscriptionLatency; +//@synthesize lock = _lock; +//@synthesize persistedClusterData = _persistedClusterData; + +- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller +{ + // `super` was NSObject, is now MTRDevice. MTRDevice hides its `init` + if (self = [super initForSubclasses]) { + _lock = OS_UNFAIR_LOCK_INIT; + _timeSyncLock = OS_UNFAIR_LOCK_INIT; + _nodeID = [nodeID copy]; + _fabricIndex = controller.fabricIndex; + _deviceController = controller; + _queue + = dispatch_queue_create("org.csa-iot.matter.framework.device.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + _expectedValueCache = [NSMutableDictionary dictionary]; + _asyncWorkQueue = [[MTRAsyncWorkQueue alloc] initWithContext:self]; + _state = MTRDeviceStateUnknown; + _internalDeviceState = MTRInternalDeviceStateUnsubscribed; + if (controller.controllerDataStore) { + _persistedClusterData = [[NSCache alloc] init]; + } else { + _persistedClusterData = nil; + } + _clusterDataToPersist = nil; + _persistedClusters = [NSMutableSet set]; + + // If there is a data store, make sure we have an observer to monitor system clock changes, so + // NSDate-based write coalescing could be reset and not get into a bad state. + if (_persistedClusterData) { + mtr_weakify(self); + _systemTimeChangeObserverToken = [[NSNotificationCenter defaultCenter] addObserverForName:NSSystemClockDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + mtr_strongify(self); + std::lock_guard lock(self->_lock); + [self _resetStorageBehaviorState]; + }]; + } + + _delegates = [NSMutableSet set]; + + MTR_LOG_DEBUG("%@ init with hex nodeID 0x%016llX", self, _nodeID.unsignedLongLongValue); + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:_systemTimeChangeObserverToken]; + + // TODO: retain cycle and clean up https://github.com/project-chip/connectedhomeip/issues/34267 + MTR_LOG("MTRDevice dealloc: %p", self); +} + +- (NSString *)description +{ + return [NSString + stringWithFormat:@"[fabric: %u, nodeID: 0x%016llX]", self, _fabricIndex, _nodeID.unsignedLongLongValue]; +} + ++ (MTRDevice *)deviceWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller +{ + return [controller deviceForNodeID:nodeID]; +} + +#pragma mark - Time Synchronization + +- (void)_setTimeOnDevice +{ + NSDate * now = [NSDate date]; + // If no date available, error + if (!now) { + MTR_LOG_ERROR("%@ Could not retrieve current date. Unable to setUTCTime on endpoints.", self); + return; + } + + uint64_t matterEpochTimeMicroseconds = 0; + if (!DateToMatterEpochMicroseconds(now, matterEpochTimeMicroseconds)) { + MTR_LOG_ERROR("%@ Could not convert NSDate (%@) to Matter Epoch Time. Unable to setUTCTime on endpoints.", self, now); + return; + } + + // Set Time on each Endpoint with a Time Synchronization Cluster Server + NSArray * endpointsToSync = [self _endpointsWithTimeSyncClusterServer]; + for (NSNumber * endpoint in endpointsToSync) { + MTR_LOG_DEBUG("%@ Setting Time on Endpoint %@", self, endpoint); + [self _setUTCTime:matterEpochTimeMicroseconds withGranularity:MTRTimeSynchronizationGranularityMicrosecondsGranularity forEndpoint:endpoint]; + + // Check how many DST offsets this endpoint supports. + auto dstOffsetsMaxSizePath = [MTRAttributePath attributePathWithEndpointID:endpoint clusterID:@(MTRClusterIDTypeTimeSynchronizationID) attributeID:@(MTRAttributeIDTypeClusterTimeSynchronizationAttributeDSTOffsetListMaxSizeID)]; + auto dstOffsetsMaxSize = [self readAttributeWithEndpointID:dstOffsetsMaxSizePath.endpoint clusterID:dstOffsetsMaxSizePath.cluster attributeID:dstOffsetsMaxSizePath.attribute params:nil]; + if (dstOffsetsMaxSize == nil) { + // This endpoint does not support TZ, so won't support SetDSTOffset. + MTR_LOG("%@ Unable to SetDSTOffset on endpoint %@, since it does not support the TZ feature", self, endpoint); + continue; + } + auto attrReport = [[MTRAttributeReport alloc] initWithResponseValue:@{ + MTRAttributePathKey : dstOffsetsMaxSizePath, + MTRDataKey : dstOffsetsMaxSize, + } + error:nil]; + uint8_t maxOffsetCount; + if (attrReport == nil) { + MTR_LOG_ERROR("%@ DSTOffsetListMaxSize value on endpoint %@ is invalid. Defaulting to 1.", self, endpoint); + maxOffsetCount = 1; + } else { + NSNumber * maxOffsetCountAsNumber = attrReport.value; + maxOffsetCount = maxOffsetCountAsNumber.unsignedCharValue; + if (maxOffsetCount == 0) { + MTR_LOG_ERROR("%@ DSTOffsetListMaxSize value on endpoint %@ is 0, which is not allowed. Defaulting to 1.", self, endpoint); + maxOffsetCount = 1; + } + } + auto * dstOffsets = MTRComputeDSTOffsets(maxOffsetCount); + if (dstOffsets == nil) { + MTR_LOG_ERROR("%@ Could not retrieve DST offset information. Unable to setDSTOffset on endpoint %@.", self, endpoint); + continue; + } + + [self _setDSTOffsets:dstOffsets forEndpoint:endpoint]; + } +} + +- (void)_scheduleNextUpdate:(UInt64)nextUpdateInSeconds +{ + mtr_weakify(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (nextUpdateInSeconds * NSEC_PER_SEC)), self.queue, ^{ + MTR_LOG_DEBUG("%@ Timer expired, start Device Time Update", self); + mtr_strongify(self); + if (self) { + [self _performScheduledTimeUpdate]; + } else { + MTR_LOG_DEBUG("%@ MTRDevice no longer valid. No Timer Scheduled will be scheduled for a Device Time Update.", self); + return; + } + }); + self.timeUpdateScheduled = YES; + MTR_LOG_DEBUG("%@ Timer Scheduled for next Device Time Update, in %llu seconds", self, nextUpdateInSeconds); +} + +// Time Updates are a day apart (this can be changed in the future) +#define MTR_DEVICE_TIME_UPDATE_DEFAULT_WAIT_TIME_SEC (24 * 60 * 60) +// assume lock is held +- (void)_updateDeviceTimeAndScheduleNextUpdate +{ + os_unfair_lock_assert_owner(&self->_timeSyncLock); + if (self.timeUpdateScheduled) { + MTR_LOG_DEBUG("%@ Device Time Update already scheduled", self); + return; + } + + [self _setTimeOnDevice]; + [self _scheduleNextUpdate:MTR_DEVICE_TIME_UPDATE_DEFAULT_WAIT_TIME_SEC]; +} + +- (void)_performScheduledTimeUpdate +{ + std::lock_guard lock(_timeSyncLock); + // Device needs to still be reachable + if (self.state != MTRDeviceStateReachable) { + MTR_LOG_DEBUG("%@ Device is not reachable, canceling Device Time Updates.", self); + return; + } + // Device must not be invalidated + if (!self.timeUpdateScheduled) { + MTR_LOG_DEBUG("%@ Device Time Update is no longer scheduled, MTRDevice may have been invalidated.", self); + return; + } + self.timeUpdateScheduled = NO; + [self _updateDeviceTimeAndScheduleNextUpdate]; +} + +- (NSArray *)_endpointsWithTimeSyncClusterServer +{ + auto partsList = [self readAttributeWithEndpointID:@(0) clusterID:@(MTRClusterIDTypeDescriptorID) attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID) params:nil]; + NSMutableArray * endpointsOnDevice = [self arrayOfNumbersFromAttributeValue:partsList]; + if (!endpointsOnDevice) { + endpointsOnDevice = [[NSMutableArray alloc] init]; + } + // Add Root node! + [endpointsOnDevice addObject:@(0)]; + + NSMutableArray * endpointsWithTimeSyncCluster = [[NSMutableArray alloc] init]; + for (NSNumber * endpoint in endpointsOnDevice) { + // Get list of server clusters on endpoint + auto clusterList = [self readAttributeWithEndpointID:endpoint clusterID:@(MTRClusterIDTypeDescriptorID) attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributeServerListID) params:nil]; + NSArray * clusterArray = [self arrayOfNumbersFromAttributeValue:clusterList]; + + if (clusterArray && [clusterArray containsObject:@(MTRClusterIDTypeTimeSynchronizationID)]) { + [endpointsWithTimeSyncCluster addObject:endpoint]; + } + } + MTR_LOG_DEBUG("%@ Device has following endpoints with Time Sync Cluster Server: %@", self, endpointsWithTimeSyncCluster); + return endpointsWithTimeSyncCluster; +} + +- (void)_setUTCTime:(UInt64)matterEpochTime withGranularity:(uint8_t)granularity forEndpoint:(NSNumber *)endpoint +{ + MTR_LOG_DEBUG(" %@ _setUTCTime with matterEpochTime: %llu, endpoint %@", self, matterEpochTime, endpoint); + MTRTimeSynchronizationClusterSetUTCTimeParams * params = [[MTRTimeSynchronizationClusterSetUTCTimeParams + alloc] init]; + params.utcTime = @(matterEpochTime); + params.granularity = @(granularity); + auto setUTCTimeResponseHandler = ^(id _Nullable response, NSError * _Nullable error) { + if (error) { + MTR_LOG_ERROR("%@ _setUTCTime failed on endpoint %@, with parameters %@, error: %@", self, endpoint, params, error); + } + }; + + [self _invokeKnownCommandWithEndpointID:endpoint + clusterID:@(MTRClusterIDTypeTimeSynchronizationID) + commandID:@(MTRCommandIDTypeClusterTimeSynchronizationCommandSetUTCTimeID) + commandPayload:params + expectedValues:nil + expectedValueInterval:nil + timedInvokeTimeout:nil + serverSideProcessingTimeout:params.serverSideProcessingTimeout + responseClass:nil + queue:self.queue + completion:setUTCTimeResponseHandler]; +} + +- (void)_setDSTOffsets:(NSArray *)dstOffsets forEndpoint:(NSNumber *)endpoint +{ + MTR_LOG_DEBUG("%@ _setDSTOffsets with offsets: %@, endpoint %@", + self, dstOffsets, endpoint); + + MTRTimeSynchronizationClusterSetDSTOffsetParams * params = [[MTRTimeSynchronizationClusterSetDSTOffsetParams + alloc] init]; + params.dstOffset = dstOffsets; + + auto setDSTOffsetResponseHandler = ^(id _Nullable response, NSError * _Nullable error) { + if (error) { + MTR_LOG_ERROR("%@ _setDSTOffsets failed on endpoint %@, with parameters %@, error: %@", self, endpoint, params, error); + } + }; + + [self _invokeKnownCommandWithEndpointID:endpoint + clusterID:@(MTRClusterIDTypeTimeSynchronizationID) + commandID:@(MTRCommandIDTypeClusterTimeSynchronizationCommandSetDSTOffsetID) + commandPayload:params + expectedValues:nil + expectedValueInterval:nil + timedInvokeTimeout:nil + serverSideProcessingTimeout:params.serverSideProcessingTimeout + responseClass:nil + queue:self.queue + completion:setDSTOffsetResponseHandler]; +} + +- (NSMutableArray *)arrayOfNumbersFromAttributeValue:(MTRDeviceDataValueDictionary)dataDictionary +{ + if (![MTRArrayValueType isEqual:dataDictionary[MTRTypeKey]]) { + return nil; + } + + id value = dataDictionary[MTRValueKey]; + if (![value isKindOfClass:NSArray.class]) { + return nil; + } + + NSArray * valueArray = value; + __auto_type outputArray = [NSMutableArray arrayWithCapacity:valueArray.count]; + + for (id item in valueArray) { + if (![item isKindOfClass:NSDictionary.class]) { + return nil; + } + + NSDictionary * itemDictionary = item; + id data = itemDictionary[MTRDataKey]; + if (![data isKindOfClass:NSDictionary.class]) { + return nil; + } + + NSDictionary * dataDictionary = data; + id dataType = dataDictionary[MTRTypeKey]; + id dataValue = dataDictionary[MTRValueKey]; + if (![dataType isKindOfClass:NSString.class] || ![dataValue isKindOfClass:NSNumber.class]) { + return nil; + } + [outputArray addObject:dataValue]; + } + return outputArray; +} + +#pragma mark Subscription and delegate handling + +// subscription intervals are in seconds +#define MTR_DEVICE_SUBSCRIPTION_MAX_INTERVAL_MIN (10 * 60) // 10 minutes (for now) +#define MTR_DEVICE_SUBSCRIPTION_MAX_INTERVAL_MAX (60 * 60) // 60 minutes + +- (BOOL)_subscriptionsAllowed +{ + os_unfair_lock_assert_owner(&self->_lock); + + // We should not allow a subscription for device controllers over XPC. + return ![_deviceController isKindOfClass:MTRDeviceControllerOverXPC.class]; +} + +- (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue +{ + MTR_LOG("%@ setDelegate %@", self, delegate); + [self _addDelegate:delegate queue:queue interestedPathsForAttributes:nil interestedPathsForEvents:nil]; +} + +- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue +{ + MTR_LOG("%@ addDelegate %@", self, delegate); + [self _addDelegate:delegate queue:queue interestedPathsForAttributes:nil interestedPathsForEvents:nil]; +} + +- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents +{ + MTR_LOG("%@ addDelegate %@ with interested attribute paths %@ event paths %@", self, delegate, interestedPathsForAttributes, interestedPathsForEvents); + [self _addDelegate:delegate queue:queue interestedPathsForAttributes:interestedPathsForAttributes interestedPathsForEvents:interestedPathsForEvents]; +} + +- (void)_addDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents +{ + std::lock_guard lock(_lock); + + // Replace delegate info with the same delegate object, and opportunistically remove defunct delegate references + NSMutableSet * delegatesToRemove = [NSMutableSet set]; + for (MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo in _delegates) { + id strongDelegate = delegateInfo.delegate; + if (!strongDelegate) { + [delegatesToRemove addObject:delegateInfo]; + MTR_LOG("%@ removing delegate info for nil delegate %p", self, delegateInfo.delegatePointerValue); + } else if (strongDelegate == delegate) { + [delegatesToRemove addObject:delegateInfo]; + MTR_LOG("%@ replacing delegate info for %p", self, delegate); + } + } + if (delegatesToRemove.count) { + NSUInteger oldDelegatesCount = _delegates.count; + [_delegates minusSet:delegatesToRemove]; + MTR_LOG("%@ addDelegate: removed %lu", self, static_cast(_delegates.count - oldDelegatesCount)); + } + + MTRDeviceDelegateInfo_ConcreteCopy * newDelegateInfo = [[MTRDeviceDelegateInfo_ConcreteCopy alloc] initWithDelegate:delegate queue:queue interestedPathsForAttributes:interestedPathsForAttributes interestedPathsForEvents:interestedPathsForEvents]; + [_delegates addObject:newDelegateInfo]; + MTR_LOG("%@ added delegate info %@", self, newDelegateInfo); + + __block BOOL shouldSetUpSubscription = [self _subscriptionsAllowed]; + + // For unit testing only. If this ever changes to not being for unit testing purposes, + // we would need to move the code outside of where we acquire the lock above. +#ifdef DEBUG + [self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestShouldSetUpSubscriptionForDevice:)]) { + shouldSetUpSubscription = [testDelegate unitTestShouldSetUpSubscriptionForDevice:self]; + } + }]; +#endif + + if (shouldSetUpSubscription) { + MTR_LOG("%@ - starting subscription setup", self); + // Record the time of first addDelegate call that triggers initial subscribe, and do not reset this value on subsequent addDelegate calls + if (!_initialSubscribeStart) { + _initialSubscribeStart = [NSDate now]; + } + if ([self _deviceUsesThread]) { + MTR_LOG(" => %@ - device is a thread device, scheduling in pool", self); + [self _scheduleSubscriptionPoolWork:^{ + std::lock_guard lock(self->_lock); + [self _setupSubscriptionWithReason:@"delegate is set and scheduled subscription is happening"]; + } inNanoseconds:0 description:@"MTRDevice setDelegate first subscription"]; + } else { + [self _setupSubscriptionWithReason:@"delegate is set and subscription is needed"]; + } + } +} + +- (void)removeDelegate:(id)delegate +{ + MTR_LOG("%@ removeDelegate %@", self, delegate); + + std::lock_guard lock(_lock); + + NSMutableSet * delegatesToRemove = [NSMutableSet set]; + [self _iterateDelegatesWithBlock:^(MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo) { + id strongDelegate = delegateInfo.delegate; + if (strongDelegate == delegate) { + [delegatesToRemove addObject:delegateInfo]; + MTR_LOG("%@ removing delegate info %@ for %p", self, delegateInfo, delegate); + } + }]; + if (delegatesToRemove.count) { + NSUInteger oldDelegatesCount = _delegates.count; + [_delegates minusSet:delegatesToRemove]; + MTR_LOG("%@ removeDelegate: removed %lu", self, static_cast(_delegates.count - oldDelegatesCount)); + } +} + +- (void)invalidate +{ + MTR_LOG("%@ invalidate", self); + + [_asyncWorkQueue invalidate]; + + os_unfair_lock_lock(&self->_timeSyncLock); + _timeUpdateScheduled = NO; + os_unfair_lock_unlock(&self->_timeSyncLock); + + os_unfair_lock_lock(&self->_lock); + + _state = MTRDeviceStateUnknown; + + [_delegates removeAllObjects]; + + // Make sure we don't try to resubscribe if we have a pending resubscribe + // attempt, since we now have no delegate. + _reattemptingSubscription = NO; + + [_deviceController asyncDispatchToMatterQueue:^{ + MTR_LOG("%@ invalidate disconnecting ReadClient and SubscriptionCallback", self); + + // Destroy the read client and callback (has to happen on the Matter + // queue, to avoid deleting objects that are being referenced), to + // tear down the subscription. We will get no more callbacks from + // the subscription after this point. + std::lock_guard lock(self->_lock); + self->_currentReadClient = nullptr; + if (self->_currentSubscriptionCallback) { + delete self->_currentSubscriptionCallback; + } + self->_currentSubscriptionCallback = nullptr; + + [self _changeInternalState:MTRInternalDeviceStateUnsubscribed]; + } + errorHandler:nil]; + + [self _stopConnectivityMonitoring]; + + os_unfair_lock_unlock(&self->_lock); +} + +- (void)nodeMayBeAdvertisingOperational +{ + assertChipStackLockedByCurrentThread(); + + MTR_LOG("%@ saw new operational advertisement", self); + + [self _triggerResubscribeWithReason:@"operational advertisement seen" + nodeLikelyReachable:YES]; +} + +// Trigger a resubscribe as needed. nodeLikelyReachable should be YES if we +// have reason to suspect the node is now reachable, NO if we have no idea +// whether it might be. +- (void)_triggerResubscribeWithReason:(NSString *)reason nodeLikelyReachable:(BOOL)nodeLikelyReachable +{ + MTR_LOG("%@ _triggerResubscribeWithReason called with reason %@", self, reason); + assertChipStackLockedByCurrentThread(); + + // We might want to trigger a resubscribe on our existing ReadClient. Do + // that outside the scope of our lock, so we're not calling arbitrary code + // we don't control with the lock held. This is safe, because we are + // running on he Matter queue and the ReadClient can't get destroyed while + // we are on that queue. + ReadClient * readClientToResubscribe = nullptr; + SubscriptionCallback * subscriptionCallback = nullptr; + + os_unfair_lock_lock(&self->_lock); + + // Don't change state to MTRDeviceStateReachable, since the device might not + // in fact be reachable yet; we won't know until we have managed to + // establish a CASE session. And at that point, our subscription will + // trigger the state change as needed. + if (self.reattemptingSubscription) { + [self _reattemptSubscriptionNowIfNeededWithReason:reason]; + } else { + readClientToResubscribe = self->_currentReadClient; + subscriptionCallback = self->_currentSubscriptionCallback; + } + os_unfair_lock_unlock(&self->_lock); + + if (readClientToResubscribe) { + if (nodeLikelyReachable) { + // If we have reason to suspect the node is now reachable, reset the + // backoff timer, so that if this attempt fails we'll try again + // quickly; it's possible we'll just catch the node at a bad time + // here (e.g. still booting up), but should try again reasonably quickly. + subscriptionCallback->ResetResubscriptionBackoff(); + } + readClientToResubscribe->TriggerResubscribeIfScheduled(reason.UTF8String); + } +} + +// Return YES if we are in a state where, apart from communication issues with +// the device, we will be able to get reports via our subscription. +- (BOOL)_subscriptionAbleToReport +{ + std::lock_guard lock(_lock); + if (![self _delegateExists]) { + // No delegate definitely means no subscription. + return NO; + } + + // For unit testing only, matching logic in setDelegate +#ifdef DEBUG + __block BOOL useTestDelegateOverride = NO; + __block BOOL testDelegateShouldSetUpSubscriptionForDevice = NO; + [self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestShouldSetUpSubscriptionForDevice:)]) { + useTestDelegateOverride = YES; + testDelegateShouldSetUpSubscriptionForDevice = [testDelegate unitTestShouldSetUpSubscriptionForDevice:self]; + } + }]; + if (useTestDelegateOverride && !testDelegateShouldSetUpSubscriptionForDevice) { + return NO; + } + +#endif + + // Subscriptions are not able to report if they are not allowed. + return [self _subscriptionsAllowed]; +} + +// Notification that read-through was skipped for an attribute read. +- (void)_readThroughSkipped +{ + std::lock_guard lock(_lock); + if (_state == MTRDeviceStateReachable) { + // We're getting reports from the device, so there's nothing else to be + // done here. We could skip this check, because our "try to + // resubscribe" code would be a no-op in this case, but then we'd have + // an extra dispatch in the common case of read-while-subscribed, which + // is not great for peformance. + return; + } + + if (_lastSubscriptionFailureTime == nil) { + // No need to try to do anything here, because we have never failed a + // subscription attempt (so we might be in the middle of one now, and no + // need to prod things along). + return; + } + + if ([[NSDate now] timeIntervalSinceDate:_lastSubscriptionFailureTime] < MTRDEVICE_MIN_RESUBSCRIBE_DUE_TO_READ_INTERVAL_SECONDS) { + // Not enough time has passed since we last tried. Don't create extra + // network traffic. + // + // TODO: Do we need to worry about this being too spammy in the log if + // we keep getting reads while not subscribed? We could add another + // backoff timer or counter for the log line... + MTR_LOG_DEBUG("%@ skipping resubscribe from skipped read-through: not enough time has passed since %@", self, _lastSubscriptionFailureTime); + return; + } + + // Do the remaining work on the Matter queue, because we may want to touch + // ReadClient in there. If the dispatch fails, that's fine; it means our + // controller has shut down, so nothing to be done. + [_deviceController asyncDispatchToMatterQueue:^{ + [self _triggerResubscribeWithReason:@"read-through skipped while not subscribed" nodeLikelyReachable:NO]; + } + errorHandler:nil]; +} + +- (BOOL)_delegateExists +{ + os_unfair_lock_assert_owner(&self->_lock); + return [self _iterateDelegatesWithBlock:nil]; +} + +// Returns YES if any non-null delegates were found +- (BOOL)_iterateDelegatesWithBlock:(void(NS_NOESCAPE ^)(MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo)_Nullable)block +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (!_delegates.count) { + MTR_LOG_DEBUG("%@ no delegates to iterate", self); + return NO; + } + + // Opportunistically remove defunct delegate references on every iteration + NSMutableSet * delegatesToRemove = nil; + for (MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo in _delegates) { + id strongDelegate = delegateInfo.delegate; + if (strongDelegate) { + if (block) { + @autoreleasepool { + block(delegateInfo); + } + } + (void) strongDelegate; // ensure it stays alive + } else { + if (!delegatesToRemove) { + delegatesToRemove = [NSMutableSet set]; + } + [delegatesToRemove addObject:delegateInfo]; + } + } + + if (delegatesToRemove.count) { + [_delegates minusSet:delegatesToRemove]; + MTR_LOG("%@ _iterateDelegatesWithBlock: removed %lu remaining %lu", self, static_cast(delegatesToRemove.count), (unsigned long) static_cast(_delegates.count)); + } + + return (_delegates.count > 0); +} + +- (BOOL)_callDelegatesWithBlock:(void (^)(id delegate))block +{ + os_unfair_lock_assert_owner(&self->_lock); + + __block NSUInteger delegatesCalled = 0; + [self _iterateDelegatesWithBlock:^(MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo) { + if ([delegateInfo callDelegateWithBlock:block]) { + delegatesCalled++; + } + }]; + + return (delegatesCalled > 0); +} + +#ifdef DEBUG +// Only used for unit test purposes - normal delegate should not expect or handle being called back synchronously +// Returns YES if a delegate is called +- (void)_callFirstDelegateSynchronouslyWithBlock:(void (^)(id delegate))block +{ + os_unfair_lock_assert_owner(&self->_lock); + + for (MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo in _delegates) { + if ([delegateInfo callDelegateSynchronouslyWithBlock:block]) { + MTR_LOG("%@ _callFirstDelegateSynchronouslyWithBlock: successfully called %@", self, delegateInfo); + return; + } + } +} +#endif + +- (void)_callDelegateDeviceCachePrimed +{ + os_unfair_lock_assert_owner(&self->_lock); + [self _callDelegatesWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(deviceCachePrimed:)]) { + [delegate deviceCachePrimed:self]; + } + }]; +} + +// assume lock is held +- (void)_changeState:(MTRDeviceState)state +{ + os_unfair_lock_assert_owner(&self->_lock); + MTRDeviceState lastState = _state; + _state = state; + if (lastState != state) { + if (state != MTRDeviceStateReachable) { + MTR_LOG("%@ reachability state change %lu => %lu, set estimated start time to nil", self, static_cast(lastState), + static_cast(state)); + _estimatedStartTime = nil; + _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; + } else { + MTR_LOG( + "%@ reachability state change %lu => %lu", self, static_cast(lastState), static_cast(state)); + } + [self _callDelegatesWithBlock:^(id delegate) { + [delegate device:self stateChanged:state]; + }]; + } else { + MTR_LOG( + "%@ Not reporting reachability state change, since no change in state %lu => %lu", self, static_cast(lastState), static_cast(state)); + } +} + +- (void)_changeInternalState:(MTRInternalDeviceState)state +{ + os_unfair_lock_assert_owner(&self->_lock); + MTRInternalDeviceState lastState = _internalDeviceState; + _internalDeviceState = state; + if (lastState != state) { + MTR_LOG("%@ internal state change %lu => %lu", self, static_cast(lastState), static_cast(state)); + + /* BEGIN DRAGONS: This is a huge hack for a specific use case, do not rename, remove or modify behavior here */ + // TODO: This should only be called for thread devices + [self _callDelegatesWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(_deviceInternalStateChanged:)]) { + [delegate _deviceInternalStateChanged:self]; + } + }]; + /* END DRAGONS */ + } +} + +#ifdef DEBUG +- (MTRInternalDeviceState)_getInternalState +{ + std::lock_guard lock(self->_lock); + return _internalDeviceState; +} +#endif + +// First Time Sync happens 2 minutes after reachability (this can be changed in the future) +#define MTR_DEVICE_TIME_UPDATE_INITIAL_WAIT_TIME_SEC (60 * 2) +- (void)_handleSubscriptionEstablished +{ + os_unfair_lock_lock(&self->_lock); + + // If subscription had reset since this handler was scheduled, do not execute "established" logic below + if (!HaveSubscriptionEstablishedRightNow(_internalDeviceState)) { + MTR_LOG("%@ _handleSubscriptionEstablished run with internal state %lu - skipping subscription establishment logic", self, static_cast(_internalDeviceState)); + return; + } + + // We have completed the subscription work - remove from the subscription pool. + [self _clearSubscriptionPoolWork]; + + // No need to monitor connectivity after subscription establishment + [self _stopConnectivityMonitoring]; + + // reset subscription attempt wait time when subscription succeeds + _lastSubscriptionAttemptWait = 0; + + auto initialSubscribeStart = _initialSubscribeStart; + // We no longer need to track subscribe latency for this device. + _initialSubscribeStart = nil; + + if (initialSubscribeStart != nil) { + // We want time interval from initialSubscribeStart to now, not the other + // way around. + NSTimeInterval subscriptionLatency = -[initialSubscribeStart timeIntervalSinceNow]; + if (_estimatedSubscriptionLatency == nil) { + _estimatedSubscriptionLatency = @(subscriptionLatency); + } else { + NSTimeInterval newSubscriptionLatencyEstimate = MTRDEVICE_SUBSCRIPTION_LATENCY_NEW_VALUE_WEIGHT * subscriptionLatency + (1 - MTRDEVICE_SUBSCRIPTION_LATENCY_NEW_VALUE_WEIGHT) * _estimatedSubscriptionLatency.doubleValue; + _estimatedSubscriptionLatency = @(newSubscriptionLatencyEstimate); + } + [self _storePersistedDeviceData]; + } + + os_unfair_lock_unlock(&self->_lock); + + os_unfair_lock_lock(&self->_timeSyncLock); + + if (!self.timeUpdateScheduled) { + [self _scheduleNextUpdate:MTR_DEVICE_TIME_UPDATE_INITIAL_WAIT_TIME_SEC]; + } + + os_unfair_lock_unlock(&self->_timeSyncLock); +} + +- (void)_handleSubscriptionError:(NSError *)error +{ + std::lock_guard lock(_lock); + [self _doHandleSubscriptionError:error]; +} + +- (void)_doHandleSubscriptionError:(NSError *)error +{ + os_unfair_lock_assert_owner(&_lock); + + [self _changeInternalState:MTRInternalDeviceStateUnsubscribed]; + _unreportedEvents = nil; + + [self _changeState:MTRDeviceStateUnreachable]; +} + +- (BOOL)deviceUsesThread +{ + std::lock_guard lock(_lock); + return [self _deviceUsesThread]; +} + +// This method is used for signaling whether to use the subscription pool. This functions as +// a heuristic for whether to throttle subscriptions to the device via a pool of subscriptions. +// If products appear that have both Thread and Wifi enabled but are primarily on wifi, this +// method will need to be updated to reflect that. +- (BOOL)_deviceUsesThread +{ + os_unfair_lock_assert_owner(&self->_lock); + +#ifdef DEBUG + // Note: This is a hack to allow our unit tests to test the subscription pooling behavior we have implemented for thread, so we mock devices to be a thread device + __block BOOL pretendThreadEnabled = NO; + [self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestPretendThreadEnabled:)]) { + pretendThreadEnabled = [testDelegate unitTestPretendThreadEnabled:self]; + } + }]; + if (pretendThreadEnabled) { + return YES; + } +#endif + + MTRClusterPath * networkCommissioningClusterPath = [MTRClusterPath clusterPathWithEndpointID:@(kRootEndpointId) clusterID:@(MTRClusterIDTypeNetworkCommissioningID)]; + MTRDeviceClusterData * networkCommissioningClusterData = [self _clusterDataForPath:networkCommissioningClusterPath]; + NSNumber * networkCommissioningClusterFeatureMapValueNumber = networkCommissioningClusterData.attributes[@(MTRClusterGlobalAttributeFeatureMapID)][MTRValueKey]; + + if (networkCommissioningClusterFeatureMapValueNumber == nil) + return NO; + if (![networkCommissioningClusterFeatureMapValueNumber isKindOfClass:[NSNumber class]]) { + MTR_LOG_ERROR("%@ Unexpected NetworkCommissioning FeatureMap value %@", self, networkCommissioningClusterFeatureMapValueNumber); + return NO; + } + + uint32_t networkCommissioningClusterFeatureMapValue = static_cast(networkCommissioningClusterFeatureMapValueNumber.unsignedLongValue); + + return (networkCommissioningClusterFeatureMapValue & MTRNetworkCommissioningFeatureThreadNetworkInterface) != 0 ? YES : NO; +} + +- (void)_clearSubscriptionPoolWork +{ + os_unfair_lock_assert_owner(&self->_lock); + MTRAsyncWorkCompletionBlock completion = self->_subscriptionPoolWorkCompletionBlock; + if (completion) { +#ifdef DEBUG + [self _callDelegatesWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestSubscriptionPoolWorkComplete:)]) { + [testDelegate unitTestSubscriptionPoolWorkComplete:self]; + } + }]; +#endif + self->_subscriptionPoolWorkCompletionBlock = nil; + completion(MTRAsyncWorkComplete); + } +} + +- (void)_scheduleSubscriptionPoolWork:(dispatch_block_t)workBlock inNanoseconds:(int64_t)inNanoseconds description:(NSString *)description +{ + os_unfair_lock_assert_owner(&self->_lock); + + // Sanity check we are not scheduling for this device multiple times in the pool + if (_subscriptionPoolWorkCompletionBlock) { + MTR_LOG("%@ already scheduled in subscription pool for this device - ignoring: %@", self, description); + return; + } + + // Wait the required amount of time, then put it in the subscription pool to wait additionally for a spot, if needed + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, inNanoseconds), dispatch_get_main_queue(), ^{ + // In the case where a resubscription triggering event happened and already established, running the work block should result in a no-op + MTRAsyncWorkItem * workItem = [[MTRAsyncWorkItem alloc] initWithQueue:self.queue]; + [workItem setReadyHandler:^(id _Nonnull context, NSInteger retryCount, MTRAsyncWorkCompletionBlock _Nonnull completion) { + MTR_LOG("%@ - work item is ready to attempt pooled subscription", self); + os_unfair_lock_lock(&self->_lock); +#ifdef DEBUG + [self _callDelegatesWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestSubscriptionPoolDequeue:)]) { + [testDelegate unitTestSubscriptionPoolDequeue:self]; + } + }]; +#endif + if (self->_subscriptionPoolWorkCompletionBlock) { + // This means a resubscription triggering event happened and is now in-progress + MTR_LOG("%@ timer fired but already running in subscription pool - ignoring: %@", self, description); + os_unfair_lock_unlock(&self->_lock); + + // call completion as complete to remove from queue + completion(MTRAsyncWorkComplete); + return; + } + + // Otherwise, save the completion block + self->_subscriptionPoolWorkCompletionBlock = completion; + os_unfair_lock_unlock(&self->_lock); + + workBlock(); + }]; + [self->_deviceController.concurrentSubscriptionPool enqueueWorkItem:workItem description:description]; + MTR_LOG("%@ - enqueued in the subscription pool", self); + }); +} + +- (void)_handleResubscriptionNeededWithDelay:(NSNumber *)resubscriptionDelayMs +{ + BOOL deviceUsesThread; + + os_unfair_lock_lock(&self->_lock); + + [self _changeState:MTRDeviceStateUnknown]; + [self _changeInternalState:MTRInternalDeviceStateResubscribing]; + + // If we are here, then the ReadClient either just detected a subscription + // drop or just tried again and failed. Either way, count it as "tried and + // failed to subscribe": in the latter case it's actually true, and in the + // former case we recently had a subscription and do not want to be forcing + // retries immediately. + _lastSubscriptionFailureTime = [NSDate now]; + + deviceUsesThread = [self _deviceUsesThread]; + + // If a previous resubscription failed, remove the item from the subscription pool. + [self _clearSubscriptionPoolWork]; + + os_unfair_lock_unlock(&self->_lock); + + // Use the existing _triggerResubscribeWithReason mechanism, which does the right checks when + // this block is run -- if other triggering events had happened, this would become a no-op. + auto resubscriptionBlock = ^{ + [self->_deviceController asyncDispatchToMatterQueue:^{ + [self _triggerResubscribeWithReason:@"ResubscriptionNeeded timer fired" nodeLikelyReachable:NO]; + } errorHandler:^(NSError * _Nonnull error) { + // If controller is not running, clear work item from the subscription queue + MTR_LOG_ERROR("%@ could not dispatch to matter queue for resubscription - error %@", self, error); + std::lock_guard lock(self->_lock); + [self _clearSubscriptionPoolWork]; + }]; + }; + + int64_t resubscriptionDelayNs = static_cast(resubscriptionDelayMs.unsignedIntValue * NSEC_PER_MSEC); + if (deviceUsesThread) { + std::lock_guard lock(_lock); + // For Thread-enabled devices, schedule the _triggerResubscribeWithReason call to run in the subscription pool + [self _scheduleSubscriptionPoolWork:resubscriptionBlock inNanoseconds:resubscriptionDelayNs description:@"ReadClient resubscription"]; + } else { + // For non-Thread-enabled devices, just call the resubscription block after the specified time + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, resubscriptionDelayNs), self.queue, resubscriptionBlock); + } + + // Set up connectivity monitoring in case network routability changes for the positive, to accelerate resubscription + [self _setupConnectivityMonitoring]; +} + +- (void)_handleSubscriptionReset:(NSNumber * _Nullable)retryDelay +{ + std::lock_guard lock(_lock); + [self _doHandleSubscriptionReset:retryDelay]; +} + +- (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay +{ + os_unfair_lock_assert_owner(&_lock); + + // If we are here, then either we failed to establish initial CASE, or we + // failed to send the initial SubscribeRequest message, or our ReadClient + // has given up completely. Those all count as "we have tried and failed to + // subscribe". + _lastSubscriptionFailureTime = [NSDate now]; + + // if there is no delegate then also do not retry + if (![self _delegateExists]) { + // NOTE: Do not log anything here: we have been invalidated, and the + // Matter stack might already be torn down. + return; + } + + // don't schedule multiple retries + if (self.reattemptingSubscription) { + return; + } + + self.reattemptingSubscription = YES; + + NSTimeInterval secondsToWait; + if (_lastSubscriptionAttemptWait < MTRDEVICE_SUBSCRIPTION_ATTEMPT_MIN_WAIT_SECONDS) { + _lastSubscriptionAttemptWait = MTRDEVICE_SUBSCRIPTION_ATTEMPT_MIN_WAIT_SECONDS; + secondsToWait = _lastSubscriptionAttemptWait; + } else if (retryDelay != nil) { + // The device responded but is currently busy. Reset our backoff + // counter, so that we don't end up waiting for a long time if the next + // attempt fails for some reason, and retry after whatever time period + // the device told us to use. + _lastSubscriptionAttemptWait = 0; + secondsToWait = retryDelay.doubleValue; + MTR_LOG("%@ resetting resubscribe attempt counter, and delaying by the server-provided delay: %f", + self, secondsToWait); + } else { + _lastSubscriptionAttemptWait *= 2; + if (_lastSubscriptionAttemptWait > MTRDEVICE_SUBSCRIPTION_ATTEMPT_MAX_WAIT_SECONDS) { + _lastSubscriptionAttemptWait = MTRDEVICE_SUBSCRIPTION_ATTEMPT_MAX_WAIT_SECONDS; + } + secondsToWait = _lastSubscriptionAttemptWait; + } + + MTR_LOG("%@ scheduling to reattempt subscription in %f seconds", self, secondsToWait); + + // If we started subscription or session establishment but failed, remove item from the subscription pool so we can re-queue. + [self _clearSubscriptionPoolWork]; + + // Call _reattemptSubscriptionNowIfNeededWithReason when timer fires - if subscription is + // in a better state at that time this will be a no-op. + auto resubscriptionBlock = ^{ + std::lock_guard lock(self->_lock); + [self _reattemptSubscriptionNowIfNeededWithReason:@"got subscription reset"]; + }; + + int64_t resubscriptionDelayNs = static_cast(secondsToWait * NSEC_PER_SEC); + if ([self _deviceUsesThread]) { + // For Thread-enabled devices, schedule the _reattemptSubscriptionNowIfNeededWithReason call to run in the subscription pool + [self _scheduleSubscriptionPoolWork:resubscriptionBlock inNanoseconds:resubscriptionDelayNs description:@"MTRDevice resubscription"]; + } else { + // For non-Thread-enabled devices, just call the resubscription block after the specified time + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, resubscriptionDelayNs), self.queue, resubscriptionBlock); + } +} + +- (void)_reattemptSubscriptionNowIfNeededWithReason:(NSString *)reason +{ + os_unfair_lock_assert_owner(&self->_lock); + if (!self.reattemptingSubscription) { + return; + } + + MTR_LOG("%@ reattempting subscription with reason %@", self, reason); + self.reattemptingSubscription = NO; + [self _setupSubscriptionWithReason:reason]; +} + +- (void)_handleUnsolicitedMessageFromPublisher +{ + std::lock_guard lock(_lock); + + [self _changeState:MTRDeviceStateReachable]; + + [self _callDelegatesWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(deviceBecameActive:)]) { + [delegate deviceBecameActive:self]; + } + }]; + + // in case this is called during exponential back off of subscription + // reestablishment, this starts the attempt right away + // TODO: This doesn't really make sense. If we _don't_ have a live + // ReadClient how did we get this notification and if we _do_ have an active + // ReadClient, this call or _setupSubscriptionWithReason would be no-ops. + [self _reattemptSubscriptionNowIfNeededWithReason:@"got unsolicited message from publisher"]; +} + +- (void)_markDeviceAsUnreachableIfNeverSubscribed +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (HadSubscriptionEstablishedOnce(_internalDeviceState)) { + return; + } + + MTR_LOG("%@ still not subscribed, marking the device as unreachable", self); + [self _changeState:MTRDeviceStateUnreachable]; +} + +- (void)_handleReportBegin +{ + std::lock_guard lock(_lock); + + _receivingReport = YES; + if (_state != MTRDeviceStateReachable) { + [self _changeState:MTRDeviceStateReachable]; + } + + // If we currently don't have an established subscription, this must be a + // priming report. + _receivingPrimingReport = !HaveSubscriptionEstablishedRightNow(_internalDeviceState); +} + +- (NSDictionary *)_clusterDataToPersistSnapshot +{ + os_unfair_lock_assert_owner(&self->_lock); + NSMutableDictionary * clusterDataToReturn = [NSMutableDictionary dictionary]; + for (MTRClusterPath * clusterPath in _clusterDataToPersist) { + clusterDataToReturn[clusterPath] = [_clusterDataToPersist[clusterPath] copy]; + } + + return clusterDataToReturn; +} + +- (NSTimeInterval)_reportToPersistenceDelayTimeAfterMutiplier +{ + return _storageBehaviorConfiguration.reportToPersistenceDelayTime * _reportToPersistenceDelayCurrentMultiplier; +} + +- (NSTimeInterval)_reportToPersistenceDelayTimeMaxAfterMutiplier +{ + return _storageBehaviorConfiguration.reportToPersistenceDelayTimeMax * _reportToPersistenceDelayCurrentMultiplier; +} + +- (BOOL)_dataStoreExists +{ + os_unfair_lock_assert_owner(&self->_lock); + return _persistedClusterData != nil; +} + +- (void)_persistClusterData +{ + os_unfair_lock_assert_owner(&self->_lock); + + // Sanity check + if (![self _dataStoreExists]) { + MTR_LOG_ERROR("%@ storage behavior: no data store in _persistClusterData!", self); + return; + } + + // Nothing to persist + if (!_clusterDataToPersist.count) { + return; + } + + MTR_LOG("%@ Storing cluster information (data version and attributes) count: %lu", self, static_cast(_clusterDataToPersist.count)); + // We're going to hand out these MTRDeviceClusterData objects to our + // storage implementation, which will try to read them later. Make sure + // we snapshot the state here instead of handing out live copies. + NSDictionary * clusterData = [self _clusterDataToPersistSnapshot]; + [_deviceController.controllerDataStore storeClusterData:clusterData forNodeID:_nodeID]; + for (MTRClusterPath * clusterPath in _clusterDataToPersist) { + [_persistedClusterData setObject:_clusterDataToPersist[clusterPath] forKey:clusterPath]; + [_persistedClusters addObject:clusterPath]; + } + + // TODO: There is one edge case not handled well here: if the + // storeClusterData call above fails somehow, and then the data gets + // evicted from _persistedClusterData, we could end up in a situation + // where when we page things in from storage we have stale values and + // hence effectively lose the delta that we failed to persist. + // + // The only way to handle this would be to detect it when it happens, + // then re-subscribe at that point, which would cause the relevant data + // to be sent to us via the priming read. + _clusterDataToPersist = nil; + +#ifdef DEBUG + [self _callDelegatesWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestClusterDataPersisted:)]) { + [testDelegate unitTestClusterDataPersisted:self]; + } + }]; +#endif +} + +- (BOOL)_deviceIsReportingExcessively +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (!_deviceReportingExcessivelyStartTime) { + return NO; + } + + NSTimeInterval intervalSinceDeviceReportingExcessively = -[_deviceReportingExcessivelyStartTime timeIntervalSinceNow]; + BOOL deviceIsReportingExcessively = intervalSinceDeviceReportingExcessively > _storageBehaviorConfiguration.deviceReportingExcessivelyIntervalThreshold; + if (deviceIsReportingExcessively) { + MTR_LOG("%@ storage behavior: device has been reporting excessively for %.3lf seconds", self, intervalSinceDeviceReportingExcessively); + } + return deviceIsReportingExcessively; +} + +- (void)_persistClusterDataAsNeeded +{ + std::lock_guard lock(_lock); + + // Nothing to persist + if (!_clusterDataToPersist.count) { + return; + } + + // This is run with a dispatch_after, and need to check again if this device is reporting excessively + if ([self _deviceIsReportingExcessively]) { + return; + } + + NSDate * lastReportTime = [_mostRecentReportTimes lastObject]; + NSTimeInterval intervalSinceLastReport = -[lastReportTime timeIntervalSinceNow]; + if (intervalSinceLastReport < [self _reportToPersistenceDelayTimeAfterMutiplier]) { + // A report came in after this call was scheduled + + if (!_clusterDataPersistenceFirstScheduledTime) { + MTR_LOG_ERROR("%@ storage behavior: expects _clusterDataPersistenceFirstScheduledTime if _clusterDataToPersist exists", self); + return; + } + + NSTimeInterval intervalSinceFirstScheduledPersistence = -[_clusterDataPersistenceFirstScheduledTime timeIntervalSinceNow]; + if (intervalSinceFirstScheduledPersistence < [self _reportToPersistenceDelayTimeMaxAfterMutiplier]) { + MTR_LOG("%@ storage behavior: not persisting: intervalSinceLastReport %lf intervalSinceFirstScheduledPersistence %lf", self, intervalSinceLastReport, intervalSinceFirstScheduledPersistence); + // The max delay is also not reached - do not persist yet + return; + } + } + + // At this point, there is data to persist, and either _reportToPersistenceDelayTime was + // reached, or _reportToPersistenceDelayTimeMax was reached. Time to persist: + [self _persistClusterData]; + + _clusterDataPersistenceFirstScheduledTime = nil; +} + +#ifdef DEBUG +- (void)unitTestSetMostRecentReportTimes:(NSMutableArray *)mostRecentReportTimes +{ + _mostRecentReportTimes = mostRecentReportTimes; +} +#endif + +- (void)_scheduleClusterDataPersistence +{ + os_unfair_lock_assert_owner(&self->_lock); + + // No persisted data / lack of controller data store + if (![self _dataStoreExists]) { + MTR_LOG_DEBUG("%@ storage behavior: no data store", self); + return; + } + + // Nothing to persist + if (!_clusterDataToPersist.count) { + MTR_LOG_DEBUG("%@ storage behavior: nothing to persist", self); + return; + } + + // If there is no storage behavior configuration, make a default one + if (!_storageBehaviorConfiguration) { + _storageBehaviorConfiguration = [[MTRDeviceStorageBehaviorConfiguration alloc] init]; + [_storageBehaviorConfiguration checkValuesAndResetToDefaultIfNecessary]; + } + + // Directly store if the storage behavior optimization is disabled + if (_storageBehaviorConfiguration.disableStorageBehaviorOptimization) { + [self _persistClusterData]; + return; + } + + // If we have nothing stored at all yet, store directly, so we move into a + // primed state. + if (!_deviceCachePrimed) { + [self _persistClusterData]; + return; + } + + // Ensure there is an array to keep the most recent report times + if (!_mostRecentReportTimes) { + _mostRecentReportTimes = [NSMutableArray array]; + } + + // Mark when first report comes in to know when _reportToPersistenceDelayTimeMax is hit + if (!_clusterDataPersistenceFirstScheduledTime) { + _clusterDataPersistenceFirstScheduledTime = [NSDate now]; + } + + // Make sure there is space in the array, and note report time + while (_mostRecentReportTimes.count >= _storageBehaviorConfiguration.recentReportTimesMaxCount) { + [_mostRecentReportTimes removeObjectAtIndex:0]; + } + [_mostRecentReportTimes addObject:[NSDate now]]; + + // Calculate running average and update multiplier - need at least 2 items to calculate intervals + if (_mostRecentReportTimes.count > 2) { + NSTimeInterval cumulativeIntervals = 0; + for (int i = 1; i < _mostRecentReportTimes.count; i++) { + NSDate * lastDate = [_mostRecentReportTimes objectAtIndex:i - 1]; + NSDate * currentDate = [_mostRecentReportTimes objectAtIndex:i]; + NSTimeInterval intervalSinceLastReport = [currentDate timeIntervalSinceDate:lastDate]; + // Check to guard against clock change + if (intervalSinceLastReport > 0) { + cumulativeIntervals += intervalSinceLastReport; + } + } + NSTimeInterval averageTimeBetweenReports = cumulativeIntervals / (_mostRecentReportTimes.count - 1); + + if (averageTimeBetweenReports < _storageBehaviorConfiguration.timeBetweenReportsTooShortThreshold) { + // Multiplier goes from 1 to _reportToPersistenceDelayMaxMultiplier uniformly, as + // averageTimeBetweenReports go from timeBetweenReportsTooShortThreshold to + // timeBetweenReportsTooShortMinThreshold + + double intervalAmountBelowThreshold = _storageBehaviorConfiguration.timeBetweenReportsTooShortThreshold - averageTimeBetweenReports; + double intervalAmountBetweenThresholdAndMinThreshold = _storageBehaviorConfiguration.timeBetweenReportsTooShortThreshold - _storageBehaviorConfiguration.timeBetweenReportsTooShortMinThreshold; + double proportionTowardMinThreshold = intervalAmountBelowThreshold / intervalAmountBetweenThresholdAndMinThreshold; + if (proportionTowardMinThreshold > 1) { + // Clamp to 100% + proportionTowardMinThreshold = 1; + } + + // Set current multiplier to [1, MaxMultiplier] + _reportToPersistenceDelayCurrentMultiplier = 1 + (proportionTowardMinThreshold * (_storageBehaviorConfiguration.reportToPersistenceDelayMaxMultiplier - 1)); + MTR_LOG("%@ storage behavior: device reporting frequently - setting delay multiplier to %lf", self, _reportToPersistenceDelayCurrentMultiplier); + } else { + _reportToPersistenceDelayCurrentMultiplier = 1; + } + + // Also note when the running average first dips below the min threshold + if (averageTimeBetweenReports < _storageBehaviorConfiguration.timeBetweenReportsTooShortMinThreshold) { + if (!_deviceReportingExcessivelyStartTime) { + _deviceReportingExcessivelyStartTime = [NSDate now]; + MTR_LOG_DEBUG("%@ storage behavior: device is reporting excessively @%@", self, _deviceReportingExcessivelyStartTime); + } + } else { + _deviceReportingExcessivelyStartTime = nil; + } + } + + // Do not schedule persistence if device is reporting excessively + if ([self _deviceIsReportingExcessively]) { + return; + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) ([self _reportToPersistenceDelayTimeAfterMutiplier] * NSEC_PER_SEC)), self.queue, ^{ + [self _persistClusterDataAsNeeded]; + }); +} + +// Used to clear the storage behavior state when needed (system time change, or when new +// configuration is set. +// +// Also flushes unwritten cluster data to storage, if data store exists. +- (void)_resetStorageBehaviorState +{ + os_unfair_lock_assert_owner(&self->_lock); + + _clusterDataPersistenceFirstScheduledTime = nil; + _mostRecentReportTimes = nil; + _deviceReportingExcessivelyStartTime = nil; + _reportToPersistenceDelayCurrentMultiplier = 1; + + // Sanity check that there is a data + if ([self _dataStoreExists]) { + [self _persistClusterData]; + } +} + +- (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration +{ + MTR_LOG("%@ storage behavior: setStorageBehaviorConfiguration %@", self, storageBehaviorConfiguration); + std::lock_guard lock(_lock); + _storageBehaviorConfiguration = storageBehaviorConfiguration; + // Make sure the values are sane + [_storageBehaviorConfiguration checkValuesAndResetToDefaultIfNecessary]; + [self _resetStorageBehaviorState]; +} + +- (void)_handleReportEnd +{ + std::lock_guard lock(_lock); + _receivingReport = NO; + _receivingPrimingReport = NO; + _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; + + [self _scheduleClusterDataPersistence]; + + // After the handling of the report, if we detected a device configuration change, notify the delegate + // of the same. + if (_deviceConfigurationChanged) { + [self _callDelegatesWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(deviceConfigurationChanged:)]) { + [delegate deviceConfigurationChanged:self]; + } + }]; + _deviceConfigurationChanged = NO; + } + + // Do this after the _deviceConfigurationChanged check, so that we don't + // call deviceConfigurationChanged: immediately after telling our delegate + // we are now primed. + // + // TODO: Maybe we shouldn't dispatch deviceConfigurationChanged: for the + // initial priming bits? + if (!_deviceCachePrimed) { + // This is the end of the priming sequence of data reports, so we have + // all the data for the device now. + _deviceCachePrimed = YES; + [self _callDelegateDeviceCachePrimed]; + } + +// For unit testing only +#ifdef DEBUG + [self _callDelegatesWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestReportEndForDevice:)]) { + [testDelegate unitTestReportEndForDevice:self]; + } + }]; +#endif +} + +- (BOOL)_interestedPaths:(NSArray * _Nullable)interestedPaths includesAttributePath:(MTRAttributePath *)attributePath +{ + for (id interestedPath in interestedPaths) { + if ([interestedPath isKindOfClass:[NSNumber class]]) { + NSNumber * interestedEndpointIDNumber = interestedPath; + if ([interestedEndpointIDNumber isEqualToNumber:attributePath.endpoint]) { + return YES; + } + } else if ([interestedPath isKindOfClass:[MTRClusterPath class]]) { + MTRClusterPath * interestedClusterPath = interestedPath; + if ([interestedClusterPath.cluster isEqualToNumber:attributePath.cluster]) { + return YES; + } + } else if ([interestedPath isKindOfClass:[MTRAttributePath class]]) { + MTRAttributePath * interestedAttributePath = interestedPath; + if (([interestedAttributePath.cluster isEqualToNumber:attributePath.cluster]) && ([interestedAttributePath.attribute isEqualToNumber:attributePath.attribute])) { + return YES; + } + } + } + + return NO; +} + +// Returns filtered set of attributes using an interestedPaths array. +// Returns nil if no attribute report has a path that matches the paths in the interestedPaths array. +- (NSArray *> *)_filteredAttributes:(NSArray *> *)attributes forInterestedPaths:(NSArray * _Nullable)interestedPaths +{ + if (!interestedPaths) { + return attributes; + } + + if (!interestedPaths.count) { + return nil; + } + + NSMutableArray * filteredAttributes = nil; + for (NSDictionary * responseValue in attributes) { + MTRAttributePath * attributePath = responseValue[MTRAttributePathKey]; + if ([self _interestedPaths:interestedPaths includesAttributePath:attributePath]) { + if (!filteredAttributes) { + filteredAttributes = [NSMutableArray array]; + } + [filteredAttributes addObject:responseValue]; + } + } + + if (filteredAttributes.count && (filteredAttributes.count != attributes.count)) { + MTR_LOG("%@ filtered attribute report %lu => %lu", self, static_cast(attributes.count), static_cast(filteredAttributes.count)); + } + + return filteredAttributes; +} + +// assume lock is held +- (void)_reportAttributes:(NSArray *> *)attributes +{ + os_unfair_lock_assert_owner(&self->_lock); + if (attributes.count) { + [self _iterateDelegatesWithBlock:^(MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo) { + // _iterateDelegatesWithBlock calls this with an autorelease pool, and so temporary filtered attributes reports don't bloat memory + NSArray *> * filteredAttributes = [self _filteredAttributes:attributes forInterestedPaths:delegateInfo.interestedPathsForAttributes]; + if (filteredAttributes.count) { + [delegateInfo callDelegateWithBlock:^(id delegate) { + [delegate device:self receivedAttributeReport:filteredAttributes]; + }]; + } + }]; + } +} + +- (void)_handleAttributeReport:(NSArray *> *)attributeReport fromSubscription:(BOOL)isFromSubscription +{ + std::lock_guard lock(_lock); + + // _getAttributesToReportWithReportedValues will log attribute paths reported + [self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeReport fromSubscription:isFromSubscription]]; +} + +#ifdef DEBUG +- (void)unitTestInjectEventReport:(NSArray *> *)eventReport +{ + dispatch_async(self.queue, ^{ + [self _handleEventReport:eventReport]; + }); +} + +- (void)unitTestInjectAttributeReport:(NSArray *> *)attributeReport fromSubscription:(BOOL)isFromSubscription +{ + dispatch_async(self.queue, ^{ + [self _handleReportBegin]; + [self _handleAttributeReport:attributeReport fromSubscription:isFromSubscription]; + [self _handleReportEnd]; + }); +} +#endif + +- (BOOL)_interestedPaths:(NSArray * _Nullable)interestedPaths includesEventPath:(MTREventPath *)eventPath +{ + for (id interestedPath in interestedPaths) { + if ([interestedPath isKindOfClass:[NSNumber class]]) { + NSNumber * interestedEndpointIDNumber = interestedPath; + if ([interestedEndpointIDNumber isEqualToNumber:eventPath.endpoint]) { + return YES; + } + } else if ([interestedPath isKindOfClass:[MTRClusterPath class]]) { + MTRClusterPath * interestedClusterPath = interestedPath; + if ([interestedClusterPath.cluster isEqualToNumber:eventPath.cluster]) { + return YES; + } + } else if ([interestedPath isKindOfClass:[MTREventPath class]]) { + MTREventPath * interestedEventPath = interestedPath; + if (([interestedEventPath.cluster isEqualToNumber:eventPath.cluster]) && ([interestedEventPath.event isEqualToNumber:eventPath.event])) { + return YES; + } + } + } + + return NO; +} + +// Returns filtered set of events using an interestedPaths array. +// Returns nil if no event report has a path that matches the paths in the interestedPaths array. +- (NSArray *> *)_filteredEvents:(NSArray *> *)events forInterestedPaths:(NSArray * _Nullable)interestedPaths +{ + if (!interestedPaths) { + return events; + } + + if (!interestedPaths.count) { + return nil; + } + + NSMutableArray * filteredEvents = nil; + for (NSDictionary * responseValue in events) { + MTREventPath * eventPath = responseValue[MTREventPathKey]; + if ([self _interestedPaths:interestedPaths includesEventPath:eventPath]) { + if (!filteredEvents) { + filteredEvents = [NSMutableArray array]; + } + [filteredEvents addObject:responseValue]; + } + } + + if (filteredEvents.count && (filteredEvents.count != events.count)) { + MTR_LOG("%@ filtered event report %lu => %lu", self, static_cast(events.count), static_cast(filteredEvents.count)); + } + + return filteredEvents; +} + +- (void)_handleEventReport:(NSArray *> *)eventReport +{ + std::lock_guard lock(_lock); + + NSDate * oldEstimatedStartTime = _estimatedStartTime; + // Combine with previous unreported events, if they exist + NSMutableArray * reportToReturn; + if (_unreportedEvents) { + reportToReturn = _unreportedEvents; + } else { + reportToReturn = [NSMutableArray array]; + } + for (NSDictionary * eventDict in eventReport) { + // Whenever a StartUp event is received, reset the estimated start time + // New subscription case + // - Starts Unreachable + // - Start CASE and send subscription request + // - Receive priming report ReportBegin + // - Optionally receive UpTime attribute - update time and save start time estimate + // - Optionally receive StartUp event + // - Set estimated system time from event receipt time, or saved UpTime estimate if exists + // - ReportEnd handler clears the saved start time estimate based on UpTime + // Subscription dropped from client point of view case + // - Starts Unreachable + // - Resubscribe happens after some time, and then same as the above + // Server resuming subscription after reboot case + // - Starts Reachable + // - Receive priming report ReportBegin + // - Optionally receive UpTime attribute - update time and save value + // - Optionally receive StartUp event + // - Set estimated system time from event receipt time, or saved UpTime estimate if exists + // - ReportEnd handler clears the saved start time estimate based on UpTime + // Server resuming subscription after timeout case + // - Starts Reachable + // - Receive priming report ReportBegin + // - Optionally receive UpTime attribute - update time and save value + // - ReportEnd handler clears the saved start time estimate based on UpTime + MTREventPath * eventPath = eventDict[MTREventPathKey]; + BOOL isStartUpEvent = (eventPath.cluster.unsignedLongValue == MTRClusterIDTypeBasicInformationID) + && (eventPath.event.unsignedLongValue == MTREventIDTypeClusterBasicInformationEventStartUpID); + if (isStartUpEvent) { + if (_estimatedStartTimeFromGeneralDiagnosticsUpTime) { + // If UpTime was received, make use of it as mark of system start time + MTR_LOG("%@ StartUp event: set estimated start time forward to %@", self, + _estimatedStartTimeFromGeneralDiagnosticsUpTime); + _estimatedStartTime = _estimatedStartTimeFromGeneralDiagnosticsUpTime; + } else { + // If UpTime was not received, reset estimated start time in case of reboot + MTR_LOG("%@ StartUp event: set estimated start time to nil", self); + _estimatedStartTime = nil; + } + } + + // If event time is of MTREventTimeTypeSystemUpTime type, then update estimated start time as needed + NSNumber * eventTimeTypeNumber = eventDict[MTREventTimeTypeKey]; + if (!eventTimeTypeNumber) { + MTR_LOG_ERROR("%@ Event %@ missing event time type", self, eventDict); + continue; + } + MTREventTimeType eventTimeType = (MTREventTimeType) eventTimeTypeNumber.unsignedIntegerValue; + if (eventTimeType == MTREventTimeTypeSystemUpTime) { + NSNumber * eventTimeValueNumber = eventDict[MTREventSystemUpTimeKey]; + if (!eventTimeValueNumber) { + MTR_LOG_ERROR("%@ Event %@ missing event time value", self, eventDict); + continue; + } + NSTimeInterval eventTimeValue = eventTimeValueNumber.doubleValue; + NSDate * potentialSystemStartTime = [NSDate dateWithTimeIntervalSinceNow:-eventTimeValue]; + if (!_estimatedStartTime || ([potentialSystemStartTime compare:_estimatedStartTime] == NSOrderedAscending)) { + _estimatedStartTime = potentialSystemStartTime; + } + } + + NSMutableDictionary * eventToReturn = eventDict.mutableCopy; + if (_receivingPrimingReport) { + eventToReturn[MTREventIsHistoricalKey] = @(YES); + } else { + eventToReturn[MTREventIsHistoricalKey] = @(NO); + } + + [reportToReturn addObject:eventToReturn]; + } + if (oldEstimatedStartTime != _estimatedStartTime) { + MTR_LOG("%@ updated estimated start time to %@", self, _estimatedStartTime); + } + + __block BOOL delegatesCalled = NO; + [self _iterateDelegatesWithBlock:^(MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo) { + // _iterateDelegatesWithBlock calls this with an autorelease pool, and so temporary filtered event reports don't bloat memory + NSArray *> * filteredEvents = [self _filteredEvents:reportToReturn forInterestedPaths:delegateInfo.interestedPathsForEvents]; + if (filteredEvents.count) { + [delegateInfo callDelegateWithBlock:^(id delegate) { + [delegate device:self receivedEventReport:filteredEvents]; + }]; + delegatesCalled = YES; + } + }]; + if (delegatesCalled) { + _unreportedEvents = nil; + } else { + // save unreported events + _unreportedEvents = reportToReturn; + } +} + +#ifdef DEBUG +- (void)unitTestClearClusterData +{ + std::lock_guard lock(_lock); + NSAssert([self _dataStoreExists], @"Test is not going to test what it thinks is testing!"); + [_persistedClusterData removeAllObjects]; +} +#endif + +- (void)_reconcilePersistedClustersWithStorage +{ + os_unfair_lock_assert_owner(&self->_lock); + + NSMutableSet * clusterPathsToRemove = [NSMutableSet set]; + for (MTRClusterPath * clusterPath in _persistedClusters) { + MTRDeviceClusterData * data = [_deviceController.controllerDataStore getStoredClusterDataForNodeID:_nodeID endpointID:clusterPath.endpoint clusterID:clusterPath.cluster]; + if (!data) { + [clusterPathsToRemove addObject:clusterPath]; + } + } + + MTR_LOG_ERROR("%@ Storage missing %lu / %lu clusters - reconciling in-memory records", self, static_cast(clusterPathsToRemove.count), static_cast(_persistedClusters.count)); + [_persistedClusters minusSet:clusterPathsToRemove]; +} + +- (nullable MTRDeviceClusterData *)_clusterDataForPath:(MTRClusterPath *)clusterPath +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (_clusterDataToPersist != nil) { + // Use the "dirty" values, if we have them. + MTRDeviceClusterData * data = _clusterDataToPersist[clusterPath]; + if (data != nil) { + return data; + } + } + + if ([self _dataStoreExists]) { + MTRDeviceClusterData * data = [_persistedClusterData objectForKey:clusterPath]; + if (data != nil) { + return data; + } + } + + if (![_persistedClusters containsObject:clusterPath]) { + // We are not expected to have this cluster, so no point in paging it in + // loading it from storage. + return nil; + } + + NSAssert(_deviceController.controllerDataStore != nil, + @"How can _persistedClusters have an entry if we have no persistence?"); + NSAssert(_persistedClusterData != nil, + @"How can _persistedClusterData not exist if we have persisted clusters?"); + + // Page in the stored value for the data. + MTRDeviceClusterData * data = [_deviceController.controllerDataStore getStoredClusterDataForNodeID:_nodeID endpointID:clusterPath.endpoint clusterID:clusterPath.cluster]; + MTR_LOG("%@ cluster path %@ cache miss - load from storage success %@", self, clusterPath, YES_NO(data)); + if (data != nil) { + [_persistedClusterData setObject:data forKey:clusterPath]; + } else { + // If clusterPath is in _persistedClusters and the data store returns nil for it, then the in-memory cache is now not dependable, and subscription should be reset and reestablished to reload cache from device + + // First make sure _persistedClusters is consistent with storage, so repeated calls don't immediately re-trigger this + [self _reconcilePersistedClustersWithStorage]; + + [self _resetSubscriptionWithReasonString:[NSString stringWithFormat:@"Data store has no data for cluster %@", clusterPath]]; + } + + return data; +} + +- (NSSet *)_knownClusters +{ + os_unfair_lock_assert_owner(&self->_lock); + + // We might have some clusters that have not been persisted at all yet, and + // some that have been persisted but are still present in + // _clusterDataToPersist because they have been modified since then. + NSMutableSet * clusterPaths = [_persistedClusters mutableCopy]; + if (_clusterDataToPersist != nil) { + [clusterPaths unionSet:[NSSet setWithArray:[_clusterDataToPersist allKeys]]]; + } + return clusterPaths; +} + +- (NSDictionary *)_getCachedDataVersions +{ + NSMutableDictionary * dataVersions = [NSMutableDictionary dictionary]; + std::lock_guard lock(_lock); + + for (MTRClusterPath * path in [self _knownClusters]) { + dataVersions[path] = [self _clusterDataForPath:path].dataVersion; + } + + MTR_LOG_DEBUG("%@ _getCachedDataVersions dataVersions count: %lu", self, static_cast(dataVersions.count)); + + return dataVersions; +} + +- (MTRDeviceDataValueDictionary _Nullable)_cachedAttributeValueForPath:(MTRAttributePath *)path +{ + os_unfair_lock_assert_owner(&self->_lock); + + // We need an actual MTRClusterPath, not a subsclass, to do _clusterDataForPath. + auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster]; + + MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; + if (clusterData == nil) { + return nil; + } + + return clusterData.attributes[path.attribute]; +} + +- (void)_setCachedAttributeValue:(MTRDeviceDataValueDictionary _Nullable)value forPath:(MTRAttributePath *)path fromSubscription:(BOOL)isFromSubscription +{ + os_unfair_lock_assert_owner(&self->_lock); + + // We need an actual MTRClusterPath, not a subclass, to do _clusterDataForPath. + auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster]; + + MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; + if (clusterData == nil) { + if (value == nil) { + // Nothing to do. + return; + } + + clusterData = [[MTRDeviceClusterData alloc] init]; + } + + [clusterData storeValue:value forAttribute:path.attribute]; + + if (value != nil + && isFromSubscription + && !_receivingPrimingReport + && AttributeHasChangesOmittedQuality(path)) { + // Do not persist new values for Changes Omitted Quality (aka C Quality) + // attributes unless they're part of a Priming Report or from a read response. + // (removals are OK) + + // log when a device violates expectations for Changes Omitted Quality attributes. + using namespace chip::Tracing::DarwinFramework; + MATTER_LOG_METRIC_BEGIN(kMetricUnexpectedCQualityUpdate); + [self _addInformationalAttributesToCurrentMetricScope]; + MATTER_LOG_METRIC_END(kMetricUnexpectedCQualityUpdate); + + return; + } + + if (_clusterDataToPersist == nil) { + _clusterDataToPersist = [NSMutableDictionary dictionary]; + } + _clusterDataToPersist[clusterPath] = clusterData; +} + +- (void)_removeCachedAttribute:(NSNumber *)attributeID fromCluster:(MTRClusterPath *)clusterPath +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (_clusterDataToPersist == nil) { + return; + } + auto * clusterData = _clusterDataToPersist[clusterPath]; + [clusterData removeValueForAttribute:attributeID]; +} + +- (void)_createDataVersionFilterListFromDictionary:(NSDictionary *)dataVersions dataVersionFilterList:(DataVersionFilter **)dataVersionFilterList count:(size_t *)count +{ + size_t dataVersionFilterSize = dataVersions.count; + + // Check if any filter list should be generated + if (dataVersionFilterSize == 0) { + *count = 0; + *dataVersionFilterList = nullptr; + return; + } + + DataVersionFilter * dataVersionFilterArray = new DataVersionFilter[dataVersionFilterSize]; + size_t i = 0; + for (MTRClusterPath * path in dataVersions) { + NSNumber * dataVersionNumber = dataVersions[path]; + dataVersionFilterArray[i++] = DataVersionFilter(static_cast(path.endpoint.unsignedShortValue), static_cast(path.cluster.unsignedLongValue), static_cast(dataVersionNumber.unsignedLongValue)); + } + + *dataVersionFilterList = dataVersionFilterArray; + *count = dataVersionFilterSize; +} + +- (void)_setupConnectivityMonitoring +{ +#if ENABLE_CONNECTIVITY_MONITORING + // Dispatch to own queue first to avoid deadlock with syncGetCompressedFabricID + dispatch_async(self.queue, ^{ + // Get the required info before setting up the connectivity monitor + NSNumber * compressedFabricID = [self->_deviceController syncGetCompressedFabricID]; + if (!compressedFabricID) { + MTR_LOG_ERROR("%@ could not get compressed fabricID", self); + return; + } + + // Now lock for _connectivityMonitor + std::lock_guard lock(self->_lock); + if (self->_connectivityMonitor) { + // already monitoring + return; + } + + self->_connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithCompressedFabricID:compressedFabricID nodeID:self.nodeID]; + [self->_connectivityMonitor startMonitoringWithHandler:^{ + [self->_deviceController asyncDispatchToMatterQueue:^{ + [self _triggerResubscribeWithReason:@"device connectivity changed" nodeLikelyReachable:YES]; + } + errorHandler:nil]; + } queue:self.queue]; + }); +#endif +} + +- (void)_stopConnectivityMonitoring +{ + os_unfair_lock_assert_owner(&_lock); + + if (_connectivityMonitor) { + [_connectivityMonitor stopMonitoring]; + _connectivityMonitor = nil; + } +} + +- (void)_resetSubscriptionWithReasonString:(NSString *)reasonString +{ + os_unfair_lock_assert_owner(&self->_lock); + MTR_LOG_ERROR("%@ %@ - resetting subscription", self, reasonString); + + [_deviceController asyncDispatchToMatterQueue:^{ + MTR_LOG("%@ subscription reset disconnecting ReadClient and SubscriptionCallback", self); + + std::lock_guard lock(self->_lock); + self->_currentReadClient = nullptr; + if (self->_currentSubscriptionCallback) { + delete self->_currentSubscriptionCallback; + } + self->_currentSubscriptionCallback = nullptr; + + [self _doHandleSubscriptionError:nil]; + // Use nil reset delay so that this keeps existing backoff timing + [self _doHandleSubscriptionReset:nil]; + } + errorHandler:nil]; +} + +#ifdef DEBUG +- (void)unitTestResetSubscription +{ + std::lock_guard lock(self->_lock); + [self _resetSubscriptionWithReasonString:@"Unit test reset subscription"]; +} +#endif + +// assume lock is held +- (void)_setupSubscriptionWithReason:(NSString *)reason +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (![self _subscriptionsAllowed]) { + MTR_LOG("%@ _setupSubscription: Subscriptions not allowed. Do not set up subscription (reason: %@)", self, reason); + return; + } + +#ifdef DEBUG + __block NSNumber * delegateMin = nil; + Optional maxIntervalOverride; + [self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestMaxIntervalOverrideForSubscription:)]) { + delegateMin = [testDelegate unitTestMaxIntervalOverrideForSubscription:self]; + } + }]; + if (delegateMin) { + maxIntervalOverride.Emplace(delegateMin.unsignedIntValue); + } +#endif + + // for now just subscribe once + if (!NeedToStartSubscriptionSetup(_internalDeviceState)) { + MTR_LOG("%@ setupSubscription: no need to subscribe due to internal state %lu (reason: %@)", self, static_cast(_internalDeviceState), reason); + return; + } + + [self _changeInternalState:MTRInternalDeviceStateSubscribing]; + + MTR_LOG("%@ setting up subscription with reason: %@", self, reason); + + __block bool markUnreachableAfterWait = true; +#ifdef DEBUG + [self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) { + if ([testDelegate respondsToSelector:@selector(unitTestSuppressTimeBasedReachabilityChanges:)]) { + markUnreachableAfterWait = ![testDelegate unitTestSuppressTimeBasedReachabilityChanges:self]; + } + }]; +#endif + + if (markUnreachableAfterWait) { + // Set up a timer to mark as not reachable if it takes too long to set up a subscription + mtr_weakify(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, static_cast(kSecondsToWaitBeforeMarkingUnreachableAfterSettingUpSubscription) * static_cast(NSEC_PER_SEC)), self.queue, ^{ + mtr_strongify(self); + if (self != nil) { + std::lock_guard lock(self->_lock); + [self _markDeviceAsUnreachableIfNeverSubscribed]; + } + }); + } + + // This marks begin of initial subscription to the device (before CASE is established). The end is only marked after successfully setting + // up the subscription since it is always retried as long as the MTRDevice is kept running. + MATTER_LOG_METRIC_BEGIN(kMetricMTRDeviceInitialSubscriptionSetup); + + // Call directlyGetSessionForNode because the subscription setup already goes through the subscription pool queue + [_deviceController + directlyGetSessionForNode:_nodeID.unsignedLongLongValue + completion:^(chip::Messaging::ExchangeManager * _Nullable exchangeManager, + const chip::Optional & session, NSError * _Nullable error, + NSNumber * _Nullable retryDelay) { + if (error != nil) { + MTR_LOG_ERROR("%@ getSessionForNode error %@", self, error); + dispatch_async(self.queue, ^{ + [self _handleSubscriptionError:error]; + [self _handleSubscriptionReset:retryDelay]; + }); + return; + } + + auto callback = std::make_unique( + ^(NSArray * value) { + MTR_LOG("%@ got attribute report %@", self, value); + dispatch_async(self.queue, ^{ + // OnAttributeData + [self _handleAttributeReport:value fromSubscription:YES]; +#ifdef DEBUG + self->_unitTestAttributesReportedSinceLastCheck += value.count; +#endif + }); + }, + ^(NSArray * value) { + MTR_LOG("%@ got event report %@", self, value); + dispatch_async(self.queue, ^{ + // OnEventReport + [self _handleEventReport:value]; + }); + }, + ^(NSError * error) { + MTR_LOG_ERROR("%@ got subscription error %@", self, error); + dispatch_async(self.queue, ^{ + // OnError + [self _handleSubscriptionError:error]; + }); + }, + ^(NSError * error, NSNumber * resubscriptionDelayMs) { + MTR_LOG_ERROR("%@ got resubscription error %@ delay %@", self, error, resubscriptionDelayMs); + dispatch_async(self.queue, ^{ + // OnResubscriptionNeeded + [self _handleResubscriptionNeededWithDelay:resubscriptionDelayMs]; + }); + }, + ^(void) { + MTR_LOG("%@ got subscription established", self); + std::lock_guard lock(self->_lock); + + // First synchronously change state + if (HadSubscriptionEstablishedOnce(self->_internalDeviceState)) { + [self _changeInternalState:MTRInternalDeviceStateLaterSubscriptionEstablished]; + } else { + MATTER_LOG_METRIC_END(kMetricMTRDeviceInitialSubscriptionSetup, CHIP_NO_ERROR); + [self _changeInternalState:MTRInternalDeviceStateInitialSubscriptionEstablished]; + } + + [self _changeState:MTRDeviceStateReachable]; + + // Then async work that shouldn't be performed on the matter queue + dispatch_async(self.queue, ^{ + // OnSubscriptionEstablished + [self _handleSubscriptionEstablished]; + }); + }, + ^(void) { + MTR_LOG("%@ got subscription done", self); + // Drop our pointer to the ReadClient immediately, since + // it's about to be destroyed and we don't want to be + // holding a dangling pointer. + std::lock_guard lock(self->_lock); + self->_currentReadClient = nullptr; + self->_currentSubscriptionCallback = nullptr; + + dispatch_async(self.queue, ^{ + // OnDone + [self _handleSubscriptionReset:nil]; + }); + }, + ^(void) { + MTR_LOG("%@ got unsolicited message from publisher", self); + dispatch_async(self.queue, ^{ + // OnUnsolicitedMessageFromPublisher + [self _handleUnsolicitedMessageFromPublisher]; + }); + }, + ^(void) { + MTR_LOG("%@ got report begin", self); + dispatch_async(self.queue, ^{ + [self _handleReportBegin]; + }); + }, + ^(void) { + MTR_LOG("%@ got report end", self); + dispatch_async(self.queue, ^{ + [self _handleReportEnd]; + }); + }); + + // Set up a cluster state cache. We just want this for the logic it has for + // tracking data versions and event numbers so we minimize the amount of data we + // request on resubscribes, so tell it not to store data. + auto clusterStateCache = std::make_unique(*callback.get(), + /* highestReceivedEventNumber = */ NullOptional, + /* cacheData = */ false); + auto readClient = std::make_unique(InteractionModelEngine::GetInstance(), exchangeManager, + clusterStateCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); + + // Wildcard endpoint, cluster, attribute, event. + auto attributePath = std::make_unique(); + auto eventPath = std::make_unique(); + // We want to get event reports at the minInterval, not the maxInterval. + eventPath->mIsUrgentEvent = true; + ReadPrepareParams readParams(session.Value()); + + readParams.mMinIntervalFloorSeconds = 0; + // Select a max interval based on the device's claimed idle sleep interval. + auto idleSleepInterval = std::chrono::duration_cast( + session.Value()->GetRemoteMRPConfig().mIdleRetransTimeout); + + auto maxIntervalCeilingMin = System::Clock::Seconds32(MTR_DEVICE_SUBSCRIPTION_MAX_INTERVAL_MIN); + if (idleSleepInterval < maxIntervalCeilingMin) { + idleSleepInterval = maxIntervalCeilingMin; + } + + auto maxIntervalCeilingMax = System::Clock::Seconds32(MTR_DEVICE_SUBSCRIPTION_MAX_INTERVAL_MAX); + if (idleSleepInterval > maxIntervalCeilingMax) { + idleSleepInterval = maxIntervalCeilingMax; + } +#ifdef DEBUG + if (maxIntervalOverride.HasValue()) { + idleSleepInterval = maxIntervalOverride.Value(); + } +#endif + readParams.mMaxIntervalCeilingSeconds = static_cast(idleSleepInterval.count()); + + readParams.mpAttributePathParamsList = attributePath.get(); + readParams.mAttributePathParamsListSize = 1; + readParams.mpEventPathParamsList = eventPath.get(); + readParams.mEventPathParamsListSize = 1; + readParams.mKeepSubscriptions = true; + readParams.mIsFabricFiltered = false; + + // Subscribe with data version filter list from our cache. + size_t dataVersionFilterListSize = 0; + DataVersionFilter * dataVersionFilterList; + [self _createDataVersionFilterListFromDictionary:[self _getCachedDataVersions] dataVersionFilterList:&dataVersionFilterList count:&dataVersionFilterListSize]; + + readParams.mDataVersionFilterListSize = dataVersionFilterListSize; + readParams.mpDataVersionFilterList = dataVersionFilterList; + attributePath.release(); + eventPath.release(); + + // TODO: Change from local filter list generation to rehydrating ClusterStateCache to take advantage of existing filter list sorting algorithm + + // SendAutoResubscribeRequest cleans up the params, even on failure. + CHIP_ERROR err = readClient->SendAutoResubscribeRequest(std::move(readParams)); + if (err != CHIP_NO_ERROR) { + NSError * error = [MTRError errorForCHIPErrorCode:err logContext:self]; + MTR_LOG_ERROR("%@ SendAutoResubscribeRequest error %@", self, error); + dispatch_async(self.queue, ^{ + [self _handleSubscriptionError:error]; + [self _handleSubscriptionReset:nil]; + }); + + return; + } + + MTR_LOG("%@ Subscribe with data version list size %lu", self, static_cast(dataVersionFilterListSize)); + + // Callback and ClusterStateCache and ReadClient will be deleted + // when OnDone is called. + os_unfair_lock_lock(&self->_lock); + self->_currentReadClient = readClient.get(); + self->_currentSubscriptionCallback = callback.get(); + os_unfair_lock_unlock(&self->_lock); + callback->AdoptReadClient(std::move(readClient)); + callback->AdoptClusterStateCache(std::move(clusterStateCache)); + callback.release(); + }]; + + // Set up connectivity monitoring in case network becomes routable after any part of the subscription process goes into backoff retries. + [self _setupConnectivityMonitoring]; +} + +#ifdef DEBUG +- (NSUInteger)unitTestAttributesReportedSinceLastCheck +{ + NSUInteger attributesReportedSinceLastCheck = _unitTestAttributesReportedSinceLastCheck; + _unitTestAttributesReportedSinceLastCheck = 0; + return attributesReportedSinceLastCheck; +} + +- (NSUInteger)unitTestNonnullDelegateCount +{ + std::lock_guard lock(self->_lock); + + NSUInteger nonnullDelegateCount = 0; + for (MTRDeviceDelegateInfo_ConcreteCopy * delegateInfo in _delegates) { + if (delegateInfo.delegate) { + nonnullDelegateCount++; + } + } + + return nonnullDelegateCount; +} +#endif + +#pragma mark Device Interactions + +// Helper function to determine whether an attribute has "Changes Omitted" quality, which indicates that past the priming report in +// a subscription, this attribute is not expected to be reported when its value changes +// * TODO: xml+codegen version to replace this hardcoded list. +static BOOL AttributeHasChangesOmittedQuality(MTRAttributePath * attributePath) +{ + switch (attributePath.cluster.unsignedLongValue) { + case MTRClusterEthernetNetworkDiagnosticsID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterEthernetNetworkDiagnosticsAttributePacketRxCountID: + case MTRClusterEthernetNetworkDiagnosticsAttributePacketTxCountID: + case MTRClusterEthernetNetworkDiagnosticsAttributeTxErrCountID: + case MTRClusterEthernetNetworkDiagnosticsAttributeCollisionCountID: + case MTRClusterEthernetNetworkDiagnosticsAttributeOverrunCountID: + case MTRClusterEthernetNetworkDiagnosticsAttributeCarrierDetectID: + case MTRClusterEthernetNetworkDiagnosticsAttributeTimeSinceResetID: + return YES; + default: + return NO; + } + case MTRClusterGeneralDiagnosticsID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterGeneralDiagnosticsAttributeUpTimeID: + case MTRClusterGeneralDiagnosticsAttributeTotalOperationalHoursID: + return YES; + default: + return NO; + } + case MTRClusterThreadNetworkDiagnosticsID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterThreadNetworkDiagnosticsAttributeOverrunCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeDetachedRoleCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeChildRoleCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRouterRoleCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeLeaderRoleCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeAttachAttemptCountID: + case MTRClusterThreadNetworkDiagnosticsAttributePartitionIdChangeCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeBetterPartitionAttachAttemptCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeParentChangeCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxTotalCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxUnicastCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxBroadcastCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxAckRequestedCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxAckedCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxNoAckRequestedCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxDataCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxDataPollCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxBeaconCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxBeaconRequestCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxOtherCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxRetryCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxDirectMaxRetryExpiryCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxIndirectMaxRetryExpiryCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxErrCcaCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxErrAbortCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeTxErrBusyChannelCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxTotalCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxUnicastCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxBroadcastCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxDataCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxDataPollCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxBeaconCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxBeaconRequestCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxOtherCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxAddressFilteredCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxDestAddrFilteredCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxDuplicatedCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxErrNoFrameCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxErrUnknownNeighborCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxErrInvalidSrcAddrCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxErrSecCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxErrFcsCountID: + case MTRClusterThreadNetworkDiagnosticsAttributeRxErrOtherCountID: + return YES; + default: + return NO; + } + case MTRClusterWiFiNetworkDiagnosticsID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterWiFiNetworkDiagnosticsAttributeRssiID: + case MTRClusterWiFiNetworkDiagnosticsAttributeBeaconLostCountID: + case MTRClusterWiFiNetworkDiagnosticsAttributeBeaconRxCountID: + case MTRClusterWiFiNetworkDiagnosticsAttributePacketMulticastRxCountID: + case MTRClusterWiFiNetworkDiagnosticsAttributePacketMulticastTxCountID: + case MTRClusterWiFiNetworkDiagnosticsAttributePacketUnicastRxCountID: + case MTRClusterWiFiNetworkDiagnosticsAttributePacketUnicastTxCountID: + case MTRClusterWiFiNetworkDiagnosticsAttributeCurrentMaxRateID: + case MTRClusterWiFiNetworkDiagnosticsAttributeOverrunCountID: + return YES; + default: + return NO; + } + case MTRClusterOperationalCredentialsID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterOperationalCredentialsAttributeNOCsID: + case MTRClusterOperationalCredentialsAttributeTrustedRootCertificatesID: + return YES; + default: + return NO; + } + case MTRClusterPowerSourceID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterPowerSourceAttributeWiredAssessedInputVoltageID: + case MTRClusterPowerSourceAttributeWiredAssessedInputFrequencyID: + case MTRClusterPowerSourceAttributeWiredAssessedCurrentID: + case MTRClusterPowerSourceAttributeBatVoltageID: + case MTRClusterPowerSourceAttributeBatPercentRemainingID: + case MTRClusterPowerSourceAttributeBatTimeRemainingID: + case MTRClusterPowerSourceAttributeBatTimeToFullChargeID: + case MTRClusterPowerSourceAttributeBatChargingCurrentID: + return YES; + default: + return NO; + } + case MTRClusterTimeSynchronizationID: + switch (attributePath.attribute.unsignedLongValue) { + case MTRClusterTimeSynchronizationAttributeUTCTimeID: + case MTRClusterTimeSynchronizationAttributeLocalTimeID: + return YES; + default: + return NO; + } + default: + return NO; + } +} + +- (NSDictionary * _Nullable)readAttributeWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + attributeID:(NSNumber *)attributeID + params:(MTRReadParams * _Nullable)params +{ + MTRAttributePath * attributePath = [MTRAttributePath attributePathWithEndpointID:endpointID + clusterID:clusterID + attributeID:attributeID]; + + BOOL attributeIsSpecified = MTRAttributeIsSpecified(clusterID.unsignedIntValue, attributeID.unsignedIntValue); + BOOL hasChangesOmittedQuality; + if (attributeIsSpecified) { + hasChangesOmittedQuality = AttributeHasChangesOmittedQuality(attributePath); + } else { + if (params == nil) { + hasChangesOmittedQuality = NO; + } else { + hasChangesOmittedQuality = !params.assumeUnknownAttributesReportable; + } + } + + // Return current known / expected value right away + NSDictionary * attributeValueToReturn = [self _attributeValueDictionaryForAttributePath:attributePath]; + + // Send read request to device if any of the following are true: + // 1. Subscription not in a state we can expect reports + // 2. The attribute has the Changes Omitted quality, so we won't get reports for it. + // 3. The attribute is not in the spec, and the read params asks to assume + // an unknown attribute has the Changes Omitted quality. + if (![self _subscriptionAbleToReport] || hasChangesOmittedQuality) { + // Read requests container will be a mutable array of items, each being an array containing: + // [attribute request path, params] + // Batching handler should only coalesce when params are equal. + + // For this single read API there's only 1 array item. Use NSNull to stand in for nil params for easy comparison. + MTRAttributeRequestPath * readRequestPath = [MTRAttributeRequestPath requestPathWithEndpointID:endpointID + clusterID:clusterID + attributeID:attributeID]; + NSArray * readRequestData = @[ readRequestPath, params ?: [NSNull null] ]; + + // But first, check if a duplicate read request is already queued and return + if ([_asyncWorkQueue hasDuplicateForTypeID:MTRDeviceWorkItemDuplicateReadTypeID workItemData:readRequestData]) { + return attributeValueToReturn; + } + + NSMutableArray * readRequests = [NSMutableArray arrayWithObject:readRequestData]; + + // Create work item, set ready handler to perform task, then enqueue the work + MTRAsyncWorkItem * workItem = [[MTRAsyncWorkItem alloc] initWithQueue:self.queue]; + uint64_t workItemID = workItem.uniqueID; // capture only the ID, not the work item + NSNumber * nodeID = [self nodeID]; + + [workItem setBatchingID:MTRDeviceWorkItemBatchingReadID data:readRequests handler:^(id opaqueDataCurrent, id opaqueDataNext) { + mtr_hide(self); // don't capture self accidentally + NSMutableArray * readRequestsCurrent = opaqueDataCurrent; + NSMutableArray * readRequestsNext = opaqueDataNext; + + MTRBatchingOutcome outcome = MTRNotBatched; + while (readRequestsNext.count) { + // Can only read up to 9 paths at a time, per spec + if (readRequestsCurrent.count >= 9) { + MTR_LOG("Batching read attribute work item [%llu]: cannot add more work, item is full [0x%016llX:%@:0x%llx:0x%llx]", workItemID, nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + return outcome; + } + + // if params don't match then they cannot be merged + if (![readRequestsNext[0][MTRDeviceReadRequestFieldParamsIndex] + isEqual:readRequestsCurrent[0][MTRDeviceReadRequestFieldParamsIndex]]) { + MTR_LOG("Batching read attribute work item [%llu]: cannot add more work, parameter mismatch [0x%016llX:%@:0x%llx:0x%llx]", workItemID, nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + return outcome; + } + + // merge the next item's first request into the current item's list + auto readItem = readRequestsNext.firstObject; + [readRequestsNext removeObjectAtIndex:0]; + [readRequestsCurrent addObject:readItem]; + MTR_LOG("Batching read attribute work item [%llu]: added %@ (now %lu requests total) [0x%016llX:%@:0x%llx:0x%llx]", + workItemID, readItem, static_cast(readRequestsCurrent.count), nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + outcome = MTRBatchedPartially; + } + NSCAssert(readRequestsNext.count == 0, @"should have batched everything or returned early"); + return MTRBatchedFully; + }]; + [workItem setDuplicateTypeID:MTRDeviceWorkItemDuplicateReadTypeID handler:^(id opaqueItemData, BOOL * isDuplicate, BOOL * stop) { + mtr_hide(self); // don't capture self accidentally + for (NSArray * readItem in readRequests) { + if ([readItem isEqual:opaqueItemData]) { + MTR_LOG("Read attribute work item [%llu] report duplicate %@ [0x%016llX:%@:0x%llx:0x%llx]", workItemID, readItem, nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + *isDuplicate = YES; + *stop = YES; + return; + } + } + *stop = NO; + }]; + [workItem setReadyHandler:^(MTRDevice_Concrete * self, NSInteger retryCount, MTRAsyncWorkCompletionBlock completion) { + // Sanity check + if (readRequests.count == 0) { + MTR_LOG_ERROR("Read attribute work item [%llu] contained no read requests", workItemID); + completion(MTRAsyncWorkComplete); + return; + } + + // Build the attribute paths from the read requests + NSMutableArray * attributePaths = [NSMutableArray array]; + for (NSArray * readItem in readRequests) { + NSAssert(readItem.count == 2, @"invalid read attribute item"); + [attributePaths addObject:readItem[MTRDeviceReadRequestFieldPathIndex]]; + } + // If param is the NSNull stand-in, then just use nil + id readParamObject = readRequests[0][MTRDeviceReadRequestFieldParamsIndex]; + MTRReadParams * readParams = (![readParamObject isEqual:[NSNull null]]) ? readParamObject : nil; + + MTRBaseDevice * baseDevice = [self newBaseDevice]; + [baseDevice + readAttributePaths:attributePaths + eventPaths:nil + params:readParams + includeDataVersion:YES + queue:self.queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + if (values) { + // Since the format is the same data-value dictionary, this looks like an + // attribute report + MTR_LOG("Read attribute work item [%llu] result: %@ [0x%016llX:%@:0x%llX:0x%llX]", workItemID, values, nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + [self _handleAttributeReport:values fromSubscription:NO]; + } + + // TODO: better retry logic + if (error && (retryCount < 2)) { + MTR_LOG_ERROR("Read attribute work item [%llu] failed (will retry): %@ [0x%016llX:%@:0x%llx:0x%llx]", workItemID, error, nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + completion(MTRAsyncWorkNeedsRetry); + } else { + if (error) { + MTR_LOG("Read attribute work item [%llu] failed (giving up): %@ [0x%016llX:%@:0x%llx:0x%llx]", workItemID, error, nodeID.unsignedLongLongValue, endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue); + } + completion(MTRAsyncWorkComplete); + } + }]; + }]; + [_asyncWorkQueue enqueueWorkItem:workItem descriptionWithFormat:@"read %@ 0x%llx 0x%llx", endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue]; + } else { + [self _readThroughSkipped]; + } + + return attributeValueToReturn; +} + +- (void)writeAttributeWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + attributeID:(NSNumber *)attributeID + value:(id)value + expectedValueInterval:(NSNumber *)expectedValueInterval + timedWriteTimeout:(NSNumber * _Nullable)timeout +{ + if (timeout) { + timeout = MTRClampedNumber(timeout, @(1), @(UINT16_MAX)); + } + expectedValueInterval = MTRClampedNumber(expectedValueInterval, @(1), @(UINT32_MAX)); + MTRAttributePath * attributePath = [MTRAttributePath attributePathWithEndpointID:endpointID + clusterID:clusterID + + attributeID:attributeID]; + + __block BOOL useValueAsExpectedValue = YES; +#ifdef DEBUG + os_unfair_lock_lock(&self->_lock); + [self _callFirstDelegateSynchronouslyWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(unitTestShouldSkipExpectedValuesForWrite:)]) { + useValueAsExpectedValue = ![delegate unitTestShouldSkipExpectedValuesForWrite:self]; + } + }]; + os_unfair_lock_unlock(&self->_lock); +#endif + + uint64_t expectedValueID = 0; + if (useValueAsExpectedValue) { + // Commit change into expected value cache + NSDictionary * newExpectedValueDictionary = @{ MTRAttributePathKey : attributePath, MTRDataKey : value }; + [self setExpectedValues:@[ newExpectedValueDictionary ] + expectedValueInterval:expectedValueInterval + expectedValueID:&expectedValueID]; + } + + MTRAsyncWorkItem * workItem = [[MTRAsyncWorkItem alloc] initWithQueue:self.queue]; + uint64_t workItemID = workItem.uniqueID; // capture only the ID, not the work item + NSNumber * nodeID = _nodeID; + + // Write request data is an array of items (for now always length 1). Each + // item is an array containing: + // + // [ attribute path, value, timedWriteTimeout, expectedValueID ] + // + // where expectedValueID is stored as NSNumber and NSNull represents nil timeouts + auto * writeData = @[ attributePath, [value copy], timeout ?: [NSNull null], @(expectedValueID) ]; + + NSMutableArray * writeRequests = [NSMutableArray arrayWithObject:writeData]; + + [workItem setBatchingID:MTRDeviceWorkItemBatchingWriteID data:writeRequests handler:^(id opaqueDataCurrent, id opaqueDataNext) { + mtr_hide(self); // don't capture self accidentally + NSMutableArray * writeRequestsCurrent = opaqueDataCurrent; + NSMutableArray * writeRequestsNext = opaqueDataNext; + + if (writeRequestsCurrent.count != 1) { + // Very unexpected! + MTR_LOG_ERROR("Batching write attribute work item [%llu]: Unexpected write request count %lu", workItemID, static_cast(writeRequestsCurrent.count)); + return MTRNotBatched; + } + + MTRBatchingOutcome outcome = MTRNotBatched; + while (writeRequestsNext.count) { + // If paths don't match, we cannot replace the earlier write + // with the later one. + if (![writeRequestsNext[0][MTRDeviceWriteRequestFieldPathIndex] + isEqual:writeRequestsCurrent[0][MTRDeviceWriteRequestFieldPathIndex]]) { + MTR_LOG("Batching write attribute work item [%llu]: cannot replace with next work item due to path mismatch", workItemID); + return outcome; + } + + // Replace our one request with the first one from the next item. + auto writeItem = writeRequestsNext.firstObject; + [writeRequestsNext removeObjectAtIndex:0]; + [writeRequestsCurrent replaceObjectAtIndex:0 withObject:writeItem]; + MTR_LOG("Batching write attribute work item [%llu]: replaced with new write value %@ [0x%016llX]", + workItemID, writeItem, nodeID.unsignedLongLongValue); + outcome = MTRBatchedPartially; + } + NSCAssert(writeRequestsNext.count == 0, @"should have batched everything or returned early"); + return MTRBatchedFully; + }]; + // The write operation will install a duplicate check handler, to return NO for "isDuplicate". Since a write operation may + // change values, only read requests after this should be considered for duplicate requests. + [workItem setDuplicateTypeID:MTRDeviceWorkItemDuplicateReadTypeID handler:^(id opaqueItemData, BOOL * isDuplicate, BOOL * stop) { + *isDuplicate = NO; + *stop = YES; + }]; + [workItem setReadyHandler:^(MTRDevice_Concrete * self, NSInteger retryCount, MTRAsyncWorkCompletionBlock completion) { + MTRBaseDevice * baseDevice = [self newBaseDevice]; + // Make sure to use writeRequests here, because that's what our batching + // handler will modify as needed. + NSCAssert(writeRequests.count == 1, @"Incorrect number of write requests: %lu", static_cast(writeRequests.count)); + + auto * request = writeRequests[0]; + MTRAttributePath * path = request[MTRDeviceWriteRequestFieldPathIndex]; + + id timedWriteTimeout = request[MTRDeviceWriteRequestFieldTimeoutIndex]; + if (timedWriteTimeout == [NSNull null]) { + timedWriteTimeout = nil; + } + + [baseDevice + writeAttributeWithEndpointID:path.endpoint + clusterID:path.cluster + attributeID:path.attribute + value:request[MTRDeviceWriteRequestFieldValueIndex] + timedWriteTimeout:timedWriteTimeout + queue:self.queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + if (error) { + MTR_LOG_ERROR("Write attribute work item [%llu] failed: %@", workItemID, error); + if (useValueAsExpectedValue) { + NSNumber * expectedValueID = request[MTRDeviceWriteRequestFieldExpectedValueIDIndex]; + [self removeExpectedValueForAttributePath:attributePath expectedValueID:expectedValueID.unsignedLongLongValue]; + } + } + completion(MTRAsyncWorkComplete); + }]; + }]; + [_asyncWorkQueue enqueueWorkItem:workItem descriptionWithFormat:@"write %@ 0x%llx 0x%llx", endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue]; +} + +- (void)invokeCommandWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + commandID:(NSNumber *)commandID + commandFields:(NSDictionary * _Nullable)commandFields + expectedValues:(NSArray *> * _Nullable)expectedValues + expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval + queue:(dispatch_queue_t)queue + completion:(MTRDeviceResponseHandler)completion +{ + if (commandFields == nil) { + commandFields = @{ + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], + }; + } + + [self invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + expectedValues:expectedValues + expectedValueInterval:expectedValueInterval + timedInvokeTimeout:nil + queue:queue + completion:completion]; +} + +- (void)invokeCommandWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + commandID:(NSNumber *)commandID + commandFields:(id)commandFields + expectedValues:(NSArray *> * _Nullable)expectedValues + expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval + timedInvokeTimeout:(NSNumber * _Nullable)timeout + queue:(dispatch_queue_t)queue + completion:(MTRDeviceResponseHandler)completion +{ + // We don't have a way to communicate a non-default invoke timeout + // here for now. + // TODO: https://github.com/project-chip/connectedhomeip/issues/24563 + + [self _invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + expectedValues:expectedValues + expectedValueInterval:expectedValueInterval + timedInvokeTimeout:timeout + serverSideProcessingTimeout:nil + queue:queue + completion:completion]; +} + +- (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + commandID:(NSNumber *)commandID + commandFields:(id)commandFields + expectedValues:(NSArray *> * _Nullable)expectedValues + expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval + timedInvokeTimeout:(NSNumber * _Nullable)timeout + serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout + queue:(dispatch_queue_t)queue + completion:(MTRDeviceResponseHandler)completion +{ + if (!expectedValueInterval || ([expectedValueInterval compare:@(0)] == NSOrderedAscending)) { + expectedValues = nil; + } else { + expectedValueInterval = MTRClampedNumber(expectedValueInterval, @(1), @(UINT32_MAX)); + } + + serverSideProcessingTimeout = [serverSideProcessingTimeout copy]; + timeout = [timeout copy]; + + if (timeout == nil && MTRCommandNeedsTimedInvoke(clusterID, commandID)) { + timeout = @(MTR_DEFAULT_TIMED_INTERACTION_TIMEOUT_MS); + } + + NSDate * cutoffTime; + if (timeout) { + cutoffTime = [NSDate dateWithTimeIntervalSinceNow:(timeout.doubleValue / 1000)]; + } + + uint64_t expectedValueID = 0; + NSMutableArray * attributePaths = nil; + if (expectedValues) { + [self setExpectedValues:expectedValues expectedValueInterval:expectedValueInterval expectedValueID:&expectedValueID]; + attributePaths = [NSMutableArray array]; + for (NSDictionary * expectedValue in expectedValues) { + [attributePaths addObject:expectedValue[MTRAttributePathKey]]; + } + } + MTRAsyncWorkItem * workItem = [[MTRAsyncWorkItem alloc] initWithQueue:self.queue]; + uint64_t workItemID = workItem.uniqueID; // capture only the ID, not the work item + // The command operation will install a duplicate check handler, to return NO for "isDuplicate". Since a command operation may + // change values, only read requests after this should be considered for duplicate requests. + [workItem setDuplicateTypeID:MTRDeviceWorkItemDuplicateReadTypeID handler:^(id opaqueItemData, BOOL * isDuplicate, BOOL * stop) { + *isDuplicate = NO; + *stop = YES; + }]; + [workItem setReadyHandler:^(MTRDevice_Concrete * self, NSInteger retryCount, MTRAsyncWorkCompletionBlock workCompletion) { + auto workDone = ^(NSArray *> * _Nullable values, NSError * _Nullable error) { + dispatch_async(queue, ^{ + completion(values, error); + }); + if (error && expectedValues) { + [self removeExpectedValuesForAttributePaths:attributePaths expectedValueID:expectedValueID]; + } + workCompletion(MTRAsyncWorkComplete); + }; + + NSNumber * timedInvokeTimeout = nil; + if (timeout) { + auto * now = [NSDate now]; + if ([now compare:cutoffTime] == NSOrderedDescending) { + // Our timed invoke timeout has expired already. Command + // was queued for too long. Do not send it out. + workDone(nil, [MTRError errorForIMStatusCode:Status::Timeout]); + return; + } + + // Recompute the actual timeout left, accounting for time spent + // in our queuing and retries. + timedInvokeTimeout = @([cutoffTime timeIntervalSinceDate:now] * 1000); + } + MTRBaseDevice * baseDevice = [self newBaseDevice]; + [baseDevice + _invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + timedInvokeTimeout:timedInvokeTimeout + serverSideProcessingTimeout:serverSideProcessingTimeout + queue:self.queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + // Log the data at the INFO level (not usually persisted permanently), + // but make sure we log the work completion at the DEFAULT level. + MTR_LOG("Invoke work item [%llu] received command response: %@ error: %@", workItemID, values, error); + // TODO: This 5-retry cap is very arbitrary. + // TODO: Should there be some sort of backoff here? + if (error != nil && error.domain == MTRInteractionErrorDomain && error.code == MTRInteractionErrorCodeBusy && retryCount < 5) { + workCompletion(MTRAsyncWorkNeedsRetry); + return; + } + + workDone(values, error); + }]; + }]; + [_asyncWorkQueue enqueueWorkItem:workItem descriptionWithFormat:@"invoke %@ 0x%llx 0x%llx", endpointID, clusterID.unsignedLongLongValue, commandID.unsignedLongLongValue]; +} + +- (void)_invokeKnownCommandWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + commandID:(NSNumber *)commandID + commandPayload:(id)commandPayload + expectedValues:(NSArray *> * _Nullable)expectedValues + expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval + timedInvokeTimeout:(NSNumber * _Nullable)timeout + serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout + responseClass:(Class _Nullable)responseClass + queue:(dispatch_queue_t)queue + completion:(void (^)(id _Nullable response, NSError * _Nullable error))completion +{ + if (![commandPayload respondsToSelector:@selector(_encodeAsDataValue:)]) { + dispatch_async(queue, ^{ + completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]); + }); + return; + } + + NSError * encodingError; + auto * commandFields = [commandPayload _encodeAsDataValue:&encodingError]; + if (commandFields == nil) { + dispatch_async(queue, ^{ + completion(nil, encodingError); + }); + return; + } + + auto responseHandler = ^(NSArray *> * _Nullable values, NSError * _Nullable error) { + id _Nullable response = nil; + if (error == nil) { + if (values.count != 1) { + error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeSchemaMismatch userInfo:nil]; + } else if (responseClass != nil) { + response = [[responseClass alloc] initWithResponseValue:values[0] error:&error]; + } + } + completion(response, error); + }; + + [self _invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + expectedValues:expectedValues + expectedValueInterval:expectedValueInterval + timedInvokeTimeout:timeout + serverSideProcessingTimeout:serverSideProcessingTimeout + queue:queue + completion:responseHandler]; +} + +- (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode + discriminator:(NSNumber *)discriminator + duration:(NSNumber *)duration + queue:(dispatch_queue_t)queue + completion:(MTRDeviceOpenCommissioningWindowHandler)completion +{ + auto * baseDevice = [self newBaseDevice]; + [baseDevice openCommissioningWindowWithSetupPasscode:setupPasscode + discriminator:discriminator + duration:duration + queue:queue + completion:completion]; +} + +- (void)openCommissioningWindowWithDiscriminator:(NSNumber *)discriminator + duration:(NSNumber *)duration + queue:(dispatch_queue_t)queue + completion:(MTRDeviceOpenCommissioningWindowHandler)completion +{ + auto * baseDevice = [self newBaseDevice]; + [baseDevice openCommissioningWindowWithDiscriminator:discriminator duration:duration queue:queue completion:completion]; +} + +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + auto * baseDevice = [self newBaseDevice]; + [baseDevice downloadLogOfType:type + timeout:timeout + queue:queue + completion:completion]; +} + +#pragma mark - Cache management + +// assume lock is held +- (void)_checkExpiredExpectedValues +{ + os_unfair_lock_assert_owner(&self->_lock); + + // find expired attributes, and calculate next timer fire date + NSDate * now = [NSDate date]; + NSDate * nextExpirationDate = nil; + // Set of NSArray with 2 elements [path, value] - this is used in this method only + NSMutableSet * attributeInfoToRemove = [NSMutableSet set]; + for (MTRAttributePath * attributePath in _expectedValueCache) { + NSArray * expectedValue = _expectedValueCache[attributePath]; + NSDate * attributeExpirationDate = expectedValue[MTRDeviceExpectedValueFieldExpirationTimeIndex]; + if (expectedValue) { + if ([now compare:attributeExpirationDate] == NSOrderedDescending) { + // expired - save [path, values] pair to attributeToRemove + [attributeInfoToRemove addObject:@[ attributePath, expectedValue[MTRDeviceExpectedValueFieldValueIndex] ]]; + } else { + // get the next expiration date + if (!nextExpirationDate || [nextExpirationDate compare:attributeExpirationDate] == NSOrderedDescending) { + nextExpirationDate = attributeExpirationDate; + } + } + } + } + + // remove from expected value cache and report attributes as needed + NSMutableArray * attributesToReport = [NSMutableArray array]; + NSMutableArray * attributePathsToReport = [NSMutableArray array]; + for (NSArray * attributeInfo in attributeInfoToRemove) { + // compare with known value and mark for report if different + MTRAttributePath * attributePath = attributeInfo[0]; + NSDictionary * attributeDataValue = attributeInfo[1]; + NSDictionary * cachedAttributeDataValue = [self _cachedAttributeValueForPath:attributePath]; + if (cachedAttributeDataValue + && ![self _attributeDataValue:attributeDataValue isEqualToDataValue:cachedAttributeDataValue]) { + [attributesToReport addObject:@{ MTRAttributePathKey : attributePath, MTRDataKey : cachedAttributeDataValue, MTRPreviousDataKey : attributeDataValue }]; + [attributePathsToReport addObject:attributePath]; + } + + _expectedValueCache[attributePath] = nil; + } + + // log attribute paths + MTR_LOG("%@ report from expired expected values %@", self, attributePathsToReport); + [self _reportAttributes:attributesToReport]; + +// Have a reasonable minimum wait time for expiration timers +#define MTR_DEVICE_EXPIRATION_CHECK_TIMER_MINIMUM_WAIT_TIME (0.1) + + if (nextExpirationDate && _expectedValueCache.count && !self.expirationCheckScheduled) { + NSTimeInterval waitTime = [nextExpirationDate timeIntervalSinceDate:now]; + if (waitTime < MTR_DEVICE_EXPIRATION_CHECK_TIMER_MINIMUM_WAIT_TIME) { + waitTime = MTR_DEVICE_EXPIRATION_CHECK_TIMER_MINIMUM_WAIT_TIME; + } + mtr_weakify(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (waitTime * NSEC_PER_SEC)), self.queue, ^{ + mtr_strongify(self); + [self _performScheduledExpirationCheck]; + }); + } +} + +- (void)_performScheduledExpirationCheck +{ + std::lock_guard lock(_lock); + + self.expirationCheckScheduled = NO; + [self _checkExpiredExpectedValues]; +} + +// Get attribute value dictionary for an attribute path from the right cache +- (NSDictionary *)_attributeValueDictionaryForAttributePath:(MTRAttributePath *)attributePath +{ + std::lock_guard lock(_lock); + + // First check expected value cache + NSArray * expectedValue = _expectedValueCache[attributePath]; + if (expectedValue) { + NSDate * now = [NSDate date]; + if ([now compare:expectedValue[MTRDeviceExpectedValueFieldExpirationTimeIndex]] == NSOrderedDescending) { + // expired - purge and fall through + _expectedValueCache[attributePath] = nil; + } else { + // not yet expired - return result + return expectedValue[MTRDeviceExpectedValueFieldValueIndex]; + } + } + + // Then check read cache + NSDictionary * cachedAttributeValue = [self _cachedAttributeValueForPath:attributePath]; + if (cachedAttributeValue) { + return cachedAttributeValue; + } else { + // TODO: when not found in cache, generated default values should be used + MTR_LOG("%@ _attributeValueDictionaryForAttributePath: could not find cached attribute values for attribute %@", self, + attributePath); + } + + return nil; +} + +- (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary *)theOther +{ + // Sanity check for nil cases + if (!one && !theOther) { + MTR_LOG_ERROR("%@ attribute data-value comparison does not expect comparing two nil dictionaries", self); + return YES; + } + if (!one || !theOther) { + // Comparing against nil is expected, and should return NO quietly + return NO; + } + + // Attribute data-value dictionaries are equal if type and value are equal, and specifically, this should return true if values are both nil + return [one[MTRTypeKey] isEqual:theOther[MTRTypeKey]] && ((one[MTRValueKey] == theOther[MTRValueKey]) || [one[MTRValueKey] isEqual:theOther[MTRValueKey]]); +} + +// Utility to return data value dictionary without data version +- (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue +{ + // Sanity check for nil - return the same input to fail gracefully + if (!attributeValue || !attributeValue[MTRTypeKey]) { + return attributeValue; + } + + if (attributeValue[MTRValueKey]) { + return @{ MTRTypeKey : attributeValue[MTRTypeKey], MTRValueKey : attributeValue[MTRValueKey] }; + } else { + return @{ MTRTypeKey : attributeValue[MTRTypeKey] }; + } +} + +// Update cluster data version and also note the change, so at onReportEnd it can be persisted +- (void)_noteDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPath *)clusterPath +{ + os_unfair_lock_assert_owner(&self->_lock); + + BOOL dataVersionChanged = NO; + // Update data version used for subscription filtering + MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; + if (!clusterData) { + clusterData = [[MTRDeviceClusterData alloc] initWithDataVersion:dataVersion attributes:nil]; + dataVersionChanged = YES; + } else if (![clusterData.dataVersion isEqualToNumber:dataVersion]) { + clusterData.dataVersion = dataVersion; + dataVersionChanged = YES; + } + + if (dataVersionChanged) { + if (_clusterDataToPersist == nil) { + _clusterDataToPersist = [NSMutableDictionary dictionary]; + } + _clusterDataToPersist[clusterPath] = clusterData; + } +} + +- (BOOL)_attributeAffectsDeviceConfiguration:(MTRAttributePath *)attributePath +{ + // Check for attributes in the descriptor cluster that affect device configuration. + if (attributePath.cluster.unsignedLongValue == MTRClusterIDTypeDescriptorID) { + switch (attributePath.attribute.unsignedLongValue) { + case MTRAttributeIDTypeClusterDescriptorAttributePartsListID: + case MTRAttributeIDTypeClusterDescriptorAttributeServerListID: + case MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID: { + return YES; + } + } + } + + // Check for global attributes that affect device configuration. + switch (attributePath.attribute.unsignedLongValue) { + case MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID: + case MTRAttributeIDTypeGlobalAttributeAttributeListID: + case MTRAttributeIDTypeGlobalAttributeClusterRevisionID: + case MTRAttributeIDTypeGlobalAttributeFeatureMapID: + return YES; + } + return NO; +} + +- (void)_removeClusters:(NSSet *)clusterPathsToRemove + doRemoveFromDataStore:(BOOL)doRemoveFromDataStore +{ + os_unfair_lock_assert_owner(&self->_lock); + + [_persistedClusters minusSet:clusterPathsToRemove]; + + for (MTRClusterPath * path in clusterPathsToRemove) { + [_persistedClusterData removeObjectForKey:path]; + [_clusterDataToPersist removeObjectForKey:path]; + if (doRemoveFromDataStore) { + [self.deviceController.controllerDataStore clearStoredClusterDataForNodeID:self.nodeID endpointID:path.endpoint clusterID:path.cluster]; + } + } +} + +- (void)_removeAttributes:(NSSet *)attributes fromCluster:(MTRClusterPath *)clusterPath +{ + os_unfair_lock_assert_owner(&self->_lock); + + for (NSNumber * attribute in attributes) { + [self _removeCachedAttribute:attribute fromCluster:clusterPath]; + } + // Just clear out the NSCache entry for this cluster, so we'll load it from storage as needed. + [_persistedClusterData removeObjectForKey:clusterPath]; + [self.deviceController.controllerDataStore removeAttributes:attributes fromCluster:clusterPath forNodeID:self.nodeID]; +} + +- (void)_pruneEndpointsIn:(MTRDeviceDataValueDictionary)previousPartsListValue + missingFrom:(MTRDeviceDataValueDictionary)newPartsListValue +{ + // If the parts list changed and one or more endpoints were removed, remove all the + // clusters for all those endpoints from our data structures. + // Also remove those endpoints from the data store. + NSMutableSet * toBeRemovedEndpoints = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousPartsListValue]]; + NSSet * endpointsOnDevice = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:newPartsListValue]]; + [toBeRemovedEndpoints minusSet:endpointsOnDevice]; + + for (NSNumber * endpoint in toBeRemovedEndpoints) { + NSMutableSet * clusterPathsToRemove = [[NSMutableSet alloc] init]; + for (MTRClusterPath * path in _persistedClusters) { + if ([path.endpoint isEqualToNumber:endpoint]) { + [clusterPathsToRemove addObject:path]; + } + } + [self _removeClusters:clusterPathsToRemove doRemoveFromDataStore:NO]; + [self.deviceController.controllerDataStore clearStoredClusterDataForNodeID:self.nodeID endpointID:endpoint]; + + [_deviceController asyncDispatchToMatterQueue:^{ + std::lock_guard lock(self->_lock); + if (self->_currentSubscriptionCallback) { + self->_currentSubscriptionCallback->ClearCachedAttributeState(static_cast(endpoint.unsignedLongLongValue)); + } + } errorHandler:nil]; + } +} + +- (void)_pruneClustersIn:(MTRDeviceDataValueDictionary)previousServerListValue + missingFrom:(MTRDeviceDataValueDictionary)newServerListValue + forEndpoint:(NSNumber *)endpointID +{ + // If the server list changed and clusters were removed, remove those clusters from our data structures. + // Also remove them from the data store. + NSMutableSet * toBeRemovedClusters = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousServerListValue]]; + NSSet * clustersStillOnEndpoint = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:newServerListValue]]; + [toBeRemovedClusters minusSet:clustersStillOnEndpoint]; + + NSMutableSet * clusterPathsToRemove = [[NSMutableSet alloc] init]; + for (MTRClusterPath * path in _persistedClusters) { + if ([path.endpoint isEqualToNumber:endpointID] && [toBeRemovedClusters containsObject:path.cluster]) { + [clusterPathsToRemove addObject:path]; + } + } + [self _removeClusters:clusterPathsToRemove doRemoveFromDataStore:YES]; + + [_deviceController asyncDispatchToMatterQueue:^{ + std::lock_guard lock(self->_lock); + if (self->_currentSubscriptionCallback) { + for (NSNumber * cluster in toBeRemovedClusters) { + ConcreteClusterPath clusterPath(static_cast(endpointID.unsignedLongLongValue), + static_cast(cluster.unsignedLongLongValue)); + self->_currentSubscriptionCallback->ClearCachedAttributeState(clusterPath); + } + } + } errorHandler:nil]; +} + +- (void)_pruneAttributesIn:(MTRDeviceDataValueDictionary)previousAttributeListValue + missingFrom:(MTRDeviceDataValueDictionary)newAttributeListValue + forCluster:(MTRClusterPath *)clusterPath +{ + // If the attribute list changed and attributes were removed, remove the attributes from our + // data structures. + NSMutableSet * toBeRemovedAttributes = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousAttributeListValue]]; + NSSet * attributesStillInCluster = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:newAttributeListValue]]; + + [toBeRemovedAttributes minusSet:attributesStillInCluster]; + [self _removeAttributes:toBeRemovedAttributes fromCluster:clusterPath]; + + [_deviceController asyncDispatchToMatterQueue:^{ + std::lock_guard lock(self->_lock); + if (self->_currentSubscriptionCallback) { + for (NSNumber * attribute in toBeRemovedAttributes) { + ConcreteAttributePath attributePath(static_cast(clusterPath.endpoint.unsignedLongLongValue), + static_cast(clusterPath.cluster.unsignedLongLongValue), + static_cast(attribute.unsignedLongLongValue)); + self->_currentSubscriptionCallback->ClearCachedAttributeState(attributePath); + } + } + } errorHandler:nil]; +} + +- (void)_pruneStoredDataForPath:(MTRAttributePath *)attributePath + missingFrom:(MTRDeviceDataValueDictionary)newAttributeDataValue +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (![self _dataStoreExists] && !_clusterDataToPersist.count) { + MTR_LOG_DEBUG("%@ No data store to prune from", self); + return; + } + + // Check if parts list changed or server list changed for the descriptor cluster or the attribute list changed for a cluster. + // If yes, we might need to prune any deleted endpoints, clusters or attributes from the storage and persisted cluster data. + if (attributePath.cluster.unsignedLongValue == MTRClusterIDTypeDescriptorID) { + if (attributePath.attribute.unsignedLongValue == MTRAttributeIDTypeClusterDescriptorAttributePartsListID && [attributePath.endpoint isEqualToNumber:@(kRootEndpointId)]) { + [self _pruneEndpointsIn:[self _cachedAttributeValueForPath:attributePath] missingFrom:newAttributeDataValue]; + return; + } + + if (attributePath.attribute.unsignedLongValue == MTRAttributeIDTypeClusterDescriptorAttributeServerListID) { + [self _pruneClustersIn:[self _cachedAttributeValueForPath:attributePath] missingFrom:newAttributeDataValue forEndpoint:attributePath.endpoint]; + return; + } + } + + if (attributePath.attribute.unsignedLongValue == MTRAttributeIDTypeGlobalAttributeAttributeListID) { + [self _pruneAttributesIn:[self _cachedAttributeValueForPath:attributePath] missingFrom:newAttributeDataValue forCluster:[MTRClusterPath clusterPathWithEndpointID:attributePath.endpoint clusterID:attributePath.cluster]]; + } +} + +// assume lock is held +- (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *> *)reportedAttributeValues fromSubscription:(BOOL)isFromSubscription +{ + os_unfair_lock_assert_owner(&self->_lock); + + NSMutableArray * attributesToReport = [NSMutableArray array]; + NSMutableArray * attributePathsToReport = [NSMutableArray array]; + for (NSDictionary * attributeResponseValue in reportedAttributeValues) { + MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey]; + NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey]; + NSError * attributeError = attributeResponseValue[MTRErrorKey]; + NSDictionary * previousValue; + + // sanity check either data value or error must exist + if (!attributeDataValue && !attributeError) { + MTR_LOG("%@ report %@ no data value or error: %@", self, attributePath, attributeResponseValue); + continue; + } + + // Additional signal to help mark events as being received during priming report in the event the device rebooted and we get a subscription resumption priming report without noticing it became unreachable first + if (_receivingReport && AttributeHasChangesOmittedQuality(attributePath)) { + _receivingPrimingReport = YES; + } + + // check if value is different than cache, and report if needed + BOOL shouldReportAttribute = NO; + + // if this is an error, report and purge cache + if (attributeError) { + shouldReportAttribute = YES; + previousValue = [self _cachedAttributeValueForPath:attributePath]; + MTR_LOG_ERROR("%@ report %@ error %@ purge expected value %@ read cache %@", self, attributePath, attributeError, + _expectedValueCache[attributePath], previousValue); + _expectedValueCache[attributePath] = nil; + // TODO: Is this clearing business really what we want? + [self _setCachedAttributeValue:nil forPath:attributePath fromSubscription:isFromSubscription]; + } else { + // First separate data version and restore data value to a form without data version + NSNumber * dataVersion = attributeDataValue[MTRDataVersionKey]; + MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID:attributePath.endpoint clusterID:attributePath.cluster]; + if (dataVersion) { + // Remove data version from what we cache in memory + attributeDataValue = [self _dataValueWithoutDataVersion:attributeDataValue]; + } + + previousValue = [self _cachedAttributeValueForPath:attributePath]; +#ifdef DEBUG + __block BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:previousValue]; +#else + BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:previousValue]; +#endif + // Now that we have grabbed previousValue, update our cache with the attribute value. + if (readCacheValueChanged) { + if (dataVersion) { + [self _noteDataVersion:dataVersion forClusterPath:clusterPath]; + } + + [self _pruneStoredDataForPath:attributePath missingFrom:attributeDataValue]; + + if (!_deviceConfigurationChanged) { + _deviceConfigurationChanged = [self _attributeAffectsDeviceConfiguration:attributePath]; + if (_deviceConfigurationChanged) { + MTR_LOG("Device configuration changed due to changes in attribute %@", attributePath); + } + } + + [self _setCachedAttributeValue:attributeDataValue forPath:attributePath fromSubscription:isFromSubscription]; + } + +#ifdef DEBUG + // Unit test only code. + if (!readCacheValueChanged) { + [self _callFirstDelegateSynchronouslyWithBlock:^(id delegate) { + if ([delegate respondsToSelector:@selector(unitTestForceAttributeReportsIfMatchingCache:)]) { + readCacheValueChanged = [delegate unitTestForceAttributeReportsIfMatchingCache:self]; + } + }]; + } +#endif // DEBUG + + NSArray * expectedValue = _expectedValueCache[attributePath]; + + // Report the attribute if a read would get a changed value. This happens + // when our cached value changes and no expected value exists. + if (readCacheValueChanged && !expectedValue) { + shouldReportAttribute = YES; + } + + if (!shouldReportAttribute) { + // If an expected value exists, the attribute will not be reported at this time. + // When the expected value interval expires, the correct value will be reported, + // if needed. + if (expectedValue) { + MTR_LOG("%@ report %@ value filtered - expected value still present", self, attributePath); + } else { + MTR_LOG("%@ report %@ value filtered - same as read cache", self, attributePath); + } + } + + // If General Diagnostics UpTime attribute, update the estimated start time as needed. + if ((attributePath.cluster.unsignedLongValue == MTRClusterGeneralDiagnosticsID) + && (attributePath.attribute.unsignedLongValue == MTRClusterGeneralDiagnosticsAttributeUpTimeID)) { + // verify that the uptime is indeed the data type we want + if ([attributeDataValue[MTRTypeKey] isEqual:MTRUnsignedIntegerValueType]) { + NSNumber * upTimeNumber = attributeDataValue[MTRValueKey]; + NSTimeInterval upTime = upTimeNumber.unsignedLongLongValue; // UpTime unit is defined as seconds in the spec + NSDate * potentialSystemStartTime = [NSDate dateWithTimeIntervalSinceNow:-upTime]; + NSDate * oldSystemStartTime = _estimatedStartTime; + if (!_estimatedStartTime || ([potentialSystemStartTime compare:_estimatedStartTime] == NSOrderedAscending)) { + MTR_LOG("%@ General Diagnostics UpTime %.3lf: estimated start time %@ => %@", self, upTime, + oldSystemStartTime, potentialSystemStartTime); + _estimatedStartTime = potentialSystemStartTime; + } + + // Save estimate in the subscription resumption case, for when StartUp event uses it + _estimatedStartTimeFromGeneralDiagnosticsUpTime = potentialSystemStartTime; + } + } + } + + if (shouldReportAttribute) { + if (previousValue) { + NSMutableDictionary * mutableAttributeResponseValue = attributeResponseValue.mutableCopy; + mutableAttributeResponseValue[MTRPreviousDataKey] = previousValue; + [attributesToReport addObject:mutableAttributeResponseValue]; + } else { + [attributesToReport addObject:attributeResponseValue]; + } + [attributePathsToReport addObject:attributePath]; + } + } + + if (attributePathsToReport.count > 0) { + MTR_LOG("%@ report from reported values %@", self, attributePathsToReport); + } + + return attributesToReport; +} + +#ifdef DEBUG +- (NSUInteger)unitTestAttributeCount +{ + std::lock_guard lock(_lock); + NSUInteger count = 0; + for (MTRClusterPath * path in [self _knownClusters]) { + count += [self _clusterDataForPath:path].attributes.count; + } + return count; +} +#endif + +- (void)setPersistedClusterData:(NSDictionary *)clusterData +{ + MTR_LOG("%@ setPersistedClusterData count: %lu", self, static_cast(clusterData.count)); + if (!clusterData.count) { + return; + } + + std::lock_guard lock(_lock); + + NSAssert([self _dataStoreExists], @"Why is controller setting persisted data when we shouldn't have it?"); + + for (MTRClusterPath * clusterPath in clusterData) { + // The caller has mutable references to MTRDeviceClusterData and + // MTRClusterPath, but that should be OK, since we control all the + // callers. If that stops being OK, we'll need to copy the key and + // value here. + [_persistedClusters addObject:clusterPath]; + [_persistedClusterData setObject:clusterData[clusterPath] forKey:clusterPath]; + } + + // We have some stored data. Since we don't store data until the end of the + // initial priming report, our device cache must be primed. + _deviceCachePrimed = YES; +} + +- (void)_setLastInitialSubscribeLatency:(id)latency +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (![latency isKindOfClass:NSNumber.class]) { + // Unexpected value of some sort; just ignore it. + return; + } + + _estimatedSubscriptionLatency = latency; +} + +- (void)setPersistedDeviceData:(NSDictionary *)data +{ + MTR_LOG_DEBUG("%@ setPersistedDeviceData: %@", self, data); + + std::lock_guard lock(_lock); + + // For now the only data we care about is our initial subscribe latency. + id initialSubscribeLatency = data[sLastInitialSubscribeLatencyKey]; + if (initialSubscribeLatency != nil) { + [self _setLastInitialSubscribeLatency:initialSubscribeLatency]; + } +} + +- (void)_storePersistedDeviceData +{ + os_unfair_lock_assert_owner(&self->_lock); + + auto datastore = _deviceController.controllerDataStore; + if (datastore == nil) { + // No way to store. + return; + } + + // For now the only data we have is our initial subscribe latency. + NSMutableDictionary * data = [NSMutableDictionary dictionary]; + if (_estimatedSubscriptionLatency != nil) { + data[sLastInitialSubscribeLatencyKey] = _estimatedSubscriptionLatency; + } + + [datastore storeDeviceData:[data copy] forNodeID:self.nodeID]; +} + +#ifdef DEBUG +- (MTRDeviceClusterData *)unitTestGetClusterDataForPath:(MTRClusterPath *)path +{ + std::lock_guard lock(_lock); + + return [[self _clusterDataForPath:path] copy]; +} + +- (NSSet *)unitTestGetPersistedClusters +{ + std::lock_guard lock(_lock); + + return [_persistedClusters copy]; +} + +- (BOOL)unitTestClusterHasBeenPersisted:(MTRClusterPath *)path +{ + std::lock_guard lock(_lock); + + return [_persistedClusters containsObject:path]; +} +#endif + +- (BOOL)deviceCachePrimed +{ + std::lock_guard lock(_lock); + return _deviceCachePrimed; +} + +// If value is non-nil, associate with expectedValueID +// If value is nil, remove only if expectedValueID matches +// previousValue is an out parameter +- (void)_setExpectedValue:(NSDictionary *)expectedAttributeValue + attributePath:(MTRAttributePath *)attributePath + expirationTime:(NSDate *)expirationTime + shouldReportValue:(BOOL *)shouldReportValue + attributeValueToReport:(NSDictionary **)attributeValueToReport + expectedValueID:(uint64_t)expectedValueID + previousValue:(NSDictionary **)previousValue +{ + os_unfair_lock_assert_owner(&self->_lock); + + *shouldReportValue = NO; + + NSArray * previousExpectedValue = _expectedValueCache[attributePath]; + if (previousExpectedValue) { + if (expectedAttributeValue + && ![self _attributeDataValue:expectedAttributeValue + isEqualToDataValue:previousExpectedValue[MTRDeviceExpectedValueFieldValueIndex]]) { + // Case where new expected value overrides previous expected value - report new expected value + *shouldReportValue = YES; + *attributeValueToReport = expectedAttributeValue; + *previousValue = previousExpectedValue[MTRDeviceExpectedValueFieldValueIndex]; + } else if (!expectedAttributeValue) { + // Remove previous expected value only if it's from the same setExpectedValues operation + NSNumber * previousExpectedValueID = previousExpectedValue[MTRDeviceExpectedValueFieldIDIndex]; + if (previousExpectedValueID.unsignedLongLongValue == expectedValueID) { + MTRDeviceDataValueDictionary cachedValue = [self _cachedAttributeValueForPath:attributePath]; + if (![self _attributeDataValue:previousExpectedValue[MTRDeviceExpectedValueFieldValueIndex] + isEqualToDataValue:cachedValue]) { + // Case of removing expected value that is different than read cache - report read cache value + *shouldReportValue = YES; + *attributeValueToReport = cachedValue; + *previousValue = previousExpectedValue[MTRDeviceExpectedValueFieldValueIndex]; + _expectedValueCache[attributePath] = nil; + } + } + } + } else { + MTRDeviceDataValueDictionary cachedValue = [self _cachedAttributeValueForPath:attributePath]; + if (expectedAttributeValue + && ![self _attributeDataValue:expectedAttributeValue isEqualToDataValue:cachedValue]) { + // Case where new expected value is different than read cache - report new expected value + *shouldReportValue = YES; + *attributeValueToReport = expectedAttributeValue; + *previousValue = cachedValue; + } else { + *previousValue = nil; + } + + // No need to report if new and previous expected value are both nil + } + + if (expectedAttributeValue) { + _expectedValueCache[attributePath] = @[ expirationTime, expectedAttributeValue, @(expectedValueID) ]; + } +} + +// assume lock is held +- (NSArray *)_getAttributesToReportWithNewExpectedValues:(NSArray *> *)expectedAttributeValues + expirationTime:(NSDate *)expirationTime + expectedValueID:(uint64_t *)expectedValueID +{ + os_unfair_lock_assert_owner(&self->_lock); + uint64_t expectedValueIDToReturn = _expectedValueNextID++; + + NSMutableArray * attributesToReport = [NSMutableArray array]; + NSMutableArray * attributePathsToReport = [NSMutableArray array]; + for (NSDictionary * attributeResponseValue in expectedAttributeValues) { + MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey]; + NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey]; + + BOOL shouldReportValue = NO; + NSDictionary * attributeValueToReport; + NSDictionary * previousValue; + [self _setExpectedValue:attributeDataValue + attributePath:attributePath + expirationTime:expirationTime + shouldReportValue:&shouldReportValue + attributeValueToReport:&attributeValueToReport + expectedValueID:expectedValueIDToReturn + previousValue:&previousValue]; + + if (shouldReportValue) { + if (previousValue) { + [attributesToReport addObject:@{ MTRAttributePathKey : attributePath, MTRDataKey : attributeValueToReport, MTRPreviousDataKey : previousValue }]; + } else { + [attributesToReport addObject:@{ MTRAttributePathKey : attributePath, MTRDataKey : attributeValueToReport }]; + } + [attributePathsToReport addObject:attributePath]; + } + } + if (expectedValueID) { + *expectedValueID = expectedValueIDToReturn; + } + + MTR_LOG("%@ report from new expected values %@", self, attributePathsToReport); + + return attributesToReport; +} + +- (void)setExpectedValues:(NSArray *> *)values expectedValueInterval:(NSNumber *)expectedValueInterval +{ + [self setExpectedValues:values expectedValueInterval:expectedValueInterval expectedValueID:nil]; +} + +// expectedValueID is an out-argument that returns an identifier to be used when removing expected values +- (void)setExpectedValues:(NSArray *> *)values + expectedValueInterval:(NSNumber *)expectedValueInterval + expectedValueID:(uint64_t *)expectedValueID +{ + // since NSTimeInterval is in seconds, convert ms into seconds in double + NSDate * expirationTime = [NSDate dateWithTimeIntervalSinceNow:expectedValueInterval.doubleValue / 1000]; + + MTR_LOG( + "%@ Setting expected values %@ with expiration time %f seconds from now", self, values, [expirationTime timeIntervalSinceNow]); + + std::lock_guard lock(_lock); + + // _getAttributesToReportWithNewExpectedValues will log attribute paths reported + NSArray * attributesToReport = [self _getAttributesToReportWithNewExpectedValues:values + expirationTime:expirationTime + expectedValueID:expectedValueID]; + [self _reportAttributes:attributesToReport]; + + [self _checkExpiredExpectedValues]; +} + +- (void)removeExpectedValuesForAttributePaths:(NSArray *)attributePaths + expectedValueID:(uint64_t)expectedValueID +{ + std::lock_guard lock(_lock); + + for (MTRAttributePath * attributePath in attributePaths) { + [self _removeExpectedValueForAttributePath:attributePath expectedValueID:expectedValueID]; + } +} + +- (void)removeExpectedValueForAttributePath:(MTRAttributePath *)attributePath expectedValueID:(uint64_t)expectedValueID +{ + std::lock_guard lock(_lock); + [self _removeExpectedValueForAttributePath:attributePath expectedValueID:expectedValueID]; +} + +- (void)_removeExpectedValueForAttributePath:(MTRAttributePath *)attributePath expectedValueID:(uint64_t)expectedValueID +{ + os_unfair_lock_assert_owner(&self->_lock); + + BOOL shouldReportValue; + NSDictionary * attributeValueToReport; + NSDictionary * previousValue; + [self _setExpectedValue:nil + attributePath:attributePath + expirationTime:nil + shouldReportValue:&shouldReportValue + attributeValueToReport:&attributeValueToReport + expectedValueID:expectedValueID + previousValue:&previousValue]; + + MTR_LOG("%@ remove expected value for path %@ should report %@", self, attributePath, shouldReportValue ? @"YES" : @"NO"); + + if (shouldReportValue) { + NSMutableDictionary * attribute = [NSMutableDictionary dictionaryWithObject:attributePath forKey:MTRAttributePathKey]; + if (attributeValueToReport) { + attribute[MTRDataKey] = attributeValueToReport; + } + if (previousValue) { + attribute[MTRPreviousDataKey] = previousValue; + } + [self _reportAttributes:@[ attribute ]]; + } +} + +- (MTRBaseDevice *)newBaseDevice +{ + return [MTRBaseDevice deviceWithNodeID:self.nodeID controller:self.deviceController]; +} + +// Client Metadata Storage + +- (NSArray *)supportedClientDataClasses +{ + return @[ [NSData class], [NSString class], [NSNumber class], [NSDictionary class], [NSArray class] ]; +} + +- (NSArray * _Nullable)clientDataKeys +{ + return [self.temporaryMetaDataCache allKeys]; +} + +- (id _Nullable)clientDataForKey:(NSString *)key +{ + if (key == nil) + return nil; + + return [self.temporaryMetaDataCache objectForKey:[NSString stringWithFormat:@"%@:-1", key]]; +} + +- (void)setClientDataForKey:(NSString *)key value:(id)value +{ + // TODO: Check supported data types, and also if they conform to NSSecureCoding, when we store these + // TODO: Need to add a delegate method, so when this value changes we call back to the client + + if (key == nil || value == nil) + return; + + if (self.temporaryMetaDataCache == nil) { + self.temporaryMetaDataCache = [NSMutableDictionary dictionary]; + } + + [self.temporaryMetaDataCache setObject:value forKey:[NSString stringWithFormat:@"%@:-1", key]]; +} + +- (void)removeClientDataForKey:(NSString *)key +{ + if (key == nil) + return; + + [self.temporaryMetaDataCache removeObjectForKey:[NSString stringWithFormat:@"%@:-1", key]]; +} + +- (NSArray * _Nullable)clientDataKeysForEndpointID:(NSNumber *)endpointID +{ + if (endpointID == nil) + return nil; + // TODO: When hooked up to storage, enumerate this better + + return [self.temporaryMetaDataCache allKeys]; +} + +- (id _Nullable)clientDataForKey:(NSString *)key endpointID:(NSNumber *)endpointID +{ + if (key == nil || endpointID == nil) + return nil; + + return [self.temporaryMetaDataCache objectForKey:[NSString stringWithFormat:@"%@:%@", key, endpointID]]; +} + +- (void)setClientDataForKey:(NSString *)key endpointID:(NSNumber *)endpointID value:(id)value +{ + if (key == nil || value == nil || endpointID == nil) + return; + + if (self.temporaryMetaDataCache == nil) { + self.temporaryMetaDataCache = [NSMutableDictionary dictionary]; + } + + [self.temporaryMetaDataCache setObject:value forKey:[NSString stringWithFormat:@"%@:%@", key, endpointID]]; +} + +- (void)removeClientDataForKey:(NSString *)key endpointID:(NSNumber *)endpointID +{ + if (key == nil || endpointID == nil) + return; + + [self.temporaryMetaDataCache removeObjectForKey:[NSString stringWithFormat:@"%@:%@", key, endpointID]]; +} + +#pragma mark Log Help + +- (nullable NSNumber *)_informationalNumberAtAttributePath:(MTRAttributePath *)attributePath +{ + auto * cachedData = [self _cachedAttributeValueForPath:attributePath]; + + auto * attrReport = [[MTRAttributeReport alloc] initWithResponseValue:@{ + MTRAttributePathKey : attributePath, + MTRDataKey : cachedData, + } + error:nil]; + + return attrReport.value; +} + +- (nullable NSNumber *)_informationalVendorID +{ + auto * vendorIDPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId) + clusterID:@(MTRClusterIDTypeBasicInformationID) + attributeID:@(MTRClusterBasicAttributeVendorIDID)]; + + return [self _informationalNumberAtAttributePath:vendorIDPath]; +} + +- (nullable NSNumber *)_informationalProductID +{ + auto * productIDPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId) + clusterID:@(MTRClusterIDTypeBasicInformationID) + attributeID:@(MTRClusterBasicAttributeProductIDID)]; + + return [self _informationalNumberAtAttributePath:productIDPath]; +} + +- (void)_addInformationalAttributesToCurrentMetricScope +{ + using namespace chip::Tracing::DarwinFramework; + MATTER_LOG_METRIC(kMetricDeviceVendorID, [self _informationalVendorID].unsignedShortValue); + MATTER_LOG_METRIC(kMetricDeviceProductID, [self _informationalProductID].unsignedShortValue); + BOOL usesThread = [self _deviceUsesThread]; + MATTER_LOG_METRIC(kMetricDeviceUsesThread, usesThread); +} + +@end + +/* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */ + +@implementation MTRDevice_Concrete (MatterPrivateForInternalDragonsDoNotFeed) + +- (BOOL)_deviceHasActiveSubscription +{ + std::lock_guard lock(_lock); + + // TODO: This should always return YES for thread devices + return HaveSubscriptionEstablishedRightNow(_internalDeviceState); +} + +- (void)_deviceMayBeReachable +{ + MTR_LOG("%@ _deviceMayBeReachable called", self); + // TODO: This should only be allowed for thread devices + [_deviceController asyncDispatchToMatterQueue:^{ + [self _triggerResubscribeWithReason:@"SPI client indicated the device may now be reachable" + nodeLikelyReachable:YES]; + } errorHandler:nil]; +} + +/* END DRAGONS */ + +@end + +@implementation MTRDevice_Concrete (Deprecated) + ++ (MTRDevice *)deviceWithNodeID:(uint64_t)nodeID deviceController:(MTRDeviceController *)deviceController +{ + return [self deviceWithNodeID:@(nodeID) controller:deviceController]; +} + +- (void)invokeCommandWithEndpointID:(NSNumber *)endpointID + clusterID:(NSNumber *)clusterID + commandID:(NSNumber *)commandID + commandFields:(id)commandFields + expectedValues:(NSArray *> * _Nullable)expectedValues + expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval + timedInvokeTimeout:(NSNumber * _Nullable)timeout + clientQueue:(dispatch_queue_t)queue + completion:(MTRDeviceResponseHandler)completion +{ + [self invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + expectedValues:expectedValues + expectedValueInterval:expectedValueInterval + timedInvokeTimeout:timeout + queue:queue + completion:completion]; +} + +@end + +#pragma mark - SubscriptionCallback +namespace { +void SubscriptionCallback::OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) +{ + if (mEventReports == nil) { + // Never got a OnReportBegin? Not much to do other than tear things down. + ReportError(CHIP_ERROR_INCORRECT_STATE); + return; + } + + MTREventPath * eventPath = [[MTREventPath alloc] initWithPath:aEventHeader.mPath]; + if (apStatus != nullptr) { + [mEventReports addObject:@ { MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForIMStatus:*apStatus] }]; + } else if (apData == nullptr) { + [mEventReports addObject:@ { + MTREventPathKey : eventPath, + MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] + }]; + } else { + id value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData); + if (value == nil) { + MTR_LOG_ERROR("Failed to decode event data for path %@", eventPath); + [mEventReports addObject:@ { + MTREventPathKey : eventPath, + MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_DECODE_FAILED], + }]; + } else { + [mEventReports addObject:[MTRBaseDevice eventReportForHeader:aEventHeader andData:value]]; + } + } + + QueueInterimReport(); +} + +void SubscriptionCallback::OnAttributeData( + const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) +{ + if (aPath.IsListItemOperation()) { + ReportError(CHIP_ERROR_INCORRECT_STATE); + return; + } + + if (mAttributeReports == nil) { + // Never got a OnReportBegin? Not much to do other than tear things down. + ReportError(CHIP_ERROR_INCORRECT_STATE); + return; + } + + MTRAttributePath * attributePath = [[MTRAttributePath alloc] initWithPath:aPath]; + if (aStatus.mStatus != Status::Success) { + [mAttributeReports addObject:@ { MTRAttributePathKey : attributePath, MTRErrorKey : [MTRError errorForIMStatus:aStatus] }]; + } else if (apData == nullptr) { + [mAttributeReports addObject:@ { + MTRAttributePathKey : attributePath, + MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] + }]; + } else { + NSNumber * dataVersionNumber = aPath.mDataVersion.HasValue() ? @(aPath.mDataVersion.Value()) : nil; + NSDictionary * value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData, dataVersionNumber); + if (value == nil) { + MTR_LOG_ERROR("Failed to decode attribute data for path %@", attributePath); + [mAttributeReports addObject:@ { + MTRAttributePathKey : attributePath, + MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_DECODE_FAILED], + }]; + } else { + [mAttributeReports addObject:@ { MTRAttributePathKey : attributePath, MTRDataKey : value }]; + } + } + + QueueInterimReport(); +} + +uint32_t SubscriptionCallback::ComputeTimeTillNextSubscription() +{ + uint32_t maxWaitTimeInMsec = 0; + uint32_t waitTimeInMsec = 0; + uint32_t minWaitTimeInMsec = 0; + + if (mResubscriptionNumRetries <= CHIP_RESUBSCRIBE_MAX_FIBONACCI_STEP_INDEX) { + maxWaitTimeInMsec = GetFibonacciForIndex(mResubscriptionNumRetries) * CHIP_RESUBSCRIBE_WAIT_TIME_MULTIPLIER_MS; + } else { + maxWaitTimeInMsec = CHIP_RESUBSCRIBE_MAX_RETRY_WAIT_INTERVAL_MS; + } + + if (maxWaitTimeInMsec != 0) { + minWaitTimeInMsec = (CHIP_RESUBSCRIBE_MIN_WAIT_TIME_INTERVAL_PERCENT_PER_STEP * maxWaitTimeInMsec) / 100; + waitTimeInMsec = minWaitTimeInMsec + (Crypto::GetRandU32() % (maxWaitTimeInMsec - minWaitTimeInMsec)); + } + + return waitTimeInMsec; +} + +CHIP_ERROR SubscriptionCallback::OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) +{ + // No need to check ReadClient internal state is Idle because ReadClient only calls OnResubscriptionNeeded after calling ClearActiveSubscriptionState(), which sets the state to Idle. + + // This part is copied from ReadClient's DefaultResubscribePolicy: + auto timeTillNextResubscriptionMs = ComputeTimeTillNextSubscription(); + ChipLogProgress(DataManagement, + "Will try to resubscribe to %02x:" ChipLogFormatX64 " at retry index %" PRIu32 " after %" PRIu32 + "ms due to error %" CHIP_ERROR_FORMAT, + apReadClient->GetFabricIndex(), ChipLogValueX64(apReadClient->GetPeerNodeId()), mResubscriptionNumRetries, timeTillNextResubscriptionMs, + aTerminationCause.Format()); + + // Schedule a maximum time resubscription, to be triggered with TriggerResubscribeIfScheduled after a separate timer. + // This way the aReestablishCASE value is saved, and the sanity checks in ScheduleResubscription are observed and returned. + ReturnErrorOnFailure(apReadClient->ScheduleResubscription(UINT32_MAX, NullOptional, aTerminationCause == CHIP_ERROR_TIMEOUT)); + + // Not as good a place to increment as when resubscription timer fires, but as is, this should be as good, because OnResubscriptionNeeded is only called from ReadClient's Close() while Idle, and nothing should cause this to happen + mResubscriptionNumRetries++; + + auto error = [MTRError errorForCHIPErrorCode:aTerminationCause]; + CallResubscriptionScheduledHandler(error, @(timeTillNextResubscriptionMs)); + + return CHIP_NO_ERROR; +} +} // anonymous namespace diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index 1416aa29dfac29..943e939f47f204 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -66,6 +66,7 @@ MTR_TESTABLE @end @interface MTRDevice () +- (instancetype)initForSubclasses; - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller; // Called from MTRClusters for writes and commands @@ -122,16 +123,16 @@ MTR_TESTABLE @end -#pragma mark - Utility for clamping numbers -// Returns a NSNumber object that is aNumber if it falls within the range [min, max]. -// Returns min or max, if it is below or above, respectively. -NSNumber * MTRClampedNumber(NSNumber * aNumber, NSNumber * min, NSNumber * max); - #pragma mark - Constants static NSString * const kDefaultSubscriptionPoolSizeOverrideKey = @"subscriptionPoolSizeOverride"; static NSString * const kTestStorageUserDefaultEnabledKey = @"enableTestStorage"; +// ex-MTRDeviceClusterData constants +static NSString * const sDataVersionKey = @"dataVersion"; +static NSString * const sAttributesKey = @"attributes"; +static NSString * const sLastInitialSubscribeLatencyKey = @"lastInitialSubscribeLatency"; + // Declared inside platform, but noting here for reference // static NSString * const kSRPTimeoutInMsecsUserDefaultKey = @"SRPTimeoutInMSecsOverride"; diff --git a/src/darwin/Framework/CHIP/MTRUtilities.h b/src/darwin/Framework/CHIP/MTRUtilities.h index 754f5da7ecadd6..a5d780383a1ae9 100644 --- a/src/darwin/Framework/CHIP/MTRUtilities.h +++ b/src/darwin/Framework/CHIP/MTRUtilities.h @@ -24,4 +24,6 @@ NS_ASSUME_NONNULL_BEGIN */ MTR_EXTERN BOOL MTREqualObjects(id _Nullable a, id _Nullable b); +MTR_EXTERN NSNumber * MTRClampedNumber(NSNumber * aNumber, NSNumber * min, NSNumber * max); + NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRUtilities.mm b/src/darwin/Framework/CHIP/MTRUtilities.mm index e9698ab24d0b41..0b46ff93250f59 100644 --- a/src/darwin/Framework/CHIP/MTRUtilities.mm +++ b/src/darwin/Framework/CHIP/MTRUtilities.mm @@ -32,3 +32,13 @@ BOOL MTREqualObjects(id _Nullable a, id _Nullable b) // Otherwise work on equality, given that we're both non nil return [a isEqual:b]; } + +NSNumber * MTRClampedNumber(NSNumber * aNumber, NSNumber * min, NSNumber * max) +{ + if ([aNumber compare:min] == NSOrderedAscending) { + return min; + } else if ([aNumber compare:max] == NSOrderedDescending) { + return max; + } + return aNumber; +} diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm index 06aa9c017be027..925ef9cb90f1dc 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm @@ -5490,6 +5490,9 @@ static BOOL AttributeIsSpecifiedInThreadBorderRouterManagementCluster(AttributeI case Attributes::ActiveDatasetTimestamp::Id: { return YES; } + case Attributes::PendingDatasetTimestamp::Id: { + return YES; + } case Attributes::GeneratedCommandList::Id: { return YES; } diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm index 95001d3ca68720..f899dba1fafe44 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm @@ -11195,7 +11195,7 @@ static id _Nullable DecodeAttributeValueForServiceAreaCluster(AttributeId aAttri if (entry_0.mapID.IsNull()) { newElement_0.mapID = nil; } else { - newElement_0.mapID = [NSNumber numberWithUnsignedChar:entry_0.mapID.Value()]; + newElement_0.mapID = [NSNumber numberWithUnsignedInt:entry_0.mapID.Value()]; } newElement_0.areaDesc = [MTRServiceAreaClusterAreaInfoStruct new]; if (entry_0.areaDesc.locationInfo.IsNull()) { @@ -11219,20 +11219,16 @@ static id _Nullable DecodeAttributeValueForServiceAreaCluster(AttributeId aAttri newElement_0.areaDesc.locationInfo.areaType = [NSNumber numberWithUnsignedChar:chip::to_underlying(entry_0.areaDesc.locationInfo.Value().areaType.Value())]; } } - if (entry_0.areaDesc.landmarkTag.IsNull()) { - newElement_0.areaDesc.landmarkTag = nil; + if (entry_0.areaDesc.landmarkInfo.IsNull()) { + newElement_0.areaDesc.landmarkInfo = nil; } else { - newElement_0.areaDesc.landmarkTag = [NSNumber numberWithUnsignedChar:chip::to_underlying(entry_0.areaDesc.landmarkTag.Value())]; - } - if (entry_0.areaDesc.positionTag.IsNull()) { - newElement_0.areaDesc.positionTag = nil; - } else { - newElement_0.areaDesc.positionTag = [NSNumber numberWithUnsignedChar:chip::to_underlying(entry_0.areaDesc.positionTag.Value())]; - } - if (entry_0.areaDesc.surfaceTag.IsNull()) { - newElement_0.areaDesc.surfaceTag = nil; - } else { - newElement_0.areaDesc.surfaceTag = [NSNumber numberWithUnsignedChar:chip::to_underlying(entry_0.areaDesc.surfaceTag.Value())]; + newElement_0.areaDesc.landmarkInfo = [MTRServiceAreaClusterLandmarkInfoStruct new]; + newElement_0.areaDesc.landmarkInfo.landmarkTag = [NSNumber numberWithUnsignedChar:chip::to_underlying(entry_0.areaDesc.landmarkInfo.Value().landmarkTag)]; + if (entry_0.areaDesc.landmarkInfo.Value().positionTag.IsNull()) { + newElement_0.areaDesc.landmarkInfo.positionTag = nil; + } else { + newElement_0.areaDesc.landmarkInfo.positionTag = [NSNumber numberWithUnsignedChar:chip::to_underlying(entry_0.areaDesc.landmarkInfo.Value().positionTag.Value())]; + } } [array_0 addObject:newElement_0]; } @@ -11260,7 +11256,7 @@ static id _Nullable DecodeAttributeValueForServiceAreaCluster(AttributeId aAttri auto & entry_0 = iter_0.GetValue(); MTRServiceAreaClusterMapStruct * newElement_0; newElement_0 = [MTRServiceAreaClusterMapStruct new]; - newElement_0.mapID = [NSNumber numberWithUnsignedChar:entry_0.mapID]; + newElement_0.mapID = [NSNumber numberWithUnsignedInt:entry_0.mapID]; newElement_0.name = AsString(entry_0.name); if (newElement_0.name == nil) { CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; @@ -15879,6 +15875,21 @@ static id _Nullable DecodeAttributeValueForThreadBorderRouterManagementCluster(A } return value; } + case Attributes::PendingDatasetTimestamp::Id: { + using TypeInfo = Attributes::PendingDatasetTimestamp::TypeInfo; + TypeInfo::DecodableType cppValue; + *aError = DataModel::Decode(aReader, cppValue); + if (*aError != CHIP_NO_ERROR) { + return nil; + } + NSNumber * _Nullable value; + if (cppValue.IsNull()) { + value = nil; + } else { + value = [NSNumber numberWithUnsignedLongLong:cppValue.Value()]; + } + return value; + } default: { break; } diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h index b8dfaa140e03e3..c9850dd3cfd50f 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h @@ -9690,7 +9690,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) /** * Cluster Service Area * - * The Service Area cluster provides an interface for controlling the locations where a device should operate, and for querying the current location. + * The Service Area cluster provides an interface for controlling the areas where a device should operate, and for querying the current area being serviced. */ MTR_PROVISIONALLY_AVAILABLE @interface MTRBaseClusterServiceArea : MTRGenericBaseCluster @@ -9706,9 +9706,7 @@ MTR_PROVISIONALLY_AVAILABLE * * This command is used to skip an area where the device operates. */ -- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams * _Nullable)params completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; -- (void)skipAreaWithCompletion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion - MTR_PROVISIONALLY_AVAILABLE; +- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams *)params completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - (void)readAttributeSupportedAreasWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - (void)subscribeAttributeSupportedAreasWithParams:(MTRSubscribeParams *)params @@ -13493,6 +13491,12 @@ MTR_PROVISIONALLY_AVAILABLE reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE; + (void)readAttributeActiveDatasetTimestampWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; +- (void)readAttributePendingDatasetTimestampWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; +- (void)subscribeAttributePendingDatasetTimestampWithParams:(MTRSubscribeParams *)params + subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished + reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE; ++ (void)readAttributePendingDatasetTimestampWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; + - (void)readAttributeGeneratedCommandListWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - (void)subscribeAttributeGeneratedCommandListWithParams:(MTRSubscribeParams *)params subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished @@ -19800,11 +19804,13 @@ typedef NS_ENUM(uint8_t, MTRServiceAreaSkipAreaStatus) { MTRServiceAreaSkipAreaStatusSuccess MTR_PROVISIONALLY_AVAILABLE = 0x00, MTRServiceAreaSkipAreaStatusInvalidAreaList MTR_PROVISIONALLY_AVAILABLE = 0x01, MTRServiceAreaSkipAreaStatusInvalidInMode MTR_PROVISIONALLY_AVAILABLE = 0x02, + MTRServiceAreaSkipAreaStatusInvalidSkippedArea MTR_PROVISIONALLY_AVAILABLE = 0x03, } MTR_PROVISIONALLY_AVAILABLE; typedef NS_OPTIONS(uint32_t, MTRServiceAreaFeature) { - MTRServiceAreaFeatureListOrder MTR_PROVISIONALLY_AVAILABLE = 0x1, - MTRServiceAreaFeatureSelectWhileRunning MTR_PROVISIONALLY_AVAILABLE = 0x2, + MTRServiceAreaFeatureSelectWhileRunning MTR_PROVISIONALLY_AVAILABLE = 0x1, + MTRServiceAreaFeatureProgressReporting MTR_PROVISIONALLY_AVAILABLE = 0x2, + MTRServiceAreaFeatureMaps MTR_PROVISIONALLY_AVAILABLE = 0x4, } MTR_PROVISIONALLY_AVAILABLE; typedef NS_ENUM(uint8_t, MTRPumpConfigurationAndControlControlMode) { diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm index 2cd1653c867faa..756425bba1b764 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm @@ -65388,11 +65388,7 @@ - (void)selectAreasWithParams:(MTRServiceAreaClusterSelectAreasParams *)params c queue:self.callbackQueue completion:responseHandler]; } -- (void)skipAreaWithCompletion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion -{ - [self skipAreaWithParams:nil completion:completion]; -} -- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams * _Nullable)params completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion +- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams *)params completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion { if (params == nil) { params = [[MTRServiceAreaClusterSkipAreaParams @@ -95178,6 +95174,42 @@ + (void)readAttributeActiveDatasetTimestampWithClusterStateCache:(MTRClusterStat completion:completion]; } +- (void)readAttributePendingDatasetTimestampWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion +{ + using TypeInfo = ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::TypeInfo; + [self.device _readKnownAttributeWithEndpointID:self.endpointID + clusterID:@(TypeInfo::GetClusterId()) + attributeID:@(TypeInfo::GetAttributeId()) + params:nil + queue:self.callbackQueue + completion:completion]; +} + +- (void)subscribeAttributePendingDatasetTimestampWithParams:(MTRSubscribeParams * _Nonnull)params + subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished + reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler +{ + using TypeInfo = ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::TypeInfo; + [self.device _subscribeToKnownAttributeWithEndpointID:self.endpointID + clusterID:@(TypeInfo::GetClusterId()) + attributeID:@(TypeInfo::GetAttributeId()) + params:params + queue:self.callbackQueue + reportHandler:reportHandler + subscriptionEstablished:subscriptionEstablished]; +} + ++ (void)readAttributePendingDatasetTimestampWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion +{ + using TypeInfo = ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::TypeInfo; + [clusterStateCacheContainer + _readKnownCachedAttributeWithEndpointID:static_cast([endpoint unsignedShortValue]) + clusterID:TypeInfo::GetClusterId() + attributeID:TypeInfo::GetAttributeId() + queue:queue + completion:completion]; +} + - (void)readAttributeGeneratedCommandListWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion { using TypeInfo = ThreadBorderRouterManagement::Attributes::GeneratedCommandList::TypeInfo; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h index aa1973220085ec..2e5c8cc7dc1b44 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h @@ -4432,6 +4432,7 @@ typedef NS_ENUM(uint32_t, MTRAttributeIDType) { MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeThreadVersionID MTR_PROVISIONALLY_AVAILABLE = 0x00000002, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeInterfaceEnabledID MTR_PROVISIONALLY_AVAILABLE = 0x00000003, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeActiveDatasetTimestampID MTR_PROVISIONALLY_AVAILABLE = 0x00000004, + MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributePendingDatasetTimestampID MTR_PROVISIONALLY_AVAILABLE = 0x00000005, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeGeneratedCommandListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeGeneratedCommandListID, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeAcceptedCommandListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeEventListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeEventListID, diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm index 3383cab3cec1aa..fa08192303b8fd 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm @@ -7598,6 +7598,10 @@ result = @"ActiveDatasetTimestamp"; break; + case MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributePendingDatasetTimestampID: + result = @"PendingDatasetTimestamp"; + break; + case MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeGeneratedCommandListID: result = @"GeneratedCommandList"; break; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h index 6b441121b72413..9db1460dbcb609 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h @@ -4505,15 +4505,13 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) /** * Cluster Service Area - * The Service Area cluster provides an interface for controlling the locations where a device should operate, and for querying the current location. + * The Service Area cluster provides an interface for controlling the areas where a device should operate, and for querying the current area being serviced. */ MTR_PROVISIONALLY_AVAILABLE @interface MTRClusterServiceArea : MTRGenericCluster - (void)selectAreasWithParams:(MTRServiceAreaClusterSelectAreasParams *)params expectedValues:(NSArray *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSelectAreasResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; -- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams * _Nullable)params expectedValues:(NSArray *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; -- (void)skipAreaWithExpectedValues:(NSArray *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion - MTR_PROVISIONALLY_AVAILABLE; +- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams *)params expectedValues:(NSArray *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - (NSDictionary * _Nullable)readAttributeSupportedAreasWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; @@ -6230,6 +6228,8 @@ MTR_PROVISIONALLY_AVAILABLE - (NSDictionary * _Nullable)readAttributeActiveDatasetTimestampWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; +- (NSDictionary * _Nullable)readAttributePendingDatasetTimestampWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; + - (NSDictionary * _Nullable)readAttributeGeneratedCommandListWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; - (NSDictionary * _Nullable)readAttributeAcceptedCommandListWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm index 361abcf5698306..73ff54e52e36f2 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm @@ -12864,11 +12864,7 @@ - (void)selectAreasWithParams:(MTRServiceAreaClusterSelectAreasParams *)params e completion:responseHandler]; } -- (void)skipAreaWithExpectedValues:(NSArray *> *)expectedValues expectedValueInterval:(NSNumber *)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion -{ - [self skipAreaWithParams:nil expectedValues:expectedValues expectedValueInterval:expectedValueIntervalMs completion:completion]; -} -- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams * _Nullable)params expectedValues:(NSArray *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion +- (void)skipAreaWithParams:(MTRServiceAreaClusterSkipAreaParams *)params expectedValues:(NSArray *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRServiceAreaClusterSkipAreaResponseParams * _Nullable data, NSError * _Nullable error))completion { if (params == nil) { params = [[MTRServiceAreaClusterSkipAreaParams @@ -17340,6 +17336,11 @@ - (void)setPendingDatasetRequestWithParams:(MTRThreadBorderRouterManagementClust return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeThreadBorderRouterManagementID) attributeID:@(MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeActiveDatasetTimestampID) params:params]; } +- (NSDictionary * _Nullable)readAttributePendingDatasetTimestampWithParams:(MTRReadParams * _Nullable)params +{ + return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeThreadBorderRouterManagementID) attributeID:@(MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributePendingDatasetTimestampID) params:params]; +} + - (NSDictionary * _Nullable)readAttributeGeneratedCommandListWithParams:(MTRReadParams * _Nullable)params { return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeThreadBorderRouterManagementID) attributeID:@(MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeGeneratedCommandListID) params:params]; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h index 79cc7d4011add6..b276bb8b1986ac 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h @@ -7671,7 +7671,7 @@ MTR_PROVISIONALLY_AVAILABLE @property (nonatomic, copy) NSNumber * _Nonnull status MTR_PROVISIONALLY_AVAILABLE; -@property (nonatomic, copy) NSString * _Nullable statusText MTR_PROVISIONALLY_AVAILABLE; +@property (nonatomic, copy) NSString * _Nonnull statusText MTR_PROVISIONALLY_AVAILABLE; /** * Initialize an MTRServiceAreaClusterSelectAreasResponseParams with a response-value dictionary @@ -7689,6 +7689,8 @@ MTR_PROVISIONALLY_AVAILABLE MTR_PROVISIONALLY_AVAILABLE @interface MTRServiceAreaClusterSkipAreaParams : NSObject + +@property (nonatomic, copy) NSNumber * _Nonnull skippedArea MTR_PROVISIONALLY_AVAILABLE; /** * Controls whether the command is a timed command (using Timed Invoke). * @@ -7720,7 +7722,7 @@ MTR_PROVISIONALLY_AVAILABLE @property (nonatomic, copy) NSNumber * _Nonnull status MTR_PROVISIONALLY_AVAILABLE; -@property (nonatomic, copy) NSString * _Nullable statusText MTR_PROVISIONALLY_AVAILABLE; +@property (nonatomic, copy) NSString * _Nonnull statusText MTR_PROVISIONALLY_AVAILABLE; /** * Initialize an MTRServiceAreaClusterSkipAreaResponseParams with a response-value dictionary diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm index eeb671df165fec..7aa736277256f6 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm @@ -21891,7 +21891,7 @@ - (instancetype)init _status = @(0); - _statusText = nil; + _statusText = @""; } return self; } @@ -21962,14 +21962,10 @@ - (CHIP_ERROR)_setFieldsFromDecodableStruct:(const chip::app::Clusters::ServiceA self.status = [NSNumber numberWithUnsignedChar:chip::to_underlying(decodableStruct.status)]; } { - if (decodableStruct.statusText.HasValue()) { - self.statusText = AsString(decodableStruct.statusText.Value()); - if (self.statusText == nil) { - CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; - return err; - } - } else { - self.statusText = nil; + self.statusText = AsString(decodableStruct.statusText); + if (self.statusText == nil) { + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + return err; } } return CHIP_NO_ERROR; @@ -21981,6 +21977,8 @@ @implementation MTRServiceAreaClusterSkipAreaParams - (instancetype)init { if (self = [super init]) { + + _skippedArea = @(0); _timedInvokeTimeoutMs = nil; _serverSideProcessingTimeout = nil; } @@ -21991,6 +21989,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone; { auto other = [[MTRServiceAreaClusterSkipAreaParams alloc] init]; + other.skippedArea = self.skippedArea; other.timedInvokeTimeoutMs = self.timedInvokeTimeoutMs; other.serverSideProcessingTimeout = self.serverSideProcessingTimeout; @@ -21999,7 +21998,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone; - (NSString *)description { - NSString * descriptionString = [NSString stringWithFormat:@"<%@: >", NSStringFromClass([self class])]; + NSString * descriptionString = [NSString stringWithFormat:@"<%@: skippedArea:%@; >", NSStringFromClass([self class]), _skippedArea]; return descriptionString; } @@ -22011,6 +22010,9 @@ - (CHIP_ERROR)_encodeToTLVReader:(chip::System::PacketBufferTLVReader &)reader { chip::app::Clusters::ServiceArea::Commands::SkipArea::Type encodableStruct; ListFreer listFreer; + { + encodableStruct.skippedArea = self.skippedArea.unsignedIntValue; + } auto buffer = chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSizeWithoutReserve, 0); if (buffer.IsNull()) { @@ -22057,7 +22059,7 @@ - (instancetype)init _status = @(0); - _statusText = nil; + _statusText = @""; } return self; } @@ -22128,14 +22130,10 @@ - (CHIP_ERROR)_setFieldsFromDecodableStruct:(const chip::app::Clusters::ServiceA self.status = [NSNumber numberWithUnsignedChar:chip::to_underlying(decodableStruct.status)]; } { - if (decodableStruct.statusText.HasValue()) { - self.statusText = AsString(decodableStruct.statusText.Value()); - if (self.statusText == nil) { - CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; - return err; - } - } else { - self.statusText = nil; + self.statusText = AsString(decodableStruct.statusText); + if (self.statusText == nil) { + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + return err; } } return CHIP_NO_ERROR; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h index db765a29de8913..8590079420a854 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h @@ -1593,12 +1593,16 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @property (nonatomic, copy) NSNumber * _Nullable dataIndex MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)); @end +MTR_PROVISIONALLY_AVAILABLE +@interface MTRServiceAreaClusterLandmarkInfoStruct : NSObject +@property (nonatomic, copy) NSNumber * _Nonnull landmarkTag MTR_PROVISIONALLY_AVAILABLE; +@property (nonatomic, copy) NSNumber * _Nullable positionTag MTR_PROVISIONALLY_AVAILABLE; +@end + MTR_PROVISIONALLY_AVAILABLE @interface MTRServiceAreaClusterAreaInfoStruct : NSObject @property (nonatomic, copy) MTRDataTypeLocationDescriptorStruct * _Nullable locationInfo MTR_PROVISIONALLY_AVAILABLE; -@property (nonatomic, copy) NSNumber * _Nullable landmarkTag MTR_PROVISIONALLY_AVAILABLE; -@property (nonatomic, copy) NSNumber * _Nullable positionTag MTR_PROVISIONALLY_AVAILABLE; -@property (nonatomic, copy) NSNumber * _Nullable surfaceTag MTR_PROVISIONALLY_AVAILABLE; +@property (nonatomic, copy) MTRServiceAreaClusterLandmarkInfoStruct * _Nullable landmarkInfo MTR_PROVISIONALLY_AVAILABLE; @end MTR_PROVISIONALLY_AVAILABLE diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm index 8bcfc3af65f9a7..02aac9e24ec0ae 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm @@ -6640,18 +6640,44 @@ - (NSString *)description @end -@implementation MTRServiceAreaClusterAreaInfoStruct +@implementation MTRServiceAreaClusterLandmarkInfoStruct - (instancetype)init { if (self = [super init]) { - _locationInfo = nil; - - _landmarkTag = nil; + _landmarkTag = @(0); _positionTag = nil; + } + return self; +} + +- (id)copyWithZone:(NSZone * _Nullable)zone +{ + auto other = [[MTRServiceAreaClusterLandmarkInfoStruct alloc] init]; + + other.landmarkTag = self.landmarkTag; + other.positionTag = self.positionTag; - _surfaceTag = nil; + return other; +} + +- (NSString *)description +{ + NSString * descriptionString = [NSString stringWithFormat:@"<%@: landmarkTag:%@; positionTag:%@; >", NSStringFromClass([self class]), _landmarkTag, _positionTag]; + return descriptionString; +} + +@end + +@implementation MTRServiceAreaClusterAreaInfoStruct +- (instancetype)init +{ + if (self = [super init]) { + + _locationInfo = nil; + + _landmarkInfo = nil; } return self; } @@ -6661,16 +6687,14 @@ - (id)copyWithZone:(NSZone * _Nullable)zone auto other = [[MTRServiceAreaClusterAreaInfoStruct alloc] init]; other.locationInfo = self.locationInfo; - other.landmarkTag = self.landmarkTag; - other.positionTag = self.positionTag; - other.surfaceTag = self.surfaceTag; + other.landmarkInfo = self.landmarkInfo; return other; } - (NSString *)description { - NSString * descriptionString = [NSString stringWithFormat:@"<%@: locationInfo:%@; landmarkTag:%@; positionTag:%@; surfaceTag:%@; >", NSStringFromClass([self class]), _locationInfo, _landmarkTag, _positionTag, _surfaceTag]; + NSString * descriptionString = [NSString stringWithFormat:@"<%@: locationInfo:%@; landmarkInfo:%@; >", NSStringFromClass([self class]), _locationInfo, _landmarkInfo]; return descriptionString; } diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 055a6c9efbcc3e..10884a64f4ffe4 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -302,6 +302,8 @@ 99AECC802798A57F00B6355B /* MTRCommissioningParameters.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99AECC7F2798A57E00B6355B /* MTRCommissioningParameters.mm */; }; 99C65E10267282F1003402F6 /* MTRControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 99C65E0F267282F1003402F6 /* MTRControllerTests.m */; }; 99D466E12798936D0089A18F /* MTRCommissioningParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 99D466E02798936D0089A18F /* MTRCommissioningParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9BDA2A062C5D9AF800A32BDD /* MTRDevice_Concrete.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9BDA2A052C5D9AF800A32BDD /* MTRDevice_Concrete.mm */; }; + 9BDA2A082C5D9AFB00A32BDD /* MTRDevice_Concrete.h in Headers */ = {isa = PBXBuildFile; fileRef = 9BDA2A072C5D9AFB00A32BDD /* MTRDevice_Concrete.h */; }; AF1CB86E2874B03B00865A96 /* MTROTAProviderDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = AF1CB86D2874B03B00865A96 /* MTROTAProviderDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF1CB8702874B04C00865A96 /* MTROTAProviderDelegateBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = AF1CB86F2874B04C00865A96 /* MTROTAProviderDelegateBridge.h */; }; AF5F90FF2878D351005503FA /* MTROTAProviderDelegateBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = AF5F90FE2878D351005503FA /* MTROTAProviderDelegateBridge.mm */; }; @@ -729,6 +731,8 @@ 99AECC7F2798A57E00B6355B /* MTRCommissioningParameters.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRCommissioningParameters.mm; sourceTree = ""; }; 99C65E0F267282F1003402F6 /* MTRControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRControllerTests.m; sourceTree = ""; }; 99D466E02798936D0089A18F /* MTRCommissioningParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRCommissioningParameters.h; sourceTree = ""; }; + 9BDA2A052C5D9AF800A32BDD /* MTRDevice_Concrete.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDevice_Concrete.mm; sourceTree = ""; }; + 9BDA2A072C5D9AFB00A32BDD /* MTRDevice_Concrete.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDevice_Concrete.h; sourceTree = ""; }; AF1CB86D2874B03B00865A96 /* MTROTAProviderDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTROTAProviderDelegate.h; sourceTree = ""; }; AF1CB86F2874B04C00865A96 /* MTROTAProviderDelegateBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTROTAProviderDelegateBridge.h; sourceTree = ""; }; AF5F90FE2878D351005503FA /* MTROTAProviderDelegateBridge.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROTAProviderDelegateBridge.mm; sourceTree = ""; }; @@ -1301,6 +1305,8 @@ 7596A84A287636C1004DAE0E /* MTRDevice_Internal.h */, 7596A84228762729004DAE0E /* MTRDevice.h */, 7596A84328762729004DAE0E /* MTRDevice.mm */, + 9BDA2A072C5D9AFB00A32BDD /* MTRDevice_Concrete.h */, + 9BDA2A052C5D9AF800A32BDD /* MTRDevice_Concrete.mm */, 88EBF8CB27FABDD500686BC1 /* MTRDeviceAttestationDelegate.h */, 7534F12728BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h */, 7534F12628BFF20300390851 /* MTRDeviceAttestationDelegate.mm */, @@ -1600,6 +1606,7 @@ 5117DD3929A931AE00FFA1AA /* MTROperationalBrowser.h in Headers */, 2C1B027B2641DB4E00780EF1 /* MTROperationalCredentialsDelegate.h in Headers */, 5173A47529C0E2ED00F67F48 /* MTRFabricInfo_Internal.h in Headers */, + 9BDA2A082C5D9AFB00A32BDD /* MTRDevice_Concrete.h in Headers */, 3D843717294979230070D20A /* MTRClusters_Internal.h in Headers */, 7596A85728788557004DAE0E /* MTRClusters.h in Headers */, 99D466E12798936D0089A18F /* MTRCommissioningParameters.h in Headers */, @@ -2001,6 +2008,7 @@ 7596A85528788557004DAE0E /* MTRClusters.mm in Sources */, 88EBF8CF27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm in Sources */, 5A6FEC9827B5C6AF00F25F42 /* MTRDeviceOverXPC.mm in Sources */, + 9BDA2A062C5D9AF800A32BDD /* MTRDevice_Concrete.mm in Sources */, 514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */, 1E4D655229C30A8700BC3478 /* MTRCommissionableBrowser.mm in Sources */, 88FA798E2B7B257100CD4B6F /* MTRMetricsCollector.mm in Sources */, diff --git a/src/darwin/Framework/chip_xcode_build_connector.sh b/src/darwin/Framework/chip_xcode_build_connector.sh index 33d4a441bf2019..b4a8195b8e0c52 100755 --- a/src/darwin/Framework/chip_xcode_build_connector.sh +++ b/src/darwin/Framework/chip_xcode_build_connector.sh @@ -103,6 +103,7 @@ declare -a args=( 'chip_enable_python_modules=false' 'chip_device_config_enable_dynamic_mrp_config=true' 'chip_log_message_max_size=4096' # might as well allow nice long log messages + 'chip_logging_backend="none"' # os_log() is integrated via CHIP_SYSTEM_CONFIG_PLATFORM_LOG 'chip_disable_platform_kvs=true' 'enable_fuzz_test_targets=false' "target_cpu=\"$target_cpu\"" diff --git a/src/lib/core/core.gni b/src/lib/core/core.gni index ba1d91fd28ca42..f2189198e36131 100644 --- a/src/lib/core/core.gni +++ b/src/lib/core/core.gni @@ -54,7 +54,23 @@ declare_args() { # Configure chip logging to output through external logging implementation. # External code will need to provide implementation for CHIP log output # function (LogV), which is defined in "src/platform/logging/LogV.h". + # Same as setting chip_logging_backend = "external" chip_use_external_logging = false +} + +declare_args() { + # Logging backend to use for targets that don't link a specific log + # backend (e.g. command line utilites usually use 'stdio'). Options: + # 'platform' - The default log backend of the device platform + # 'external' - External LogV implementation (src/platform/logging/LogV.h) + # 'none' - Discard all log output + # 'stdio' - Print to stdout + # 'syslog' - POSIX syslog() + if (chip_use_external_logging) { + chip_logging_backend = "external" + } else { + chip_logging_backend = "platform" + } # Enable short error strings. chip_config_short_error_str = false @@ -117,6 +133,15 @@ if (chip_target_style == "") { } } +assert( + chip_logging_backend == "platform" || chip_logging_backend == "external" || + chip_logging_backend == "none" || chip_logging_backend == "stdio" || + chip_logging_backend == "syslog", + "Please select a valid logging backend: platform, external, none, stdio, syslog") +assert( + !chip_use_external_logging || chip_logging_backend == "external", + "Setting chip_use_external_logging = true conflicts with selected chip_logging_backend") + assert(chip_target_style == "unix" || chip_target_style == "embedded", "Please select a valid target style: unix, embedded") diff --git a/src/platform/Darwin/BUILD.gn b/src/platform/Darwin/BUILD.gn index df4c0774e91627..0a56ce1eac4364 100644 --- a/src/platform/Darwin/BUILD.gn +++ b/src/platform/Darwin/BUILD.gn @@ -151,7 +151,6 @@ static_library("logging") { sources = [ "Logging.h", "Logging.mm", - "LoggingImpl.cpp", ] deps = [ diff --git a/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp b/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp index e1efc5357520af..b08b0ab47321b3 100644 --- a/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp +++ b/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp @@ -167,6 +167,12 @@ void GenericOpenThreadBorderRouterDelegate::OnPlatformEventHandler(const DeviceL delegate->mpAttributeChangeCallback->ReportAttributeChanged(Attributes::ActiveDatasetTimestamp::Id); }); } + if (event->ThreadStateChange.OpenThread.Flags & OT_CHANGED_PENDING_DATASET) + { + DeviceLayer::SystemLayer().ScheduleLambda([delegate]() { + delegate->mpAttributeChangeCallback->ReportAttributeChanged(Attributes::PendingDatasetTimestamp::Id); + }); + } } } diff --git a/src/platform/logging/BUILD.gn b/src/platform/logging/BUILD.gn index 9b26f40569f428..eb3c62154ce436 100644 --- a/src/platform/logging/BUILD.gn +++ b/src/platform/logging/BUILD.gn @@ -8,8 +8,8 @@ import("${chip_root}/src/lib/core/core.gni") import("${chip_root}/src/lib/shell/shell_device.gni") import("${chip_root}/src/platform/device.gni") -source_set("default") { - if (!chip_use_external_logging) { +group("default") { + if (chip_logging_backend == "platform") { deps = [] if (chip_use_pw_logging) { @@ -63,7 +63,7 @@ source_set("default") { } else if (chip_device_platform == "qpg") { deps += [ "${chip_root}/src/platform/qpg:logging" ] } else if (chip_device_platform == "darwin") { - deps += [ "${chip_root}/src/platform/Darwin:logging" ] + deps += [ ":stdio" ] # For tools / examples. The framework uses "none". } else if (chip_device_platform == "mw320") { deps += [ "${chip_root}/src/platform/nxp/mw320:logging" ] } else if (chip_device_platform == "k32w0" || @@ -82,22 +82,43 @@ source_set("default") { assert(chip_device_platform == "fake" || chip_device_platform == "external" || chip_device_platform == "none") } + } else if (chip_logging_backend == "none" || + chip_logging_backend == "stdio" || + chip_logging_backend == "syslog") { + deps = [ ":${chip_logging_backend}" ] + } else { + assert(chip_logging_backend == "external") } } source_set("headers") { public = [ "LogV.h" ] + public_deps = [ + "${chip_root}/src/lib/support:attributes", + "${chip_root}/src/lib/support:logging_constants", + ] +} + +source_set("none") { + sources = [ "impl/None.cpp" ] + deps = [ + ":headers", + "${chip_root}/src/platform:platform_base", + ] } source_set("stdio") { - sources = [ "impl/stdio/Logging.cpp" ] + sources = [ "impl/Stdio.cpp" ] + deps = [ + ":headers", + "${chip_root}/src/platform:platform_base", + ] +} +source_set("syslog") { + sources = [ "impl/Syslog.cpp" ] deps = [ ":headers", - "${chip_root}/src/lib/core:chip_config_header", - "${chip_root}/src/lib/support:attributes", - "${chip_root}/src/lib/support:logging_constants", - "${chip_root}/src/platform:platform_config_header", - "${chip_root}/src/platform/logging:headers", + "${chip_root}/src/platform:platform_base", ] } diff --git a/src/platform/Darwin/LoggingImpl.cpp b/src/platform/logging/impl/None.cpp similarity index 63% rename from src/platform/Darwin/LoggingImpl.cpp rename to src/platform/logging/impl/None.cpp index 2d6c1b3b744294..10f1b4dc87f54a 100644 --- a/src/platform/Darwin/LoggingImpl.cpp +++ b/src/platform/logging/impl/None.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2024 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. @@ -21,12 +21,11 @@ namespace chip { namespace Logging { namespace Platform { -void LogV(const char * module, uint8_t category, const char * msg, va_list v) +void LogV(const char *, uint8_t, const char *, va_list) { - // ChipPlatformLog expands to an os_log call directly (see Logging.h), so - // we don't need to do anything further here. However his function and the - // call to it still exist because of scenarios where a different logging - // backend (usually stdio) is swapped in at link time, e.g. for unit tests. + // This backend discards all log messages. This is useful when all log output + // is routed via `SetLogRedirectCallback()` and/or platform logging + // integration at the log macro level (`CHIP_SYSTEM_CONFIG_PLATFORM_LOG`). } } // namespace Platform diff --git a/src/platform/logging/impl/stdio/Logging.cpp b/src/platform/logging/impl/Stdio.cpp similarity index 73% rename from src/platform/logging/impl/stdio/Logging.cpp rename to src/platform/logging/impl/Stdio.cpp index d36fe2aae2dc64..47338de500b29d 100644 --- a/src/platform/logging/impl/stdio/Logging.cpp +++ b/src/platform/logging/impl/Stdio.cpp @@ -1,4 +1,19 @@ -/* See Project CHIP LICENSE file for licensing information. */ +/* + * + * Copyright (c) 2021-2024 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 diff --git a/src/platform/logging/impl/Syslog.cpp b/src/platform/logging/impl/Syslog.cpp new file mode 100644 index 00000000000000..163e6d7398aefe --- /dev/null +++ b/src/platform/logging/impl/Syslog.cpp @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2024 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 +#include + +namespace chip { +namespace Logging { +namespace Platform { + +namespace { +int LogPriority(uint8_t category) +{ + switch (category) + { + case kLogCategory_Error: + return LOG_ERR; + case kLogCategory_Progress: + return LOG_NOTICE; + default: + return LOG_DEBUG; + } +} +} // namespace + +void LogV(const char * module, uint8_t category, const char * msg, va_list v) +{ + static std::mutex sMutex; + static bool sInitialized = false; + static char sBuffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; + std::lock_guard guard(sMutex); + + if (!sInitialized) + { + openlog(nullptr, 0, LOG_DAEMON); + sInitialized = true; + } + + // Pre-format the message so we can include the module name + vsnprintf(sBuffer, sizeof(sBuffer), msg, v); + syslog(LogPriority(category), "%s: %s", module, sBuffer); +} + +} // namespace Platform +} // namespace Logging +} // namespace chip diff --git a/src/platform/stm32/BUILD.gn b/src/platform/stm32/BUILD.gn index 9b2ed9008c858a..061a2c4c268465 100644 --- a/src/platform/stm32/BUILD.gn +++ b/src/platform/stm32/BUILD.gn @@ -39,7 +39,6 @@ static_library("stm32") { sources = [ "../FreeRTOS/SystemTimeSupport.cpp", "../SingletonConfigurationManager.cpp", - "../logging/impl/stdio/Logging.cpp", "BLEManagerImpl.cpp", "BLEManagerImpl.h", "BlePlatformConfig.h", @@ -69,7 +68,7 @@ static_library("stm32") { "SystemPlatformConfig.h", ] - deps += [ "${chip_root}/src/platform/logging:headers" ] + deps += [ "${chip_root}/src/platform/logging:stdio" ] } public = [ diff --git a/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h b/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h index cf9a55365b9191..fb1aedc03e8d6e 100644 --- a/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h +++ b/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h @@ -227,7 +227,6 @@ class DLL_EXPORT IdentificationDeclaration { ChipLogDetail(AppServer, "\tpairing hint: %d", mPairingHint); } - if (mNoPasscode) { ChipLogDetail(AppServer, "\tno passcode: true"); @@ -349,6 +348,9 @@ class DLL_EXPORT CommissionerDeclaration void SetQRCodeDisplayed(bool newValue) { mQRCodeDisplayed = newValue; }; bool GetQRCodeDisplayed() const { return mQRCodeDisplayed; }; + void SetCancelPasscode(bool newValue) { mCancelPasscode = newValue; }; + bool GetCancelPasscode() const { return mCancelPasscode; }; + /** * Writes the CommissionerDeclaration message to the given buffer. * @@ -390,6 +392,10 @@ class DLL_EXPORT CommissionerDeclaration { ChipLogDetail(AppServer, "\tQR code displayed: true"); } + if (mCancelPasscode) + { + ChipLogDetail(AppServer, "\tPasscode cancelled: true"); + } ChipLogDetail(AppServer, "---- Commissioner Declaration End ----"); } @@ -403,6 +409,7 @@ class DLL_EXPORT CommissionerDeclaration kPasscodeDialogDisplayedTag, kCommissionerPasscodeTag, kQRCodeDisplayedTag, + kCancelPasscodeTag, kMaxNum = UINT8_MAX }; @@ -413,6 +420,7 @@ class DLL_EXPORT CommissionerDeclaration bool mPasscodeDialogDisplayed = false; bool mCommissionerPasscode = false; bool mQRCodeDisplayed = false; + bool mCancelPasscode = false; }; class DLL_EXPORT InstanceNameResolver diff --git a/src/protocols/user_directed_commissioning/UserDirectedCommissioningClient.cpp b/src/protocols/user_directed_commissioning/UserDirectedCommissioningClient.cpp index aeb5691be2554c..f0ee3bbe69abfd 100644 --- a/src/protocols/user_directed_commissioning/UserDirectedCommissioningClient.cpp +++ b/src/protocols/user_directed_commissioning/UserDirectedCommissioningClient.cpp @@ -223,6 +223,9 @@ CHIP_ERROR CommissionerDeclaration::ReadPayload(uint8_t * udcPayload, size_t pay case kQRCodeDisplayedTag: err = reader.Get(mQRCodeDisplayed); break; + case kCancelPasscodeTag: + err = reader.Get(mCancelPasscode); + break; } } diff --git a/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp b/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp index fb31049d1d99a9..118ba7c7c524e4 100644 --- a/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp +++ b/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp @@ -435,6 +435,8 @@ uint32_t CommissionerDeclaration::WritePayload(uint8_t * payloadBuffer, size_t p LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kQRCodeDisplayedTag), mQRCodeDisplayed)), LogErrorOnFailure(err)); + VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kCancelPasscodeTag), mCancelPasscode)), + LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.EndContainer(outerContainerType)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.Finalize()), LogErrorOnFailure(err)); diff --git a/src/python_testing/TC_ACL_2_2.py b/src/python_testing/TC_ACL_2_2.py index 252868a58d4ac3..a1e755b7397f88 100644 --- a/src/python_testing/TC_ACL_2_2.py +++ b/src/python_testing/TC_ACL_2_2.py @@ -14,6 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import chip.clusters as Clusters from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts diff --git a/src/python_testing/TC_BOOLCFG_2_1.py b/src/python_testing/TC_BOOLCFG_2_1.py index d6ed97540056db..484d590b258b6d 100644 --- a/src/python_testing/TC_BOOLCFG_2_1.py +++ b/src/python_testing/TC_BOOLCFG_2_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import functools import logging from operator import ior diff --git a/src/python_testing/TC_BOOLCFG_3_1.py b/src/python_testing/TC_BOOLCFG_3_1.py index fb45e60ab38f8d..8e42140b35424c 100644 --- a/src/python_testing/TC_BOOLCFG_3_1.py +++ b/src/python_testing/TC_BOOLCFG_3_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging from random import choice diff --git a/src/python_testing/TC_BOOLCFG_4_1.py b/src/python_testing/TC_BOOLCFG_4_1.py index 74f81b7bfd79bd..3240c27ea219cc 100644 --- a/src/python_testing/TC_BOOLCFG_4_1.py +++ b/src/python_testing/TC_BOOLCFG_4_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_BOOLCFG_4_2.py b/src/python_testing/TC_BOOLCFG_4_2.py index c0a74a9c46538a..b1496c49872fb0 100644 --- a/src/python_testing/TC_BOOLCFG_4_2.py +++ b/src/python_testing/TC_BOOLCFG_4_2.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --enable-key 000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg PIXIT.BOOLCFG.TEST_EVENT_TRIGGER_KEY:000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_BOOLCFG_4_3.py b/src/python_testing/TC_BOOLCFG_4_3.py index 90106efc2b8ab4..ea7ba2cac5f772 100644 --- a/src/python_testing/TC_BOOLCFG_4_3.py +++ b/src/python_testing/TC_BOOLCFG_4_3.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --enable-key 000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg PIXIT.BOOLCFG.TEST_EVENT_TRIGGER_KEY:000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_BOOLCFG_4_4.py b/src/python_testing/TC_BOOLCFG_4_4.py index b60ef12fd16f5f..2ae6dae2c763b4 100644 --- a/src/python_testing/TC_BOOLCFG_4_4.py +++ b/src/python_testing/TC_BOOLCFG_4_4.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --enable-key 000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg PIXIT.BOOLCFG.TEST_EVENT_TRIGGER_KEY:000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_BOOLCFG_5_1.py b/src/python_testing/TC_BOOLCFG_5_1.py index 90868fd8e6316c..f99fb3d8e48ca5 100644 --- a/src/python_testing/TC_BOOLCFG_5_1.py +++ b/src/python_testing/TC_BOOLCFG_5_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --enable-key 000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg PIXIT.BOOLCFG.TEST_EVENT_TRIGGER_KEY:000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_BOOLCFG_5_2.py b/src/python_testing/TC_BOOLCFG_5_2.py index 6e2657698a9d5c..cd176faf60574c 100644 --- a/src/python_testing/TC_BOOLCFG_5_2.py +++ b/src/python_testing/TC_BOOLCFG_5_2.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --enable-key 000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg PIXIT.BOOLCFG.TEST_EVENT_TRIGGER_KEY:000102030405060708090a0b0c0d0e0f --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_CC_2_2.py b/src/python_testing/TC_CC_2_2.py index 66ed2a8f563777..4f6a21b1ba2771 100644 --- a/src/python_testing/TC_CC_2_2.py +++ b/src/python_testing/TC_CC_2_2.py @@ -160,8 +160,7 @@ def accumulate_reports(): def check_report_counts(attr: ClusterObjects.ClusterAttributeDescriptor): count = sub_handler.attribute_report_counts[attr] - # TODO: should be 12 - see issue #34646 - # asserts.assert_less_equal(count, 12, "More than 12 reports received") + asserts.assert_less_equal(count, 12, "More than 12 reports received") asserts.assert_less_equal(count, gather_time, f"More than {gather_time} reports received") self.step(9) @@ -270,17 +269,16 @@ def check_report_counts(attr: ClusterObjects.ClusterAttributeDescriptor): time.sleep(20) self.step(34) - # TODO: Re-enable checks 34, 36 when #34643 is addressed logging.info(f'received reports: {sub_handler.attribute_reports[cc.Attributes.RemainingTime]}') - # count = sub_handler.attribute_report_counts[cc.Attributes.RemainingTime] - # asserts.assert_equal(count, 3, "Unexpected number of reports received") + count = sub_handler.attribute_report_counts[cc.Attributes.RemainingTime] + asserts.assert_equal(count, 3, "Unexpected number of reports received") self.step(35) asserts.assert_equal(sub_handler.attribute_reports[cc.Attributes.RemainingTime][0].value, 100, "Unexpected first report") self.step(36) - # asserts.assert_almost_equal( - # sub_handler.attribute_reports[cc.Attributes.RemainingTime][1].value, 0, delta=10, msg="Unexpected second report") + asserts.assert_almost_equal( + sub_handler.attribute_reports[cc.Attributes.RemainingTime][1].value, 150, delta=10, msg="Unexpected second report") self.step(37) asserts.assert_equal(sub_handler.attribute_reports[cc.Attributes.RemainingTime][-1].value, 0, "Unexpected last report") diff --git a/src/python_testing/TC_VALCC_2_1.py b/src/python_testing/TC_VALCC_2_1.py index c9f40d34694432..bb74f75d362e90 100644 --- a/src/python_testing/TC_VALCC_2_1.py +++ b/src/python_testing/TC_VALCC_2_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_VALCC_3_1.py b/src/python_testing/TC_VALCC_3_1.py index 1b73976bdd9aae..e24c09db66c35d 100644 --- a/src/python_testing/TC_VALCC_3_1.py +++ b/src/python_testing/TC_VALCC_3_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import time import chip.clusters as Clusters diff --git a/src/python_testing/TC_VALCC_3_2.py b/src/python_testing/TC_VALCC_3_2.py index 1cd672f6a3ee2c..9ef0886d8b0cba 100644 --- a/src/python_testing/TC_VALCC_3_2.py +++ b/src/python_testing/TC_VALCC_3_2.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import time diff --git a/src/python_testing/TC_VALCC_3_3.py b/src/python_testing/TC_VALCC_3_3.py index d987516e67ccaa..ddf3ba83d1b5c7 100644 --- a/src/python_testing/TC_VALCC_3_3.py +++ b/src/python_testing/TC_VALCC_3_3.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import time diff --git a/src/python_testing/TC_VALCC_3_4.py b/src/python_testing/TC_VALCC_3_4.py index cef6bb81ee5ff0..906854011aceb3 100644 --- a/src/python_testing/TC_VALCC_3_4.py +++ b/src/python_testing/TC_VALCC_3_4.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_VALCC_4_1.py b/src/python_testing/TC_VALCC_4_1.py index f9a1488ddf754d..183151af614893 100644 --- a/src/python_testing/TC_VALCC_4_1.py +++ b/src/python_testing/TC_VALCC_4_1.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import time import chip.clusters as Clusters diff --git a/src/python_testing/TC_VALCC_4_2.py b/src/python_testing/TC_VALCC_4_2.py index 85ee4f8d4d34ec..7e331f5f0fa33a 100644 --- a/src/python_testing/TC_VALCC_4_2.py +++ b/src/python_testing/TC_VALCC_4_2.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import time diff --git a/src/python_testing/TC_VALCC_4_3.py b/src/python_testing/TC_VALCC_4_3.py index d696464827c20f..cdb8c38ca7e286 100644 --- a/src/python_testing/TC_VALCC_4_3.py +++ b/src/python_testing/TC_VALCC_4_3.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_VALCC_4_4.py b/src/python_testing/TC_VALCC_4_4.py index 95aa1743187ee7..4fd1692da370bf 100644 --- a/src/python_testing/TC_VALCC_4_4.py +++ b/src/python_testing/TC_VALCC_4_4.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import logging import chip.clusters as Clusters diff --git a/src/python_testing/TC_VALCC_4_5.py b/src/python_testing/TC_VALCC_4_5.py index 7dd21b89eead6e..b07f4281d1c7c6 100644 --- a/src/python_testing/TC_VALCC_4_5.py +++ b/src/python_testing/TC_VALCC_4_5.py @@ -15,6 +15,15 @@ # limitations under the License. # +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + import time import chip.clusters as Clusters diff --git a/src/python_testing/execute_python_tests.py b/src/python_testing/execute_python_tests.py new file mode 100644 index 00000000000000..61b650a8078693 --- /dev/null +++ b/src/python_testing/execute_python_tests.py @@ -0,0 +1,128 @@ +# +# Copyright (c) 2024 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 argparse +import glob +import os +import subprocess + +# Function to load --app argument environment variables from a file + + +def load_env_from_yaml(file_path): + """ + Load environment variables from the specified YAML file. + + The YAML file contains key-value pairs that define --app environment variables + required for the test scripts to run. These variables configurations needed during the test execution. + + This function reads the YAML file and sets the environment variables + in the current process's environment using os.environ. + + Args: + file_path (str): The path to the YAML file containing the environment variables. + """ + with open(file_path, 'r') as file: + for line in file: + if line.strip(): # Skip empty lines + key, value = line.strip().split(': ', 1) + os.environ[key] = value + + +def main(search_directory, env_file): + # Determine the root directory of the CHIP project + chip_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) + + # Load environment variables from the specified file + load_env_from_yaml(env_file) + + # Define the base command to run tests + base_command = os.path.join(chip_root, "scripts/tests/run_python_test.py") + + # Define the files and patterns to exclude + excluded_patterns = { + "MinimalRepresentation.py", + "TC_CNET_4_4.py", + "TC_CCTRL_2_1.py", + "TC_CCTRL_2_2.py", + "TC_DGGEN_3_2.py", + "TC_EEVSE_Utils.py", + "TC_ECOINFO_2_1.py", + "TC_ECOINFO_2_2.py", + "TC_EWATERHTRBase.py", + "TC_EnergyReporting_Utils.py", + "TC_OpstateCommon.py", + "TC_pics_checker.py", + "TC_TMP_2_1.py", + "TC_MCORE_FS_1_1.py", + "TC_MCORE_FS_1_2.py", + "TC_MCORE_FS_1_3.py", + "TC_BRBINFO_4_1.py", + "TestCommissioningTimeSync.py", + "TestConformanceSupport.py", + "TestChoiceConformanceSupport.py", + "TC_DEMTestBase.py", + "choice_conformance_support.py", + "TestIdChecks.py", + "TestSpecParsingDeviceType.py", + "TestMatterTestingSupport.py", + "TestSpecParsingSupport.py", + "TestTimeSyncTrustedTimeSource.py", + "basic_composition_support.py", + "conformance_support.py", + "drlk_2_x_common.py", + "execute_python_tests.py", + "global_attribute_ids.py", + "hello_external_runner.py", + "hello_test.py", + "matter_testing_support.py", + "pics_support.py", + "spec_parsing_support.py", + "taglist_and_topology_test_support.py", + "test_plan_support.py", + "test_plan_table_generator.py" + } + + """ + Explanation for excluded files: + The above list of files are excluded from the tests as they are either not app-specific tests + or are run through a different block of tests mentioned in tests.yaml. + This is to ensure that only relevant test scripts are executed, avoiding redundancy and ensuring + the correctness of the test suite. + """ + + # Get all .py files in the directory + all_python_files = glob.glob(os.path.join(search_directory, "*.py")) + + # Filter out the files matching the excluded patterns + python_files = [file for file in all_python_files if os.path.basename(file) not in excluded_patterns] + + # Run each script with the base command + for script in python_files: + full_command = f"{base_command} --load-from-env {env_file} --script {script}" + print(f"Running command: {full_command}") + subprocess.run(full_command, shell=True, check=True) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Run Python test scripts.") + parser.add_argument("--search-directory", type=str, default="src/python_testing", + help="Directory to search for Python scripts.") + parser.add_argument("--env-file", type=str, default="/tmp/test_env.yaml", help="Path to the environment variables file.") + + args = parser.parse_args() + main(args.search_directory, args.env_file) diff --git a/src/system/SystemConfig.h b/src/system/SystemConfig.h index b37cfe555278dd..58339df4be1e47 100644 --- a/src/system/SystemConfig.h +++ b/src/system/SystemConfig.h @@ -558,7 +558,9 @@ struct LwIPEvent; * @def CHIP_SYSTEM_CONFIG_PLATFORM_LOG * * @brief - * Defines whether (1) or not (0) the system uses a platform-specific logging implementation. + * Defines whether (1) or not (0) the system uses a platform-specific implementation of + * ChipLog* macros. Most platforms do not use this option and simply provide a logging + * backend that implements LogV. * * See CHIPLogging.h for details. */ diff --git a/third_party/tizen/tizen_sdk.gni b/third_party/tizen/tizen_sdk.gni index ebbc8dadd077d0..7f711c693ea599 100644 --- a/third_party/tizen/tizen_sdk.gni +++ b/third_party/tizen/tizen_sdk.gni @@ -189,7 +189,7 @@ template("tizen_qemu_mkisofs") { "mkisofs", "-input-charset=default", "-VCHIP", # Volume ID = CHIP - "-JRU", # Joliet + Rock Ridge with untranslated filenames + "-JrU", # Joliet + Rock Ridge with untranslated filenames ] # Exclude files from the ISO image which might otherwise be included diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h b/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h index 8a951e2a54a575..b7305aa2cbfdab 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h @@ -2614,6 +2614,7 @@ static auto __attribute__((unused)) EnsureKnownEnumValue(ServiceArea::SkipAreaSt case EnumType::kSuccess: case EnumType::kInvalidAreaList: case EnumType::kInvalidInMode: + case EnumType::kInvalidSkippedArea: return val; default: return EnumType::kUnknownEnumValue; diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h b/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h index a0407517696744..b7986208f1dca8 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h @@ -3848,21 +3848,23 @@ enum class SelectAreasStatus : uint8_t // Enum for SkipAreaStatus enum class SkipAreaStatus : uint8_t { - kSuccess = 0x00, - kInvalidAreaList = 0x01, - kInvalidInMode = 0x02, + kSuccess = 0x00, + kInvalidAreaList = 0x01, + kInvalidInMode = 0x02, + kInvalidSkippedArea = 0x03, // All received enum values that are not listed above will be mapped // to kUnknownEnumValue. This is a helper enum value that should only // be used by code to process how it handles receiving and unknown // enum value. This specific should never be transmitted. - kUnknownEnumValue = 3, + kUnknownEnumValue = 4, }; // Bitmap for Feature enum class Feature : uint32_t { - kListOrder = 0x1, - kSelectWhileRunning = 0x2, + kSelectWhileRunning = 0x1, + kProgressReporting = 0x2, + kMaps = 0x4, }; } // namespace ServiceArea diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp index bea7e010160be2..90eddd9b2f1f0d 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp @@ -20357,14 +20357,12 @@ namespace Events {} // namespace Events namespace ServiceArea { namespace Structs { -namespace AreaInfoStruct { +namespace LandmarkInfoStruct { CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const { DataModel::WrappedStructEncoder encoder{ aWriter, aTag }; - encoder.Encode(to_underlying(Fields::kLocationInfo), locationInfo); encoder.Encode(to_underlying(Fields::kLandmarkTag), landmarkTag); encoder.Encode(to_underlying(Fields::kPositionTag), positionTag); - encoder.Encode(to_underlying(Fields::kSurfaceTag), surfaceTag); return encoder.Finalize(); } @@ -20382,11 +20380,7 @@ CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader) CHIP_ERROR err = CHIP_NO_ERROR; const uint8_t __context_tag = std::get(__element); - if (__context_tag == to_underlying(Fields::kLocationInfo)) - { - err = DataModel::Decode(reader, locationInfo); - } - else if (__context_tag == to_underlying(Fields::kLandmarkTag)) + if (__context_tag == to_underlying(Fields::kLandmarkTag)) { err = DataModel::Decode(reader, landmarkTag); } @@ -20394,9 +20388,46 @@ CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader) { err = DataModel::Decode(reader, positionTag); } - else if (__context_tag == to_underlying(Fields::kSurfaceTag)) + else + { + } + + ReturnErrorOnFailure(err); + } +} + +} // namespace LandmarkInfoStruct + +namespace AreaInfoStruct { +CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const +{ + DataModel::WrappedStructEncoder encoder{ aWriter, aTag }; + encoder.Encode(to_underlying(Fields::kLocationInfo), locationInfo); + encoder.Encode(to_underlying(Fields::kLandmarkInfo), landmarkInfo); + return encoder.Finalize(); +} + +CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader) +{ + detail::StructDecodeIterator __iterator(reader); + while (true) + { + auto __element = __iterator.Next(); + if (std::holds_alternative(__element)) + { + return std::get(__element); + } + + CHIP_ERROR err = CHIP_NO_ERROR; + const uint8_t __context_tag = std::get(__element); + + if (__context_tag == to_underlying(Fields::kLocationInfo)) + { + err = DataModel::Decode(reader, locationInfo); + } + else if (__context_tag == to_underlying(Fields::kLandmarkInfo)) { - err = DataModel::Decode(reader, surfaceTag); + err = DataModel::Decode(reader, landmarkInfo); } else { @@ -20625,6 +20656,7 @@ namespace SkipArea { CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const { DataModel::WrappedStructEncoder encoder{ aWriter, aTag }; + encoder.Encode(to_underlying(Fields::kSkippedArea), skippedArea); return encoder.Finalize(); } @@ -20638,6 +20670,19 @@ CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader) { return std::get(__element); } + + CHIP_ERROR err = CHIP_NO_ERROR; + const uint8_t __context_tag = std::get(__element); + + if (__context_tag == to_underlying(Fields::kSkippedArea)) + { + err = DataModel::Decode(reader, skippedArea); + } + else + { + } + + ReturnErrorOnFailure(err); } } } // namespace SkipArea. @@ -24398,6 +24443,8 @@ CHIP_ERROR TypeInfo::DecodableType::Decode(TLV::TLVReader & reader, const Concre return DataModel::Decode(reader, interfaceEnabled); case Attributes::ActiveDatasetTimestamp::TypeInfo::GetAttributeId(): return DataModel::Decode(reader, activeDatasetTimestamp); + case Attributes::PendingDatasetTimestamp::TypeInfo::GetAttributeId(): + return DataModel::Decode(reader, pendingDatasetTimestamp); case Attributes::GeneratedCommandList::TypeInfo::GetAttributeId(): return DataModel::Decode(reader, generatedCommandList); case Attributes::AcceptedCommandList::TypeInfo::GetAttributeId(): diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h index 07107feb2df637..27ee806af6e9a9 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h @@ -28344,22 +28344,41 @@ struct TypeInfo } // namespace BarrierControl namespace ServiceArea { namespace Structs { +namespace LandmarkInfoStruct { +enum class Fields : uint8_t +{ + kLandmarkTag = 0, + kPositionTag = 1, +}; + +struct Type +{ +public: + Globals::LandmarkTag landmarkTag = static_cast(0); + DataModel::Nullable positionTag; + + CHIP_ERROR Decode(TLV::TLVReader & reader); + + static constexpr bool kIsFabricScoped = false; + + CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; +}; + +using DecodableType = Type; + +} // namespace LandmarkInfoStruct namespace AreaInfoStruct { enum class Fields : uint8_t { kLocationInfo = 0, - kLandmarkTag = 1, - kPositionTag = 2, - kSurfaceTag = 3, + kLandmarkInfo = 1, }; struct Type { public: DataModel::Nullable locationInfo; - DataModel::Nullable landmarkTag; - DataModel::Nullable positionTag; - DataModel::Nullable surfaceTag; + DataModel::Nullable landmarkInfo; CHIP_ERROR Decode(TLV::TLVReader & reader); @@ -28383,7 +28402,7 @@ struct Type { public: uint32_t areaID = static_cast(0); - DataModel::Nullable mapID; + DataModel::Nullable mapID; Structs::AreaInfoStruct::Type areaDesc; CHIP_ERROR Decode(TLV::TLVReader & reader); @@ -28406,7 +28425,7 @@ enum class Fields : uint8_t struct Type { public: - uint8_t mapID = static_cast(0); + uint32_t mapID = static_cast(0); chip::CharSpan name; CHIP_ERROR Decode(TLV::TLVReader & reader); @@ -28521,7 +28540,7 @@ struct Type static constexpr ClusterId GetClusterId() { return Clusters::ServiceArea::Id; } SelectAreasStatus status = static_cast(0); - Optional statusText; + chip::CharSpan statusText; CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; @@ -28537,13 +28556,14 @@ struct DecodableType static constexpr ClusterId GetClusterId() { return Clusters::ServiceArea::Id; } SelectAreasStatus status = static_cast(0); - Optional statusText; + chip::CharSpan statusText; CHIP_ERROR Decode(TLV::TLVReader & reader); }; }; // namespace SelectAreasResponse namespace SkipArea { enum class Fields : uint8_t { + kSkippedArea = 0, }; struct Type @@ -28553,6 +28573,8 @@ struct Type static constexpr CommandId GetCommandId() { return Commands::SkipArea::Id; } static constexpr ClusterId GetClusterId() { return Clusters::ServiceArea::Id; } + uint32_t skippedArea = static_cast(0); + CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; using ResponseType = Clusters::ServiceArea::Commands::SkipAreaResponse::DecodableType; @@ -28566,6 +28588,7 @@ struct DecodableType static constexpr CommandId GetCommandId() { return Commands::SkipArea::Id; } static constexpr ClusterId GetClusterId() { return Clusters::ServiceArea::Id; } + uint32_t skippedArea = static_cast(0); CHIP_ERROR Decode(TLV::TLVReader & reader); }; }; // namespace SkipArea @@ -28584,7 +28607,7 @@ struct Type static constexpr ClusterId GetClusterId() { return Clusters::ServiceArea::Id; } SkipAreaStatus status = static_cast(0); - Optional statusText; + chip::CharSpan statusText; CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; @@ -28600,7 +28623,7 @@ struct DecodableType static constexpr ClusterId GetClusterId() { return Clusters::ServiceArea::Id; } SkipAreaStatus status = static_cast(0); - Optional statusText; + chip::CharSpan statusText; CHIP_ERROR Decode(TLV::TLVReader & reader); }; }; // namespace SkipAreaResponse @@ -36667,6 +36690,18 @@ struct TypeInfo static constexpr bool MustUseTimedWrite() { return false; } }; } // namespace ActiveDatasetTimestamp +namespace PendingDatasetTimestamp { +struct TypeInfo +{ + using Type = chip::app::DataModel::Nullable; + using DecodableType = chip::app::DataModel::Nullable; + using DecodableArgType = const chip::app::DataModel::Nullable &; + + static constexpr ClusterId GetClusterId() { return Clusters::ThreadBorderRouterManagement::Id; } + static constexpr AttributeId GetAttributeId() { return Attributes::PendingDatasetTimestamp::Id; } + static constexpr bool MustUseTimedWrite() { return false; } +}; +} // namespace PendingDatasetTimestamp namespace GeneratedCommandList { struct TypeInfo : public Clusters::Globals::Attributes::GeneratedCommandList::TypeInfo { @@ -36717,6 +36752,7 @@ struct TypeInfo Attributes::ThreadVersion::TypeInfo::DecodableType threadVersion = static_cast(0); Attributes::InterfaceEnabled::TypeInfo::DecodableType interfaceEnabled = static_cast(0); Attributes::ActiveDatasetTimestamp::TypeInfo::DecodableType activeDatasetTimestamp; + Attributes::PendingDatasetTimestamp::TypeInfo::DecodableType pendingDatasetTimestamp; Attributes::GeneratedCommandList::TypeInfo::DecodableType generatedCommandList; Attributes::AcceptedCommandList::TypeInfo::DecodableType acceptedCommandList; Attributes::EventList::TypeInfo::DecodableType eventList; diff --git a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h index b78c1e3410993a..85d56a98880f99 100644 --- a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h +++ b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h @@ -6826,6 +6826,10 @@ namespace ActiveDatasetTimestamp { static constexpr AttributeId Id = 0x00000004; } // namespace ActiveDatasetTimestamp +namespace PendingDatasetTimestamp { +static constexpr AttributeId Id = 0x00000005; +} // namespace PendingDatasetTimestamp + namespace GeneratedCommandList { static constexpr AttributeId Id = Globals::Attributes::GeneratedCommandList::Id; } // namespace GeneratedCommandList diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h index 98d6d9caee0e97..85a45a057b7eab 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h @@ -9429,6 +9429,7 @@ class ServiceAreaSkipArea : public ClusterCommand public: ServiceAreaSkipArea(CredentialIssuerCommands * credsIssuerConfig) : ClusterCommand("skip-area", credsIssuerConfig) { + AddArgument("SkippedArea", 0, UINT32_MAX, &mRequest.skippedArea); ClusterCommand::AddArguments(); } @@ -11337,6 +11338,7 @@ class WiFiNetworkManagementNetworkPassphraseRequest : public ClusterCommand | * ThreadVersion | 0x0002 | | * InterfaceEnabled | 0x0003 | | * ActiveDatasetTimestamp | 0x0004 | +| * PendingDatasetTimestamp | 0x0005 | | * GeneratedCommandList | 0xFFF8 | | * AcceptedCommandList | 0xFFF9 | | * EventList | 0xFFFA | @@ -25680,19 +25682,20 @@ void registerClusterThreadBorderRouterManagement(Commands & commands, Credential // // Attributes // - make_unique(Id, credsIssuerConfig), // - make_unique(Id, "border-router-name", Attributes::BorderRouterName::Id, credsIssuerConfig), // - make_unique(Id, "border-agent-id", Attributes::BorderAgentID::Id, credsIssuerConfig), // - make_unique(Id, "thread-version", Attributes::ThreadVersion::Id, credsIssuerConfig), // - make_unique(Id, "interface-enabled", Attributes::InterfaceEnabled::Id, credsIssuerConfig), // - make_unique(Id, "active-dataset-timestamp", Attributes::ActiveDatasetTimestamp::Id, credsIssuerConfig), // - make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // - make_unique(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig), // - make_unique(Id, "event-list", Attributes::EventList::Id, credsIssuerConfig), // - make_unique(Id, "attribute-list", Attributes::AttributeList::Id, credsIssuerConfig), // - make_unique(Id, "feature-map", Attributes::FeatureMap::Id, credsIssuerConfig), // - make_unique(Id, "cluster-revision", Attributes::ClusterRevision::Id, credsIssuerConfig), // - make_unique>(Id, credsIssuerConfig), // + make_unique(Id, credsIssuerConfig), // + make_unique(Id, "border-router-name", Attributes::BorderRouterName::Id, credsIssuerConfig), // + make_unique(Id, "border-agent-id", Attributes::BorderAgentID::Id, credsIssuerConfig), // + make_unique(Id, "thread-version", Attributes::ThreadVersion::Id, credsIssuerConfig), // + make_unique(Id, "interface-enabled", Attributes::InterfaceEnabled::Id, credsIssuerConfig), // + make_unique(Id, "active-dataset-timestamp", Attributes::ActiveDatasetTimestamp::Id, credsIssuerConfig), // + make_unique(Id, "pending-dataset-timestamp", Attributes::PendingDatasetTimestamp::Id, credsIssuerConfig), // + make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // + make_unique(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig), // + make_unique(Id, "event-list", Attributes::EventList::Id, credsIssuerConfig), // + make_unique(Id, "attribute-list", Attributes::AttributeList::Id, credsIssuerConfig), // + make_unique(Id, "feature-map", Attributes::FeatureMap::Id, credsIssuerConfig), // + make_unique(Id, "cluster-revision", Attributes::ClusterRevision::Id, credsIssuerConfig), // + make_unique>(Id, credsIssuerConfig), // make_unique>(Id, "border-router-name", Attributes::BorderRouterName::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // make_unique>(Id, "border-agent-id", Attributes::BorderAgentID::Id, @@ -25704,6 +25707,9 @@ void registerClusterThreadBorderRouterManagement(Commands & commands, Credential make_unique>>(Id, "active-dataset-timestamp", 0, UINT64_MAX, Attributes::ActiveDatasetTimestamp::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // + make_unique>>(Id, "pending-dataset-timestamp", 0, UINT64_MAX, + Attributes::PendingDatasetTimestamp::Id, + WriteCommandType::kForceWrite, credsIssuerConfig), // make_unique>>( Id, "generated-command-list", Attributes::GeneratedCommandList::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // @@ -25723,6 +25729,8 @@ void registerClusterThreadBorderRouterManagement(Commands & commands, Credential make_unique(Id, "thread-version", Attributes::ThreadVersion::Id, credsIssuerConfig), // make_unique(Id, "interface-enabled", Attributes::InterfaceEnabled::Id, credsIssuerConfig), // make_unique(Id, "active-dataset-timestamp", Attributes::ActiveDatasetTimestamp::Id, + credsIssuerConfig), // + make_unique(Id, "pending-dataset-timestamp", Attributes::PendingDatasetTimestamp::Id, credsIssuerConfig), // make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // make_unique(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig), // diff --git a/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp b/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp index b57db005357f57..d4f67568a7f746 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp +++ b/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp @@ -3975,7 +3975,7 @@ void ComplexArgumentParser::Finalize(chip::app::Clusters::DoorLock::Structs::Cre } CHIP_ERROR ComplexArgumentParser::Setup(const char * label, - chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::Type & request, + chip::app::Clusters::ServiceArea::Structs::LandmarkInfoStruct::Type & request, Json::Value & value) { VerifyOrReturnError(value.isObject(), CHIP_ERROR_INVALID_ARGUMENT); @@ -3984,19 +3984,11 @@ CHIP_ERROR ComplexArgumentParser::Setup(const char * label, Json::Value valueCopy(value); ReturnErrorOnFailure( - ComplexArgumentParser::EnsureMemberExist("AreaInfoStruct.locationInfo", "locationInfo", value.isMember("locationInfo"))); - ReturnErrorOnFailure( - ComplexArgumentParser::EnsureMemberExist("AreaInfoStruct.landmarkTag", "landmarkTag", value.isMember("landmarkTag"))); + ComplexArgumentParser::EnsureMemberExist("LandmarkInfoStruct.landmarkTag", "landmarkTag", value.isMember("landmarkTag"))); ReturnErrorOnFailure( - ComplexArgumentParser::EnsureMemberExist("AreaInfoStruct.positionTag", "positionTag", value.isMember("positionTag"))); - ReturnErrorOnFailure( - ComplexArgumentParser::EnsureMemberExist("AreaInfoStruct.surfaceTag", "surfaceTag", value.isMember("surfaceTag"))); + ComplexArgumentParser::EnsureMemberExist("LandmarkInfoStruct.positionTag", "positionTag", value.isMember("positionTag"))); char labelWithMember[kMaxLabelLength]; - snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "locationInfo"); - ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.locationInfo, value["locationInfo"])); - valueCopy.removeMember("locationInfo"); - snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "landmarkTag"); ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.landmarkTag, value["landmarkTag"])); valueCopy.removeMember("landmarkTag"); @@ -4005,9 +3997,37 @@ CHIP_ERROR ComplexArgumentParser::Setup(const char * label, ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.positionTag, value["positionTag"])); valueCopy.removeMember("positionTag"); - snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "surfaceTag"); - ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.surfaceTag, value["surfaceTag"])); - valueCopy.removeMember("surfaceTag"); + return ComplexArgumentParser::EnsureNoMembersRemaining(label, valueCopy); +} + +void ComplexArgumentParser::Finalize(chip::app::Clusters::ServiceArea::Structs::LandmarkInfoStruct::Type & request) +{ + ComplexArgumentParser::Finalize(request.landmarkTag); + ComplexArgumentParser::Finalize(request.positionTag); +} + +CHIP_ERROR ComplexArgumentParser::Setup(const char * label, + chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::Type & request, + Json::Value & value) +{ + VerifyOrReturnError(value.isObject(), CHIP_ERROR_INVALID_ARGUMENT); + + // Copy to track which members we already processed. + Json::Value valueCopy(value); + + ReturnErrorOnFailure( + ComplexArgumentParser::EnsureMemberExist("AreaInfoStruct.locationInfo", "locationInfo", value.isMember("locationInfo"))); + ReturnErrorOnFailure( + ComplexArgumentParser::EnsureMemberExist("AreaInfoStruct.landmarkInfo", "landmarkInfo", value.isMember("landmarkInfo"))); + + char labelWithMember[kMaxLabelLength]; + snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "locationInfo"); + ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.locationInfo, value["locationInfo"])); + valueCopy.removeMember("locationInfo"); + + snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "landmarkInfo"); + ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.landmarkInfo, value["landmarkInfo"])); + valueCopy.removeMember("landmarkInfo"); return ComplexArgumentParser::EnsureNoMembersRemaining(label, valueCopy); } @@ -4015,9 +4035,7 @@ CHIP_ERROR ComplexArgumentParser::Setup(const char * label, void ComplexArgumentParser::Finalize(chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::Type & request) { ComplexArgumentParser::Finalize(request.locationInfo); - ComplexArgumentParser::Finalize(request.landmarkTag); - ComplexArgumentParser::Finalize(request.positionTag); - ComplexArgumentParser::Finalize(request.surfaceTag); + ComplexArgumentParser::Finalize(request.landmarkInfo); } CHIP_ERROR ComplexArgumentParser::Setup(const char * label, chip::app::Clusters::ServiceArea::Structs::AreaStruct::Type & request, diff --git a/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.h b/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.h index b08c731a32689d..dfca56f4c3afba 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.h @@ -458,6 +458,11 @@ static CHIP_ERROR Setup(const char * label, chip::app::Clusters::DoorLock::Struc static void Finalize(chip::app::Clusters::DoorLock::Structs::CredentialStruct::Type & request); +static CHIP_ERROR Setup(const char * label, chip::app::Clusters::ServiceArea::Structs::LandmarkInfoStruct::Type & request, + Json::Value & value); + +static void Finalize(chip::app::Clusters::ServiceArea::Structs::LandmarkInfoStruct::Type & request); + static CHIP_ERROR Setup(const char * label, chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::Type & request, Json::Value & value); diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp index afebe69c9407ed..2eddf2f356bd07 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp +++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp @@ -3516,38 +3516,47 @@ CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent, } CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent, - const chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::DecodableType & value) + const chip::app::Clusters::ServiceArea::Structs::LandmarkInfoStruct::DecodableType & value) { DataModelLogger::LogString(label, indent, "{"); { - CHIP_ERROR err = LogValue("LocationInfo", indent + 1, value.locationInfo); + CHIP_ERROR err = LogValue("LandmarkTag", indent + 1, value.landmarkTag); if (err != CHIP_NO_ERROR) { - DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'LocationInfo'"); + DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'LandmarkTag'"); return err; } } { - CHIP_ERROR err = LogValue("LandmarkTag", indent + 1, value.landmarkTag); + CHIP_ERROR err = LogValue("PositionTag", indent + 1, value.positionTag); if (err != CHIP_NO_ERROR) { - DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'LandmarkTag'"); + DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'PositionTag'"); return err; } } + DataModelLogger::LogString(indent, "}"); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent, + const chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::DecodableType & value) +{ + DataModelLogger::LogString(label, indent, "{"); { - CHIP_ERROR err = LogValue("PositionTag", indent + 1, value.positionTag); + CHIP_ERROR err = LogValue("LocationInfo", indent + 1, value.locationInfo); if (err != CHIP_NO_ERROR) { - DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'PositionTag'"); + DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'LocationInfo'"); return err; } } { - CHIP_ERROR err = LogValue("SurfaceTag", indent + 1, value.surfaceTag); + CHIP_ERROR err = LogValue("LandmarkInfo", indent + 1, value.landmarkInfo); if (err != CHIP_NO_ERROR) { - DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'SurfaceTag'"); + DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'LandmarkInfo'"); return err; } } @@ -17232,6 +17241,11 @@ CHIP_ERROR DataModelLogger::LogAttribute(const chip::app::ConcreteDataAttributeP ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); return DataModelLogger::LogValue("ActiveDatasetTimestamp", 1, value); } + case ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id: { + chip::app::DataModel::Nullable value; + ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); + return DataModelLogger::LogValue("PendingDatasetTimestamp", 1, value); + } case ThreadBorderRouterManagement::Attributes::GeneratedCommandList::Id: { chip::app::DataModel::DecodableList value; ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h index fa3fc1a2760fc2..60398d0f3d2911 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h @@ -286,6 +286,9 @@ static CHIP_ERROR LogValue(const char * label, size_t indent, static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::Clusters::DoorLock::Structs::CredentialStruct::DecodableType & value); +static CHIP_ERROR LogValue(const char * label, size_t indent, + const chip::app::Clusters::ServiceArea::Structs::LandmarkInfoStruct::DecodableType & value); + static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::Clusters::ServiceArea::Structs::AreaInfoStruct::DecodableType & value); diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h index 8e18e60aaa7e63..67f8781db8fd5d 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h @@ -103950,6 +103950,9 @@ class ServiceAreaSkipArea : public ClusterCommand { ServiceAreaSkipArea() : ClusterCommand("skip-area") { +#if MTR_ENABLE_PROVISIONAL + AddArgument("SkippedArea", 0, UINT32_MAX, &mRequest.skippedArea); +#endif // MTR_ENABLE_PROVISIONAL ClusterCommand::AddArguments(); } @@ -103964,6 +103967,9 @@ class ServiceAreaSkipArea : public ClusterCommand { __auto_type * cluster = [[MTRBaseClusterServiceArea alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; __auto_type * params = [[MTRServiceAreaClusterSkipAreaParams alloc] init]; params.timedInvokeTimeoutMs = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil; +#if MTR_ENABLE_PROVISIONAL + params.skippedArea = [NSNumber numberWithUnsignedInt:mRequest.skippedArea]; +#endif // MTR_ENABLE_PROVISIONAL uint16_t repeatCount = mRepeatCount.ValueOr(1); uint16_t __block responsesNeeded = repeatCount; while (repeatCount--) { @@ -103990,6 +103996,7 @@ class ServiceAreaSkipArea : public ClusterCommand { } private: + chip::app::Clusters::ServiceArea::Commands::SkipArea::Type mRequest; }; #endif // MTR_ENABLE_PROVISIONAL @@ -147933,6 +147940,7 @@ class SubscribeAttributeWiFiNetworkManagementClusterRevision : public SubscribeA | * ThreadVersion | 0x0002 | | * InterfaceEnabled | 0x0003 | | * ActiveDatasetTimestamp | 0x0004 | +| * PendingDatasetTimestamp | 0x0005 | | * GeneratedCommandList | 0xFFF8 | | * AcceptedCommandList | 0xFFF9 | | * EventList | 0xFFFA | @@ -148591,6 +148599,91 @@ class SubscribeAttributeThreadBorderRouterManagementActiveDatasetTimestamp : pub #endif // MTR_ENABLE_PROVISIONAL #if MTR_ENABLE_PROVISIONAL +/* + * Attribute PendingDatasetTimestamp + */ +class ReadThreadBorderRouterManagementPendingDatasetTimestamp : public ReadAttribute { +public: + ReadThreadBorderRouterManagementPendingDatasetTimestamp() + : ReadAttribute("pending-dataset-timestamp") + { + } + + ~ReadThreadBorderRouterManagementPendingDatasetTimestamp() + { + } + + CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override + { + constexpr chip::ClusterId clusterId = chip::app::Clusters::ThreadBorderRouterManagement::Id; + constexpr chip::AttributeId attributeId = chip::app::Clusters::ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id; + + ChipLogProgress(chipTool, "Sending cluster (0x%08" PRIX32 ") ReadAttribute (0x%08" PRIX32 ") on endpoint %u", endpointId, clusterId, attributeId); + + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); + __auto_type * cluster = [[MTRBaseClusterThreadBorderRouterManagement alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; + [cluster readAttributePendingDatasetTimestampWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable error) { + NSLog(@"ThreadBorderRouterManagement.PendingDatasetTimestamp response %@", [value description]); + if (error == nil) { + RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value); + } else { + LogNSError("ThreadBorderRouterManagement PendingDatasetTimestamp read Error", error); + RemoteDataModelLogger::LogAttributeErrorAsJSON(@(endpointId), @(clusterId), @(attributeId), error); + } + SetCommandExitStatus(error); + }]; + return CHIP_NO_ERROR; + } +}; + +class SubscribeAttributeThreadBorderRouterManagementPendingDatasetTimestamp : public SubscribeAttribute { +public: + SubscribeAttributeThreadBorderRouterManagementPendingDatasetTimestamp() + : SubscribeAttribute("pending-dataset-timestamp") + { + } + + ~SubscribeAttributeThreadBorderRouterManagementPendingDatasetTimestamp() + { + } + + CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override + { + constexpr chip::ClusterId clusterId = chip::app::Clusters::ThreadBorderRouterManagement::Id; + constexpr chip::CommandId attributeId = chip::app::Clusters::ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id; + + ChipLogProgress(chipTool, "Sending cluster (0x%08" PRIX32 ") ReportAttribute (0x%08" PRIX32 ") on endpoint %u", clusterId, attributeId, endpointId); + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); + __auto_type * cluster = [[MTRBaseClusterThreadBorderRouterManagement alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(mMinInterval) maxInterval:@(mMaxInterval)]; + if (mKeepSubscriptions.HasValue()) { + params.replaceExistingSubscriptions = !mKeepSubscriptions.Value(); + } + if (mFabricFiltered.HasValue()) { + params.filterByFabric = mFabricFiltered.Value(); + } + if (mAutoResubscribe.HasValue()) { + params.resubscribeAutomatically = mAutoResubscribe.Value(); + } + [cluster subscribeAttributePendingDatasetTimestampWithParams:params + subscriptionEstablished:^() { mSubscriptionEstablished = YES; } + reportHandler:^(NSNumber * _Nullable value, NSError * _Nullable error) { + NSLog(@"ThreadBorderRouterManagement.PendingDatasetTimestamp response %@", [value description]); + if (error == nil) { + RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value); + } else { + RemoteDataModelLogger::LogAttributeErrorAsJSON(@(endpointId), @(clusterId), @(attributeId), error); + } + SetCommandExitStatus(error); + }]; + + return CHIP_NO_ERROR; + } +}; + +#endif // MTR_ENABLE_PROVISIONAL +#if MTR_ENABLE_PROVISIONAL + /* * Attribute GeneratedCommandList */ @@ -197664,6 +197757,10 @@ void registerClusterThreadBorderRouterManagement(Commands & commands) make_unique(), // make_unique(), // #endif // MTR_ENABLE_PROVISIONAL +#if MTR_ENABLE_PROVISIONAL + make_unique(), // + make_unique(), // +#endif // MTR_ENABLE_PROVISIONAL #if MTR_ENABLE_PROVISIONAL make_unique(), // make_unique(), //