From 70f42f2519862e692cf59d12a30ff6a0723cb335 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 24 Jul 2023 14:37:04 -0400 Subject: [PATCH 1/6] Add another clean-output since CI seems to run out of space on builds (#28226) --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 66403d723e3101..14ced0ab7474a8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -201,6 +201,8 @@ jobs: --file-exclude-regex '/(repo|zzz_generated|lwip/standalone)/' \ check \ " + - name: Clean output + run: rm -rf ./out - name: Build using build_examples.py run: | ./scripts/run_in_build_env.sh \ From f3c498fb624d1596bd6d49b9c98fb36b20473cb0 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Mon, 24 Jul 2023 15:37:19 -0400 Subject: [PATCH 2/6] Revert "Cancel incomplete BLE connection when CloseAllBleConnections() is called (#27304)" (#28143) This reverts commit ad5403e8e537612c2d2f903895642445d45d1e08. There are two issues: * https://github.com/project-chip/connectedhomeip/issues/27607 which has had no response for weeks. * https://github.com/project-chip/connectedhomeip/issues/28139 which breaks commissioning on Mac, and would break it on Linux if it implemented BLE connection cancellation. We need to sort out why CHIPDeviceController is canceling BLE connections when starting PASE over BLE (!). --- src/ble/BleLayer.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ble/BleLayer.cpp b/src/ble/BleLayer.cpp index 57a8ca8dc0fdff..9109924ccd3754 100644 --- a/src/ble/BleLayer.cpp +++ b/src/ble/BleLayer.cpp @@ -308,13 +308,6 @@ void BleLayer::Shutdown() void BleLayer::CloseAllBleConnections() { - // Cancel any ongoing attempt to establish new BLE connection - CHIP_ERROR err = CancelBleIncompleteConnection(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(Ble, "CancelBleIncompleteConnection() failed, err = %" CHIP_ERROR_FORMAT, err.Format()); - } - // Close and free all BLE end points. for (size_t i = 0; i < BLE_LAYER_NUM_BLE_ENDPOINTS; i++) { From 4850de6020f4a05ba2b05505745583abab667114 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Mon, 24 Jul 2023 15:37:33 -0400 Subject: [PATCH 3/6] Add Constants.h to the secure_channel sources. (#28219) This causes gn to correctly notice the circular dependencies between secure_channel and messaging and secure_channel and transport, which are inherent in the spec. Annotate those circular dependencies. --- src/protocols/secure_channel/BUILD.gn | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/protocols/secure_channel/BUILD.gn b/src/protocols/secure_channel/BUILD.gn index 4f9bfb6be7257f..8cf46f9d334a1f 100644 --- a/src/protocols/secure_channel/BUILD.gn +++ b/src/protocols/secure_channel/BUILD.gn @@ -10,6 +10,7 @@ static_library("secure_channel") { "CASEServer.h", "CASESession.cpp", "CASESession.h", + "Constants.h", "DefaultSessionResumptionStorage.cpp", "DefaultSessionResumptionStorage.h", "PASESession.cpp", @@ -38,5 +39,18 @@ static_library("secure_channel") { "${chip_root}/src/system", "${chip_root}/src/tracing", "${chip_root}/src/tracing:macros", + "${chip_root}/src/transport", + ] + + # secure channel requires messaging so it can send messages (e.g. for + # CASE/PASE handshakes). messaging requires secure channel so that it can do + # things like send standalone acks, which are a secure channel concept. + # + # secure channel requires transport so it can deal with sessions. transport + # requires secure channel so it can detect control messages, which are a + # secure channel concept. + allow_circular_includes_from = [ + "${chip_root}/src/messaging", + "${chip_root}/src/transport", ] } From fe4c679ace3eb134d7365d3bd0fe6c2fad6ece87 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 24 Jul 2023 17:16:24 -0400 Subject: [PATCH 4/6] Add support for `--trace-to` for python and use it in repl tests (#28179) * Start adding tracing start/stop functions * Add raii-like support for tracing * Add raii-like support for tracing * Fix compile logic * Switch linux, mac, android to C++17 by default * Start outputting trace data * Upload traces that are gathered * Fix names * Allow placeholders in script args too * Allow from-string tracing * Do not newline-separate restyle path otherwise only the first argument is processed * Restyle * Add some additional types * Minor python fixes * Import ctypes * Things run now * Add trace bits to our tests * Undo restyle-diff change * Fix some typos in naming * Add perfetto for darwin too * mobile-device-test.py does not suppor trace-to yet * Make mobile device test also be able to trace. Mobile device test seems super slow * Restyled by autopep8 * Restyled by isort --------- Co-authored-by: Andrei Litvin Co-authored-by: Restyled.io --- .github/workflows/tests.yaml | 44 +++--- .gitmodules | 2 +- scripts/tests/run_python_test.py | 8 +- src/controller/python/BUILD.gn | 11 ++ src/controller/python/chip/native/__init__.py | 8 +- .../python/chip/tracing/TracingSetup.cpp | 104 +++++++++++++++ .../python/chip/tracing/__init__.py | 125 ++++++++++++++++++ .../test/test_scripts/mobile-device-test.py | 15 ++- src/python_testing/matter_testing_support.py | 70 +++++----- 9 files changed, 331 insertions(+), 56 deletions(-) create mode 100644 src/controller/python/chip/tracing/TracingSetup.cpp create mode 100644 src/controller/python/chip/tracing/__init__.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 99f7055113d2ec..c462203ae6cd0d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -437,24 +437,25 @@ jobs: " - name: Run Tests run: | - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --script-args "--log-level INFO -t 3600 --disable-test ClusterObjectTests.TestTimedRequestTimeout"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_RR_1_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_DeviceBasicComposition.py" --script-args "--storage-path admin_storage.json --manual-code 10054912339"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_SC_3_6.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_DA_1_7.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --bool-arg allow_sdk_dac:true"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_TestEventTrigger.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --bool-arg allow_sdk_dac:true"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_ACE_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_ACE_1_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --int-arg PIXIT.ACE.APPENDPOINT:1 PIXIT.ACE.APPDEVTYPEID:0x0100 --string-arg PIXIT.ACE.APPCLUSTER:OnOff PIXIT.ACE.APPATTRIBUTE:OnOff"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_ACE_1_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_CGEN_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_DA_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_TIMESYNC_3_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_DA_1_5.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_IDM_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_FAN_3_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_FAN_3_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:log" --script "src/python_testing/TC_FAN_3_5.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"' - scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestMatterTestingSupport.py"' + mkdir -p out/trace_data + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script-args "--log-level INFO -t 3600 --disable-test ClusterObjectTests.TestTimedRequestTimeout --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RR_1_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DeviceBasicComposition.py" --script-args "--storage-path admin_storage.json --manual-code 10054912339 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_SC_3_6.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DA_1_7.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --bool-arg allow_sdk_dac:true --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_TestEventTrigger.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --bool-arg allow_sdk_dac:true --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_ACE_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_ACE_1_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --int-arg PIXIT.ACE.APPENDPOINT:1 PIXIT.ACE.APPDEVTYPEID:0x0100 --string-arg PIXIT.ACE.APPCLUSTER:OnOff PIXIT.ACE.APPATTRIBUTE:OnOff --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_ACE_1_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_CGEN_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DA_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_TIMESYNC_3_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DA_1_5.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_IDM_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_FAN_3_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_FAN_3_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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 --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_FAN_3_5.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --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"' - name: Uploading core files uses: actions/upload-artifact@v3 if: ${{ failure() && !env.ACT }} @@ -524,6 +525,13 @@ jobs: path: /cores/ # Cores are big; don't hold on to them too long. retention-days: 5 + - name: Uploading traces on failure + uses: actions/upload-artifact@v3 + if: ${{ failure() && !env.ACT }} + with: + name: trace-data-python-repl + path: out/trace_data/ + retention-days: 5 - name: Uploading diagnostic logs uses: actions/upload-artifact@v3 if: ${{ failure() && !env.ACT }} diff --git a/.gitmodules b/.gitmodules index 990cb20f276572..15082161f151d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -307,7 +307,7 @@ path = third_party/perfetto/repo url = https://github.com/google/perfetto.git branch = master - platforms = linux,android + platforms = linux,android,darwin [submodule "third_party/asr/components"] path = third_party/asr/components url = https://github.com/asriot/asriot_components.git diff --git a/scripts/tests/run_python_test.py b/scripts/tests/run_python_test.py index c607a5d07cf40f..33931accb909f9 100755 --- a/scripts/tests/run_python_test.py +++ b/scripts/tests/run_python_test.py @@ -17,6 +17,7 @@ import datetime import logging import os +import os.path import queue import re import shlex @@ -72,7 +73,7 @@ def DumpProgramOutputToQueue(thread_list: typing.List[threading.Thread], tag: st @click.option("--factoryreset", is_flag=True, help='Remove app config and repl configs (/tmp/chip* and /tmp/repl*) before running the tests.') @click.option("--app-args", type=str, default='', - help='The extra arguments passed to the device.') + help='The extra arguments passed to the device. Can use placholders like {SCRIPT_BASE_NAME}') @click.option("--script", type=click.Path(exists=True), default=os.path.join(DEFAULT_CHIP_ROOT, 'src', 'controller', @@ -81,10 +82,13 @@ def DumpProgramOutputToQueue(thread_list: typing.List[threading.Thread], tag: st 'test_scripts', 'mobile-device-test.py'), help='Test script to use.') @click.option("--script-args", type=str, default='', - help='Path to the test script to use, omit to use the default test script (mobile-device-test.py).') + help='Script arguments, can use placeholders like {SCRIPT_BASE_NAME}.') @click.option("--script-gdb", is_flag=True, help='Run script through gdb') def main(app: str, factoryreset: bool, app_args: str, script: str, script_args: str, script_gdb: bool): + app_args = app_args.replace('{SCRIPT_BASE_NAME}', os.path.splitext(os.path.basename(script))[0]) + script_args = script_args.replace('{SCRIPT_BASE_NAME}', os.path.splitext(os.path.basename(script))[0]) + if factoryreset: # Remove native app config retcode = subprocess.call("rm -rf /tmp/chip* /tmp/repl*", shell=True) diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index c76c64c65d7df4..375a65cf7becbc 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -78,6 +78,7 @@ shared_library("ChipDeviceCtrl") { "chip/internal/CommissionerImpl.cpp", "chip/logging/LoggingRedirect.cpp", "chip/native/PyChipError.cpp", + "chip/tracing/TracingSetup.cpp", "chip/utils/DeviceProxyUtils.cpp", ] defines += [ "CHIP_CONFIG_MAX_GROUPS_PER_FABRIC=50" ] @@ -120,8 +121,16 @@ shared_library("ChipDeviceCtrl") { public_deps += [ "${chip_root}/src/controller/data_model", "${chip_root}/src/credentials:file_attestation_trust_store", + "${chip_root}/src/tracing/json", + "${chip_root}/src/tracing/perfetto", + "${chip_root}/src/tracing/perfetto:file_output", "${chip_root}/third_party/jsoncpp", ] + + deps = [ + "${chip_root}/src/tracing/perfetto:event_storage", + "${chip_root}/src/tracing/perfetto:simple_initialization", + ] } else { public_deps += [ "$chip_data_model" ] } @@ -238,6 +247,7 @@ chip_python_wheel_action("chip-core") { "chip/setup_payload/__init__.py", "chip/setup_payload/setup_payload.py", "chip/storage/__init__.py", + "chip/tracing/__init__.py", "chip/utils/CommissioningBuildingBlocks.py", "chip/utils/__init__.py", "chip/yaml/__init__.py", @@ -292,6 +302,7 @@ chip_python_wheel_action("chip-core") { "chip.clusters", "chip.setup_payload", "chip.storage", + "chip.tracing", ] if (!chip_controller) { diff --git a/src/controller/python/chip/native/__init__.py b/src/controller/python/chip/native/__init__.py index 26e394e61fca6a..7801129c948532 100644 --- a/src/controller/python/chip/native/__init__.py +++ b/src/controller/python/chip/native/__init__.py @@ -197,5 +197,9 @@ def Init(bluetoothAdapter: int = None): _GetLibraryHandle(False).pychip_CommonStackInit(ctypes.c_char_p(params)) -def GetLibraryHandle(): - return _GetLibraryHandle(True) +class HandleFlags(enum.Flag): + REQUIRE_INITIALIZATION = enum.auto() + + +def GetLibraryHandle(flags=HandleFlags.REQUIRE_INITIALIZATION): + return _GetLibraryHandle(HandleFlags.REQUIRE_INITIALIZATION in flags) diff --git a/src/controller/python/chip/tracing/TracingSetup.cpp b/src/controller/python/chip/tracing/TracingSetup.cpp new file mode 100644 index 00000000000000..d09a655b74c633 --- /dev/null +++ b/src/controller/python/chip/tracing/TracingSetup.cpp @@ -0,0 +1,104 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +using chip::DeviceLayer::PlatformMgr; + +class ScopedStackLock +{ +public: + ScopedStackLock() { PlatformMgr().LockChipStack(); } + + ~ScopedStackLock() { PlatformMgr().UnlockChipStack(); } +}; + +chip::Tracing::Json::JsonBackend gJsonBackend; + +chip::Tracing::Perfetto::FileTraceOutput gPerfettoFileOutput; +chip::Tracing::Perfetto::PerfettoBackend gPerfettoBackend; + +} // namespace + +extern "C" void pychip_tracing_start_json_log(const char * file_name) +{ + + ScopedStackLock lock; + + gJsonBackend.CloseFile(); // just in case, ensure no file output + chip::Tracing::Register(gJsonBackend); +} + +extern "C" PyChipError pychip_tracing_start_json_file(const char * file_name) +{ + ScopedStackLock lock; + + CHIP_ERROR err = gJsonBackend.OpenFile(file_name); + if (err != CHIP_NO_ERROR) + { + return ToPyChipError(err); + } + chip::Tracing::Register(gJsonBackend); + return ToPyChipError(CHIP_NO_ERROR); +} + +extern "C" void pychip_tracing_start_perfetto_system() +{ + ScopedStackLock lock; + + chip::Tracing::Perfetto::Initialize(perfetto::kSystemBackend); + chip::Tracing::Perfetto::RegisterEventTrackingStorage(); + chip::Tracing::Register(gPerfettoBackend); +} + +extern "C" PyChipError pychip_tracing_start_perfetto_file(const char * file_name) +{ + ScopedStackLock lock; + + chip::Tracing::Perfetto::Initialize(perfetto::kInProcessBackend); + chip::Tracing::Perfetto::RegisterEventTrackingStorage(); + + CHIP_ERROR err = gPerfettoFileOutput.Open(file_name); + if (err != CHIP_NO_ERROR) + { + return ToPyChipError(err); + } + chip::Tracing::Register(gPerfettoBackend); + + return ToPyChipError(CHIP_NO_ERROR); +} + +extern "C" void pychip_tracing_stop() +{ + ScopedStackLock lock; + + chip::Tracing::Perfetto::FlushEventTrackingStorage(); + gPerfettoFileOutput.Close(); + chip::Tracing::Unregister(gPerfettoBackend); + chip::Tracing::Unregister(gJsonBackend); +} diff --git a/src/controller/python/chip/tracing/__init__.py b/src/controller/python/chip/tracing/__init__.py new file mode 100644 index 00000000000000..fc6ee35f3b1bb9 --- /dev/null +++ b/src/controller/python/chip/tracing/__init__.py @@ -0,0 +1,125 @@ +# +# 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. +# + +import ctypes +from enum import Enum, auto +from typing import Optional + +import chip.native +from chip.native import PyChipError + + +def _GetTracingLibraryHandle() -> ctypes.CDLL: + """ Get the native library handle with tracing methods initialized. + + Retreives the CHIP native library handle and attaches signatures to + native methods. + """ + + # Getting a handle without requiring init, as tracing methods + # do not require chip stack startup + handle = chip.native.GetLibraryHandle(chip.native.HandleFlags(0)) + + # Uses one of the type decorators as an indicator for everything being + # initialized. + if not handle.pychip_tracing_start_json_file.argtypes: + setter = chip.native.NativeLibraryHandleMethodArguments(handle) + + setter.Set('pychip_tracing_start_json_log', None, []) + setter.Set('pychip_tracing_start_json_file', PyChipError, [ctypes.c_char_p]) + + setter.Set('pychip_tracing_start_perfetto_system', None, []) + setter.Set('pychip_tracing_start_perfetto_file', PyChipError, [ctypes.c_char_p]) + + setter.Set('pychip_tracing_stop', None, []) + + return handle + + +class TraceType(Enum): + JSON = auto() + PERFETTO = auto() + + +def StartTracingTo(trace_type: TraceType, file_name: Optional[str] = None): + """ + Initiate tracing to the specified destination. + + Note that only one active trace can exist of a given type (i.e. cannot trace both + to files and logs/system). + """ + handle = _GetTracingLibraryHandle() + + if trace_type == TraceType.JSON: + if file_name is None: + handle.pychip_tracing_start_json_log() + else: + handle.pychip_tracing_start_json_file(file_name.encode('utf-8')).raise_on_error() + elif trace_type == TraceType.PERFETTO: + if file_name is None: + handle.pychip_tracing_start_perfetto_system() + else: + handle.pychip_tracing_start_perfetto_file(file_name.encode('utf-8')).raise_on_error() + else: + raise ValueError("unknown trace type") + + +def StopTracing(): + """ + Make sure tracing is stopped. + + MUST be called before application exits. + """ + _GetTracingLibraryHandle().pychip_tracing_stop() + + +class TracingContext: + """Allows scoped enter/exit for tracing, like: + + with TracingContext() as tracing: + tracing.Start(TraceType.JSON) + # ... + + """ + + def Start(self, trace_type: TraceType, file_name: Optional[str] = None): + StartTracingTo(trace_type, file_name) + + def StartFromString(self, destination: str): + """ + Convert a human string to a perfetto start. + + Supports json:log, json:path, perfetto, perfetto:path + """ + if destination == 'perfetto': + self.Start(TraceType.PERFETTO) + elif destination == 'json:log': + self.Start(TraceType.JSON) + elif destination.startswith("json:"): + self.Start(TraceType.JSON, destination[5:]) + elif destination.startswith("perfetto:"): + self.Start(TraceType.PERFETTO, destination[9:]) + else: + raise ValueError("Invalid trace-to destination: %r", destination) + + def __init__(self): + pass + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + StopTracing() diff --git a/src/controller/python/test/test_scripts/mobile-device-test.py b/src/controller/python/test/test_scripts/mobile-device-test.py index 09f13ccb444843..87e0a9acb6569b 100755 --- a/src/controller/python/test/test_scripts/mobile-device-test.py +++ b/src/controller/python/test/test_scripts/mobile-device-test.py @@ -27,6 +27,7 @@ import click import coloredlogs from base import BaseTestHelper, FailIfNot, SetTestSet, TestFail, TestTimeout, logger +from chip.tracing import TracingContext from cluster_objects import ClusterObjectTests from network_commissioning import NetworkCommissioningTests @@ -246,8 +247,12 @@ def do_tests(controller_nodeid, device_nodeid, address, timeout, discriminator, default='', type=str, help="Path that contains valid and trusted PAA Root Certificates.") +@click.option('--trace-to', + multiple=True, + default=[], + help="Trace location") def run(controller_nodeid, device_nodeid, address, timeout, discriminator, setup_pin, enable_test, disable_test, log_level, - log_format, print_test_list, paa_trust_store_path): + log_format, print_test_list, paa_trust_store_path, trace_to): coloredlogs.install(level=log_level, fmt=log_format, logger=logger) if print_test_list: @@ -267,8 +272,12 @@ def run(controller_nodeid, device_nodeid, address, timeout, discriminator, setup logger.info(f"\tEnabled Tests: {enable_test}") logger.info(f"\tDisabled Tests: {disable_test}") SetTestSet(enable_test, disable_test) - do_tests(controller_nodeid, device_nodeid, address, timeout, - discriminator, setup_pin, paa_trust_store_path) + with TracingContext() as tracing_ctx: + for destination in trace_to: + tracing_ctx.StartFromString(destination) + + do_tests(controller_nodeid, device_nodeid, address, timeout, + discriminator, setup_pin, paa_trust_store_path) if __name__ == "__main__": diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index b51da99aed15e6..7570f3ddcbe415 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -51,6 +51,7 @@ from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.interaction_model import InteractionModelError, Status from chip.storage import PersistentStorage +from chip.tracing import TracingContext from mobly import asserts, base_test, signals, utils from mobly.config_parser import ENV_MOBLY_LOGPATH, TestRunConfig from mobly.test_runner import TestRunner @@ -265,6 +266,8 @@ class MatterTestConfig: # If this is set, we will reuse root of trust keys at that location chip_tool_credentials_path: Optional[pathlib.Path] = None + trace_to: List[str] = field(default_factory=list) + class ClusterMapper: """Describe clusters/attributes using schema names.""" @@ -817,6 +820,7 @@ def convert_args_to_matter_config(args: argparse.Namespace) -> MatterTestConfig: config.pics = {} if args.PICS is None else read_pics_from_file(args.PICS) config.controller_node_id = args.controller_node_id + config.trace_to = args.trace_to # Accumulate all command-line-passed named args all_global_args = [] @@ -847,7 +851,8 @@ def parse_matter_test_args(argv: List[str]) -> MatterTestConfig: type=str, metavar='test_a test_b...', help='A list of tests in the test class to execute.') - + basic_group.add_argument('--trace-to', nargs="*", default=[], + help="Where to trace (e.g perfetto, perfetto:path, json:log, json:path)") basic_group.add_argument('--storage-path', action="store", type=pathlib.Path, metavar="PATH", help="Location for persisted storage of instance") basic_group.add_argument('--logs-path', action="store", type=pathlib.Path, metavar="PATH", help="Location for test logs") @@ -1051,44 +1056,49 @@ def default_matter_test_main(argv=None, **kwargs): matter_test_config.maximize_cert_chains = kwargs["maximize_cert_chains"] stack = MatterStackState(matter_test_config) - test_config.user_params["matter_stack"] = stash_globally(stack) - # TODO: Steer to right FabricAdmin! - # TODO: If CASE Admin Subject is a CAT tag range, then make sure to issue NOC with that CAT tag + with TracingContext() as tracing_ctx: + for destination in matter_test_config.trace_to: + tracing_ctx.StartFromString(destination) + + test_config.user_params["matter_stack"] = stash_globally(stack) + + # TODO: Steer to right FabricAdmin! + # TODO: If CASE Admin Subject is a CAT tag range, then make sure to issue NOC with that CAT tag - default_controller = stack.certificate_authorities[0].adminList[0].NewController( - nodeId=matter_test_config.controller_node_id, - paaTrustStorePath=str(matter_test_config.paa_trust_store_path), - catTags=matter_test_config.controller_cat_tags - ) - test_config.user_params["default_controller"] = stash_globally(default_controller) + default_controller = stack.certificate_authorities[0].adminList[0].NewController( + nodeId=matter_test_config.controller_node_id, + paaTrustStorePath=str(matter_test_config.paa_trust_store_path), + catTags=matter_test_config.controller_cat_tags + ) + test_config.user_params["default_controller"] = stash_globally(default_controller) - test_config.user_params["matter_test_config"] = stash_globally(matter_test_config) + test_config.user_params["matter_test_config"] = stash_globally(matter_test_config) - test_config.user_params["certificate_authority_manager"] = stash_globally(stack.certificate_authority_manager) + test_config.user_params["certificate_authority_manager"] = stash_globally(stack.certificate_authority_manager) - # Execute the test class with the config - ok = True + # Execute the test class with the config + ok = True - runner = TestRunner(log_dir=test_config.log_path, - testbed_name=test_config.testbed_name) + runner = TestRunner(log_dir=test_config.log_path, + testbed_name=test_config.testbed_name) - with runner.mobly_logger(): - if matter_test_config.commissioning_method is not None: - runner.add_test_class(test_config, CommissionDeviceTest, None) + with runner.mobly_logger(): + if matter_test_config.commissioning_method is not None: + runner.add_test_class(test_config, CommissionDeviceTest, None) - # Add the tests selected unless we have a commission-only request - if not matter_test_config.commission_only: - runner.add_test_class(test_config, test_class, tests) + # Add the tests selected unless we have a commission-only request + if not matter_test_config.commission_only: + runner.add_test_class(test_config, test_class, tests) - try: - runner.run() - ok = runner.results.is_all_pass and ok - except signals.TestAbortAll: - ok = False - except Exception: - logging.exception('Exception when executing %s.', test_config.testbed_name) - ok = False + try: + runner.run() + ok = runner.results.is_all_pass and ok + except signals.TestAbortAll: + ok = False + except Exception: + logging.exception('Exception when executing %s.', test_config.testbed_name) + ok = False # Shutdown the stack when all done stack.Shutdown() From c9ce7f21aff97aa0520ab778ae86d07af8bff507 Mon Sep 17 00:00:00 2001 From: lpbeliveau-silabs <112982107+lpbeliveau-silabs@users.noreply.github.com> Date: Mon, 24 Jul 2023 20:27:39 -0400 Subject: [PATCH 5/6] [ReadHandler] Integration of ReportScheduler into the ReadHandler and IM engine. (#28104) * Integration of ReportSchedulers into the ReadHandler and IM engine. Also added a static report scheduler to use in all test relying on the IM engine, also added test flags in ReportScheduler replace ReadHandler timing flags Refactored the TestReadInteraction to adopt correct behavior * Restyled by clang-format * Restyled by gn * Moved namespaces into class to avoid namespace pollution * Fix missing pragma once and copyright * Update src/app/reporting/ReportScheduler.h Co-authored-by: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Removed OnIntervalsChanged, removed TestReadInteraction from ReportScheduler's friend class and refactored the tests to use wrapper functions, refactored tests for the removal of OnIntervalsChanged, removed reportscheduler nullptr check form controllers * Restyled by clang-format * Fix for the TimerDelegates And also added a default Scheduler to controllers * Restyled by clang-format * Added a timer Context class to allow interface between timer delegates, ReadHandlerNodes and SynchronisedReport Scheduler * Fix using InteractionModel... error * Added Platform::Delete() for allocated TimerDelegate and ReportScheduler on controller * Update src/app/ReadHandler.cpp Co-authored-by: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> * Update src/app/TimerDelegates.h Co-authored-by: Boris Zbarsky * Addressed PR comments, next step trying to reduce RAM bloat * Restyled by clang-format * Removed IntrusiveList from the report Scheduler * Removed ReadHandlerNode's inheritance of IntrusiveListBase * Restyled by clang-format * Fix commented code and comment syntax * Restyled by clang-format * Update src/app/tests/TestReportScheduler.cpp Co-authored-by: Boris Zbarsky * Added extra check on timerDelegate in DeviceController, removed logging causing failure in linux CI * Restyled by clang-format --------- Co-authored-by: Restyled.io Co-authored-by: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> Co-authored-by: Boris Zbarsky --- src/app/InteractionModelEngine.cpp | 8 +- src/app/InteractionModelEngine.h | 6 +- src/app/ReadHandler.cpp | 159 ++--------- src/app/ReadHandler.h | 66 +---- src/app/TimerDelegates.h | 57 ++++ src/app/reporting/Engine.cpp | 3 +- src/app/reporting/ReportScheduler.h | 112 ++++++-- src/app/reporting/ReportSchedulerImpl.cpp | 28 +- .../SynchronizedReportSchedulerImpl.cpp | 92 +++--- .../SynchronizedReportSchedulerImpl.h | 6 +- .../reporting/tests/MockReportScheduler.cpp | 46 +++ src/app/reporting/tests/MockReportScheduler.h | 34 +++ src/app/server/Server.cpp | 4 +- src/app/server/Server.h | 17 +- src/app/tests/AppTestContext.cpp | 4 +- src/app/tests/BUILD.gn | 1 + src/app/tests/TestAclAttribute.cpp | 3 +- src/app/tests/TestAclEvent.cpp | 3 +- src/app/tests/TestInteractionModelEngine.cpp | 10 +- src/app/tests/TestReadInteraction.cpp | 261 +++++++++++------- src/app/tests/TestReportScheduler.cpp | 200 +++++++------- src/app/tests/TestReportingEngine.cpp | 13 +- src/app/tests/TestWriteInteraction.cpp | 21 +- src/app/tests/integration/BUILD.gn | 2 + .../tests/integration/chip_im_initiator.cpp | 4 +- .../tests/integration/chip_im_responder.cpp | 4 +- .../CHIPDeviceControllerFactory.cpp | 20 +- src/controller/CHIPDeviceControllerFactory.h | 1 + .../CHIPDeviceControllerSystemState.h | 16 +- src/lib/core/CHIPConfig.h | 14 +- 30 files changed, 711 insertions(+), 504 deletions(-) create mode 100644 src/app/TimerDelegates.h create mode 100644 src/app/reporting/tests/MockReportScheduler.cpp create mode 100644 src/app/reporting/tests/MockReportScheduler.h diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 569d0b0fdf4a4e..933b5a4c118254 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -51,16 +51,18 @@ InteractionModelEngine * InteractionModelEngine::GetInstance() } CHIP_ERROR InteractionModelEngine::Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable, - CASESessionManager * apCASESessionMgr, + reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr, SubscriptionResumptionStorage * subscriptionResumptionStorage) { VerifyOrReturnError(apFabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(reportScheduler != nullptr, CHIP_ERROR_INVALID_ARGUMENT); mpExchangeMgr = apExchangeMgr; mpFabricTable = apFabricTable; mpCASESessionMgr = apCASESessionMgr; mpSubscriptionResumptionStorage = subscriptionResumptionStorage; + mReportScheduler = reportScheduler; ReturnErrorOnFailure(mpFabricTable->AddFabricDelegate(this)); ReturnErrorOnFailure(mpExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id, this)); @@ -741,7 +743,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest // We have already reserved enough resources for read requests, and have granted enough resources for current subscriptions, so // we should be able to allocate resources requested by this request. - ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType); + ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType, mReportScheduler); if (handler == nullptr) { ChipLogProgress(InteractionModel, "no resource for %s interaction", @@ -1845,7 +1847,7 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap return; } - ReadHandler * handler = imEngine->mReadHandlers.CreateObject(*imEngine); + ReadHandler * handler = imEngine->mReadHandlers.CreateObject(*imEngine, imEngine->GetReportScheduler()); if (handler == nullptr) { ChipLogProgress(InteractionModel, "no resource for ReadHandler creation"); diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 9e6ed36cce2396..6236ff63efc5ec 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -115,7 +116,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, * */ CHIP_ERROR Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable, - CASESessionManager * apCASESessionMgr = nullptr, + reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr = nullptr, SubscriptionResumptionStorage * subscriptionResumptionStorage = nullptr); void Shutdown(); @@ -178,6 +179,8 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, reporting::Engine & GetReportingEngine() { return mReportingEngine; } + reporting::ReportScheduler * GetReportScheduler() { return mReportScheduler; } + void ReleaseAttributePathList(ObjectList *& aAttributePathList); CHIP_ERROR PushFrontAttributePathList(ObjectList *& aAttributePathList, @@ -566,6 +569,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, ObjectPool mTimedHandlers; WriteHandler mWriteHandlers[CHIP_IM_MAX_NUM_WRITE_HANDLER]; reporting::Engine mReportingEngine; + reporting::ReportScheduler * mReportScheduler = nullptr; static constexpr size_t kReservedHandlersForReads = kMinSupportedReadRequestsPerFabric * (CHIP_CONFIG_MAX_FABRICS); static constexpr size_t kReservedPathsForReads = kMinSupportedPathsPerReadRequest * kReservedHandlersForReads; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index bbaf9f7992eb30..1c3a40cc962273 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -77,16 +77,9 @@ ReadHandler::ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeCon mSessionHandle.Grab(mExchangeCtx->GetSessionHandle()); -// TODO (#27672): Uncomment when the ReportScheduler is implemented -#if 0 - if (nullptr != observer) - { - if (CHIP_NO_ERROR == SetObserver(observer)) - { - mObserver->OnReadHandlerCreated(this); - } - } -#endif + VerifyOrDie(observer != nullptr); + mObserver = observer; + mObserver->OnReadHandlerCreated(this); } #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS @@ -97,16 +90,9 @@ ReadHandler::ReadHandler(ManagementCallback & apCallback, Observer * observer) : mInteractionType = InteractionType::Subscribe; mFlags.ClearAll(); -// TODO (#27672): Uncomment when the ReportScheduler is implemented -#if 0 - if (nullptr != observer) - { - if (CHIP_NO_ERROR == SetObserver(observer)) - { - mObserver->OnReadHandlerCreated(this); - } - } -#endif + VerifyOrDie(observer != nullptr); + mObserver = observer; + mObserver->OnReadHandlerCreated(this); } void ReadHandler::ResumeSubscription(CASESessionManager & caseSessionManager, @@ -150,28 +136,14 @@ void ReadHandler::ResumeSubscription(CASESessionManager & caseSessionManager, ReadHandler::~ReadHandler() { - // TODO (#27672): Enable when the ReportScheduler is implemented and move in Close() after testing -#if 0 - if (nullptr != mObserver) - { - mObserver->OnReadHandlerDestroyed(this); - } -#endif + mObserver->OnReadHandlerDestroyed(this); + auto * appCallback = mManagementCallback.GetAppCallback(); if (mFlags.Has(ReadHandlerFlags::ActiveSubscription) && appCallback) { appCallback->OnSubscriptionTerminated(*this); } - if (IsType(InteractionType::Subscribe)) - { - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MinIntervalExpiredCallback, this); - - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MaxIntervalExpiredCallback, this); - } - if (IsAwaitingReportResponse()) { InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); @@ -290,7 +262,7 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aStatus) { - VerifyOrReturnLogError(IsReportableNow(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnLogError(mState == HandlerState::GeneratingReports, CHIP_ERROR_INCORRECT_STATE); if (IsPriming() || IsChunkedReport()) { mSessionHandle.Grab(mExchangeCtx->GetSessionHandle()); @@ -314,7 +286,7 @@ CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aSt CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, bool aMoreChunks) { - VerifyOrReturnLogError(IsReportableNow(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnLogError(mState == HandlerState::GeneratingReports, CHIP_ERROR_INCORRECT_STATE); VerifyOrDie(!IsAwaitingReportResponse()); // Should not be reportable! if (IsPriming() || IsChunkedReport()) { @@ -359,21 +331,11 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, b InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); } - if (IsType(InteractionType::Subscribe) && !IsPriming()) + // If we just finished a non-priming subscription report, notify our observers. + // Priming reports are handled when we send a SubscribeResponse. + if (IsType(InteractionType::Subscribe) && !IsPriming() && !IsChunkedReport()) { -// TODO (#27672): Enable when the ReportScheduler is implemented and remove call to UpdateReportTimer, will be handled by -// the report Scheduler -#if 0 - if (nullptr != mObserver) - { - mObserver->OnSubscriptionAction(this); - } -#endif - - // Ignore the error from UpdateReportTimer. If we've - // successfully sent the message, we need to return success from - // this method. - UpdateReportTimer(); + mObserver->OnSubscriptionAction(this); } } if (!aMoreChunks) @@ -641,16 +603,10 @@ void ReadHandler::MoveToState(const HandlerState aTargetState) // If we just unblocked sending reports, let's go ahead and schedule the reporting // engine to run to kick that off. // - if (aTargetState == HandlerState::GeneratingReports && IsReportableNow()) + if (aTargetState == HandlerState::GeneratingReports) { -// TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun() -#if 0 - if(nullptr != mObserver) - { - mObserver->OnBecameReportable(this); - } -#endif - InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); + // mObserver will take care of scheduling the report as soon as allowed + mObserver->OnBecameReportable(this); } } @@ -691,15 +647,7 @@ CHIP_ERROR ReadHandler::SendSubscribeResponse() ReturnErrorOnFailure(writer.Finalize(&packet)); VerifyOrReturnLogError(mExchangeCtx, CHIP_ERROR_INCORRECT_STATE); - // TODO (#27672): Uncomment when the ReportScheduler is implemented and remove call to UpdateReportTimer, handled by - // the report Scheduler -#if 0 - if (nullptr != mObserver) - { - mObserver->OnSubscriptionAction(this); - } -#endif - ReturnErrorOnFailure(UpdateReportTimer()); + mObserver->OnSubscriptionAction(this); ClearStateFlag(ReadHandlerFlags::PrimingReports); return mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::SubscribeResponse, std::move(packet)); @@ -818,50 +766,6 @@ void ReadHandler::PersistSubscription() } } -// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler -void ReadHandler::MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState) -{ - VerifyOrReturn(apAppState != nullptr); - ReadHandler * readHandler = static_cast(apAppState); - ChipLogDetail(DataManagement, "Unblock report hold after min %d seconds", readHandler->mMinIntervalFloorSeconds); - readHandler->ClearStateFlag(ReadHandlerFlags::WaitingUntilMinInterval); - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer( - System::Clock::Seconds16(readHandler->mMaxInterval - readHandler->mMinIntervalFloorSeconds), MaxIntervalExpiredCallback, - readHandler); -} - -// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler -void ReadHandler::MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState) -{ - VerifyOrReturn(apAppState != nullptr); - ReadHandler * readHandler = static_cast(apAppState); - readHandler->ClearStateFlag(ReadHandlerFlags::WaitingUntilMaxInterval); - ChipLogProgress(DataManagement, "Refresh subscribe timer sync after %d seconds", - readHandler->mMaxInterval - readHandler->mMinIntervalFloorSeconds); -} - -// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler -CHIP_ERROR ReadHandler::UpdateReportTimer() -{ - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MinIntervalExpiredCallback, this); - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MaxIntervalExpiredCallback, this); - - if (!IsChunkedReport()) - { - ChipLogProgress(DataManagement, "Refresh Subscribe Sync Timer with min %d seconds and max %d seconds", - mMinIntervalFloorSeconds, mMaxInterval); - SetStateFlag(ReadHandlerFlags::WaitingUntilMinInterval); - SetStateFlag(ReadHandlerFlags::WaitingUntilMaxInterval); - ReturnErrorOnFailure( - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer( - System::Clock::Seconds16(mMinIntervalFloorSeconds), MinIntervalExpiredCallback, this)); - } - - return CHIP_NO_ERROR; -} - void ReadHandler::ResetPathIterator() { mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList); @@ -897,17 +801,8 @@ void ReadHandler::AttributePathIsDirty(const AttributePathParams & aAttributeCha mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState(); } - if (IsReportableNow()) - { - // TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun() -#if 0 - if(nullptr != mObserver) - { - mObserver->OnBecameReportable(this); - } -#endif - InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); - } + // ReportScheduler will take care of verifying the reportability of the handler and schedule the run + mObserver->OnBecameReportable(this); } Transport::SecureSession * ReadHandler::GetSession() const @@ -926,20 +821,14 @@ void ReadHandler::ForceDirtyState() void ReadHandler::SetStateFlag(ReadHandlerFlags aFlag, bool aValue) { - bool oldReportable = IsReportableNow(); + bool oldReportable = IsReportable(); mFlags.Set(aFlag, aValue); // If we became reportable, schedule a reporting run. - if (!oldReportable && IsReportableNow()) + if (!oldReportable && IsReportable()) { -// TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun() -#if 0 - if(nullptr != mObserver) - { - mObserver->OnBecameReportable(this); - } -#endif - InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); + // If we became reportable, the scheduler will schedule a run as soon as allowed + mObserver->OnBecameReportable(this); } } diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 5b9bb346eb7894..5f1d3221886084 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -202,7 +202,7 @@ class ReadHandler : public Messaging::ExchangeDelegate * */ ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext, InteractionType aInteractionType, - Observer * observer = nullptr); + Observer * observer); #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS /** @@ -212,7 +212,7 @@ class ReadHandler : public Messaging::ExchangeDelegate * The callback passed in has to outlive this handler object. * */ - ReadHandler(ManagementCallback & apCallback, Observer * observer = nullptr); + ReadHandler(ManagementCallback & apCallback, Observer * observer); #endif const ObjectList * GetAttributePathList() const { return mpAttributePathList; } @@ -250,18 +250,6 @@ class ReadHandler : public Messaging::ExchangeDelegate return CHIP_NO_ERROR; } - /// @brief Add an observer to the read handler, currently only one observer is supported but all other callbacks should be - /// merged with a general observer type to allow multiple object to observe readhandlers - /// @param aObserver observer to be added - /// @return CHIP_ERROR_INVALID_ARGUMENT if passing in nullptr - CHIP_ERROR SetObserver(Observer * aObserver) - { - VerifyOrReturnError(nullptr != aObserver, CHIP_ERROR_INVALID_ARGUMENT); - // TODO (#27675) : After merging the callbacks and observer, change so the method adds a new observer to an observer pool - mObserver = aObserver; - return CHIP_NO_ERROR; - } - private: PriorityLevel GetCurrentPriority() const { return mCurrentPriority; } EventNumber & GetEventMin() { return mEventMin; } @@ -275,26 +263,16 @@ class ReadHandler : public Messaging::ExchangeDelegate enum class ReadHandlerFlags : uint8_t { - // WaitingUntilMinInterval is used to prevent subscription data delivery while we are - // waiting for the min reporting interval to elapse. - WaitingUntilMinInterval = (1 << 0), // TODO (#27672): Remove once ReportScheduler is implemented or change to test flag - - // WaitingUntilMaxInterval is used to prevent subscription empty report delivery while we - // are waiting for the max reporting interval to elaps. When WaitingUntilMaxInterval - // becomes false, we are allowed to send an empty report to keep the - // subscription alive on the client. - WaitingUntilMaxInterval = (1 << 1), // TODO (#27672): Remove once ReportScheduler is implemented - // The flag indicating we are in the middle of a series of chunked report messages, this flag will be cleared during // sending last chunked message. - ChunkedReport = (1 << 2), + ChunkedReport = (1 << 0), // Tracks whether we're in the initial phase of receiving priming // reports, which is always true for reads and true for subscriptions // prior to receiving a subscribe response. - PrimingReports = (1 << 3), - ActiveSubscription = (1 << 4), - FabricFiltered = (1 << 5), + PrimingReports = (1 << 1), + ActiveSubscription = (1 << 2), + FabricFiltered = (1 << 3), // For subscriptions, we record the dirty set generation when we started to generate the last report. // The mCurrentReportsBeginGeneration records the generation at the start of the current report. This only/ // has a meaningful value while IsReporting() is true. @@ -304,10 +282,10 @@ class ReadHandler : public Messaging::ExchangeDelegate // mPreviousReportsBeginGeneration has had its value sent to the client. // when receiving initial request, it needs mark current handler as dirty. // when there is urgent event, it needs mark current handler as dirty. - ForceDirty = (1 << 6), + ForceDirty = (1 << 4), // Don't need the response for report data if true - SuppressResponse = (1 << 7), + SuppressResponse = (1 << 5), }; /** @@ -354,17 +332,12 @@ class ReadHandler : public Messaging::ExchangeDelegate bool IsIdle() const { return mState == HandlerState::Idle; } - // TODO (#27672): Change back to IsReportable once ReportScheduler is implemented so this can assess reportability without - // considering timing. The ReporScheduler will handle timing. - /// @brief Returns whether the ReadHandler is in a state where it can immediately send a report. This function - /// is used to determine whether a report generation should be scheduled for the handler. - bool IsReportableNow() const + /// @brief Returns whether the ReadHandler is in a state where it can send a report and there is data to report. + bool IsReportable() const { - // Important: Anything that changes the state IsReportableNow depends on in - // a way that causes IsReportableNow to become true must call ScheduleRun - // on the reporting engine. - return mState == HandlerState::GeneratingReports && !mFlags.Has(ReadHandlerFlags::WaitingUntilMinInterval) && - (IsDirty() || !mFlags.Has(ReadHandlerFlags::WaitingUntilMaxInterval)); + // Important: Anything that changes the state IsReportable must call mObserver->OnBecameReportable(this) for the scheduler + // to plan the next run accordingly. + return mState == HandlerState::GeneratingReports && IsDirty(); } bool IsGeneratingReports() const { return mState == HandlerState::GeneratingReports; } bool IsAwaitingReportResponse() const { return mState == HandlerState::AwaitingReportResponse; } @@ -445,8 +418,8 @@ class ReadHandler : public Messaging::ExchangeDelegate friend class chip::app::reporting::Engine; friend class chip::app::InteractionModelEngine; - // The report scheduler needs to be able to access StateFlag private functions IsGeneratingReports() and IsDirty() to - // know when to schedule a run so it is declared as a friend class. + // The report scheduler needs to be able to access StateFlag private functions IsReportable(), IsGeneratingReports() and + // IsDirty() to know when to schedule a run so it is declared as a friend class. friend class chip::app::reporting::ReportScheduler; enum class HandlerState : uint8_t @@ -472,15 +445,6 @@ class ReadHandler : public Messaging::ExchangeDelegate */ void Close(CloseOptions options = CloseOptions::kDropPersistedSubscription); - /// @brief This function is called when the min interval timer has expired, it restarts the timer on a timeout equal to the - /// difference between the max interval and the min interval. - static void MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState); // TODO (#27672): Remove once - // ReportScheduler is implemented. - static void MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState); // TODO (#27672): Remove once - // ReportScheduler is implemented. - /// @brief This function is called when a report is sent and it restarts the min interval timer. - CHIP_ERROR UpdateReportTimer(); // TODO (#27672) : Remove once ReportScheduler is implemented. - CHIP_ERROR SendSubscribeResponse(); CHIP_ERROR ProcessSubscribeRequest(System::PacketBufferHandle && aPayload); CHIP_ERROR ProcessReadRequest(System::PacketBufferHandle && aPayload); diff --git a/src/app/TimerDelegates.h b/src/app/TimerDelegates.h new file mode 100644 index 00000000000000..8e24fd4aef09bd --- /dev/null +++ b/src/app/TimerDelegates.h @@ -0,0 +1,57 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +class DefaultTimerDelegate : public reporting::ReportScheduler::TimerDelegate +{ +public: + using TimerContext = reporting::TimerContext; + using Timeout = System::Clock::Timeout; + static void TimerCallbackInterface(System::Layer * aLayer, void * aAppState) + { + TimerContext * context = static_cast(aAppState); + context->TimerFired(); + } + CHIP_ERROR StartTimer(TimerContext * context, Timeout aTimeout) override + { + return InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer( + aTimeout, TimerCallbackInterface, context); + } + void CancelTimer(TimerContext * context) override + { + InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( + TimerCallbackInterface, context); + } + bool IsTimerActive(TimerContext * context) override + { + return InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->IsTimerActive( + TimerCallbackInterface, context); + } + + System::Clock::Timestamp GetCurrentMonotonicTimestamp() override { return System::SystemClock().GetMonotonicTimestamp(); } +}; + +} // namespace app +} // namespace chip diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index f0fea66cc0b16c..1243fff3ac1775 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -636,8 +636,7 @@ void Engine::Run() ReadHandler * readHandler = imEngine->ActiveHandlerAt(mCurReadHandlerIdx % (uint32_t) imEngine->mReadHandlers.Allocated()); VerifyOrDie(readHandler != nullptr); - // TODO (#27672): Replace with check with Report Scheduler if the read handler is reportable - if (readHandler->IsReportableNow()) + if (imEngine->GetReportScheduler()->IsReportableNow(readHandler)) { mRunningReadHandler = readHandler; CHIP_ERROR err = BuildAndSendSingleReportData(readHandler); diff --git a/src/app/reporting/ReportScheduler.h b/src/app/reporting/ReportScheduler.h index 0449ba3114f857..d691e4b36889ed 100644 --- a/src/app/reporting/ReportScheduler.h +++ b/src/app/reporting/ReportScheduler.h @@ -20,7 +20,6 @@ #include #include -#include #include namespace chip { @@ -32,6 +31,13 @@ class TestReportScheduler; using Timestamp = System::Clock::Timestamp; +class TimerContext +{ +public: + virtual ~TimerContext() {} + virtual void TimerFired() = 0; +}; + class ReportScheduler : public ReadHandler::Observer { public: @@ -45,17 +51,28 @@ class ReportScheduler : public ReadHandler::Observer /// CancelTimer) before starting a new one for that context. /// @param context context to pass to the timer callback. /// @param aTimeout time in miliseconds before the timer expires - virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) = 0; + virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) = 0; /// @brief Cancel a timer for a given context /// @param context used to identify the timer to cancel - virtual void CancelTimer(void * context) = 0; - virtual bool IsTimerActive(void * context) = 0; - virtual Timestamp GetCurrentMonotonicTimestamp() = 0; + virtual void CancelTimer(TimerContext * context) = 0; + virtual bool IsTimerActive(TimerContext * context) = 0; + virtual Timestamp GetCurrentMonotonicTimestamp() = 0; }; - class ReadHandlerNode : public IntrusiveListNodeBase<> + class ReadHandlerNode : public TimerContext { public: +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + /// Test flags to allow TestReadInteraction to simulate expiration of the minimal and maximal intervals without + /// waiting + enum class TestFlags : uint8_t{ + MinIntervalElapsed = (1 << 0), + MaxIntervalElapsed = (1 << 1), + }; + void SetTestFlags(TestFlags aFlag, bool aValue) { mFlags.Set(aFlag, aValue); } + bool GetTestFlags(TestFlags aFlag) const { return mFlags.Has(aFlag); } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST + ReadHandlerNode(ReadHandler * aReadHandler, TimerDelegate * aTimerDelegate, ReportScheduler * aScheduler) : mTimerDelegate(aTimerDelegate), mScheduler(aScheduler) { @@ -67,20 +84,37 @@ class ReportScheduler : public ReadHandler::Observer SetIntervalTimeStamps(aReadHandler); } ReadHandler * GetReadHandler() const { return mReadHandler; } + /// @brief Check if the Node is reportable now, meaning its readhandler was made reportable by attribute dirtying and /// handler state, and minimal time interval since last report has elapsed, or the maximal time interval since last /// report has elapsed bool IsReportableNow() const { - // TODO: Add flags to allow for test to simulate waiting for the min interval or max intrval to elapse when integrating - // the scheduler in the ReadHandler Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); + +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + return (mReadHandler->IsGeneratingReports() && (now >= mMinTimestamp || mFlags.Has(TestFlags::MinIntervalElapsed)) && + (mReadHandler->IsDirty() || (now >= mMaxTimestamp || mFlags.Has(TestFlags::MaxIntervalElapsed)) || + now >= mSyncTimestamp)); +#else return (mReadHandler->IsGeneratingReports() && (now >= mMinTimestamp && (mReadHandler->IsDirty() || now >= mMaxTimestamp || now >= mSyncTimestamp))); +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST } bool IsEngineRunScheduled() const { return mEngineRunScheduled; } - void SetEngineRunScheduled(bool aEnginRunScheduled) { mEngineRunScheduled = aEnginRunScheduled; } + void SetEngineRunScheduled(bool aEngineRunScheduled) + { + mEngineRunScheduled = aEngineRunScheduled; +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + // If the engine becomes unscheduled, this means a run just took place so we reset the test flags + if (!mEngineRunScheduled) + { + mFlags.Set(TestFlags::MinIntervalElapsed, false); + mFlags.Set(TestFlags::MaxIntervalElapsed, false); + } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST + } void SetIntervalTimeStamps(ReadHandler * aReadHandler) { @@ -92,7 +126,7 @@ class ReportScheduler : public ReadHandler::Observer mSyncTimestamp = mMaxTimestamp; } - void RunCallback() + void TimerFired() override { mScheduler->ReportTimerCallback(); SetEngineRunScheduled(true); @@ -111,6 +145,9 @@ class ReportScheduler : public ReadHandler::Observer System::Clock::Timestamp GetSyncTimestamp() const { return mSyncTimestamp; } private: +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + BitFlags mFlags; +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST TimerDelegate * mTimerDelegate; ReadHandler * mReadHandler; ReportScheduler * mScheduler; @@ -132,19 +169,42 @@ class ReportScheduler : public ReadHandler::Observer /// @brief Check whether a ReadHandler is reportable right now, taking into account its minimum and maximum intervals. /// @param aReadHandler read handler to check - bool IsReportableNow(ReadHandler * aReadHandler) - { - return FindReadHandlerNode(aReadHandler)->IsReportableNow(); - } // TODO: Change the IsReportableNow to IsReportable() for readHandlers + bool IsReportableNow(ReadHandler * aReadHandler) { return FindReadHandlerNode(aReadHandler)->IsReportableNow(); } /// @brief Check if a ReadHandler is reportable without considering the timing - bool IsReadHandlerReportable(ReadHandler * aReadHandler) const - { - return aReadHandler->IsGeneratingReports() && aReadHandler->IsDirty(); - } + bool IsReadHandlerReportable(ReadHandler * aReadHandler) const { return aReadHandler->IsReportable(); } /// @brief Get the number of ReadHandlers registered in the scheduler's node pool size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); } +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + void RunNodeCallbackForHandler(const ReadHandler * aReadHandler) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + node->TimerFired(); + } + void SetFlagsForHandler(const ReadHandler * aReadHandler, ReadHandlerNode::TestFlags aFlag, bool aValue) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + node->SetTestFlags(aFlag, aValue); + } + + bool CheckFlagsForHandler(const ReadHandler * aReadHandler, ReadHandlerNode::TestFlags aFlag) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + return node->GetTestFlags(aFlag); + } + Timestamp GetMinTimestampForHandler(const ReadHandler * aReadHandler) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + return node->GetMinTimestamp(); + } + Timestamp GetMaxTimestampForHandler(const ReadHandler * aReadHandler) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + return node->GetMaxTimestamp(); + } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST + protected: friend class chip::app::reporting::TestReportScheduler; @@ -153,17 +213,19 @@ class ReportScheduler : public ReadHandler::Observer /// @return Node Address if node was found, nullptr otherwise ReadHandlerNode * FindReadHandlerNode(const ReadHandler * aReadHandler) { - for (auto & iter : mReadHandlerList) - { - if (iter.GetReadHandler() == aReadHandler) + ReadHandlerNode * foundNode = nullptr; + mNodesPool.ForEachActiveObject([&foundNode, aReadHandler](ReadHandlerNode * node) { + if (node->GetReadHandler() == aReadHandler) { - return &iter; + foundNode = node; + return Loop::Break; } - } - return nullptr; + + return Loop::Continue; + }); + return foundNode; } - IntrusiveList mReadHandlerList; ObjectPool mNodesPool; TimerDelegate * mTimerDelegate; }; diff --git a/src/app/reporting/ReportSchedulerImpl.cpp b/src/app/reporting/ReportSchedulerImpl.cpp index b49884aa3f699f..b6170fba9e0fdf 100644 --- a/src/app/reporting/ReportSchedulerImpl.cpp +++ b/src/app/reporting/ReportSchedulerImpl.cpp @@ -46,7 +46,6 @@ void ReportSchedulerImpl::OnReadHandlerCreated(ReadHandler * aReadHandler) // The NodePool is the same size as the ReadHandler pool from the IM Engine, so we don't need a check for size here since if a // ReadHandler was created, space should be available. newNode = mNodesPool.CreateObject(aReadHandler, mTimerDelegate, this); - mReadHandlerList.PushBack(newNode); ChipLogProgress(DataManagement, "Registered a ReadHandler that will schedule a report between system Timestamp: %" PRIu64 @@ -59,23 +58,21 @@ void ReportSchedulerImpl::OnReadHandlerCreated(ReadHandler * aReadHandler) ScheduleReport(newTimeout, newNode); } -/// @brief When a ReadHandler becomes reportable, schedule, verifies if the min interval of a handleris elapsed. If not, -/// reschedule the report to happen when the min interval is elapsed. If it is, schedule an engine run. +/// @brief When a ReadHandler becomes reportable, schedule, recalculate and reschedule the report. void ReportSchedulerImpl::OnBecameReportable(ReadHandler * aReadHandler) { ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); VerifyOrReturn(nullptr != node); - Milliseconds32 newTimeout; CalculateNextReportTimeout(newTimeout, node); ScheduleReport(newTimeout, node); } -void ReportSchedulerImpl::OnSubscriptionAction(ReadHandler * apReadHandler) +void ReportSchedulerImpl::OnSubscriptionAction(ReadHandler * aReadHandler) { - ReadHandlerNode * node = FindReadHandlerNode(apReadHandler); + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); VerifyOrReturn(nullptr != node); - node->SetIntervalTimeStamps(apReadHandler); + node->SetIntervalTimeStamps(aReadHandler); Milliseconds32 newTimeout; CalculateNextReportTimeout(newTimeout, node); ScheduleReport(newTimeout, node); @@ -91,7 +88,6 @@ void ReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler) // Nothing to remove if the handler is not found in the list VerifyOrReturn(nullptr != removeNode); - mReadHandlerList.Remove(removeNode); mNodesPool.ReleaseObject(removeNode); } @@ -99,6 +95,11 @@ CHIP_ERROR ReportSchedulerImpl::ScheduleReport(Timeout timeout, ReadHandlerNode { // Cancel Report if it is currently scheduled mTimerDelegate->CancelTimer(node); + if (timeout == Milliseconds32(0)) + { + node->TimerFired(); + return CHIP_NO_ERROR; + } ReturnErrorOnFailure(mTimerDelegate->StartTimer(node, timeout)); return CHIP_NO_ERROR; @@ -113,11 +114,10 @@ void ReportSchedulerImpl::CancelReport(ReadHandler * aReadHandler) void ReportSchedulerImpl::UnregisterAllHandlers() { - while (!mReadHandlerList.Empty()) - { - ReadHandler * firstReadHandler = mReadHandlerList.begin()->GetReadHandler(); - OnReadHandlerDestroyed(firstReadHandler); - } + mNodesPool.ForEachActiveObject([this](ReadHandlerNode * node) { + this->OnReadHandlerDestroyed(node->GetReadHandler()); + return Loop::Continue; + }); } bool ReportSchedulerImpl::IsReportScheduled(ReadHandler * aReadHandler) @@ -129,7 +129,7 @@ bool ReportSchedulerImpl::IsReportScheduled(ReadHandler * aReadHandler) CHIP_ERROR ReportSchedulerImpl::CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aNode) { - VerifyOrReturnError(mReadHandlerList.Contains(aNode), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nullptr != FindReadHandlerNode(aNode->GetReadHandler()), CHIP_ERROR_INVALID_ARGUMENT); Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); // If the handler is reportable now, just schedule a report immediately diff --git a/src/app/reporting/SynchronizedReportSchedulerImpl.cpp b/src/app/reporting/SynchronizedReportSchedulerImpl.cpp index 16713e37e05741..b3f700b8cbcc0f 100644 --- a/src/app/reporting/SynchronizedReportSchedulerImpl.cpp +++ b/src/app/reporting/SynchronizedReportSchedulerImpl.cpp @@ -29,16 +29,15 @@ using ReadHandlerNode = ReportScheduler::ReadHandlerNode; void SynchronizedReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler) { // Verify list is populated - VerifyOrReturn((!mReadHandlerList.Empty())); + VerifyOrReturn(mNodesPool.Allocated()); ReadHandlerNode * removeNode = FindReadHandlerNode(aReadHandler); // Nothing to remove if the handler is not found in the list VerifyOrReturn(nullptr != removeNode); - mReadHandlerList.Remove(removeNode); mNodesPool.ReleaseObject(removeNode); - if (mReadHandlerList.Empty()) + if (!mNodesPool.Allocated()) { // Only cancel the timer if there are no more handlers registered CancelReport(); @@ -49,6 +48,11 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::ScheduleReport(Timeout timeout, Read { // Cancel Report if it is currently scheduled mTimerDelegate->CancelTimer(this); + if (timeout == Milliseconds32(0)) + { + ReportTimerCallback(); + return CHIP_NO_ERROR; + } ReturnErrorOnFailure(mTimerDelegate->StartTimer(this, timeout)); mTestNextReportTimestamp = mTimerDelegate->GetCurrentMonotonicTimestamp() + timeout; @@ -61,8 +65,7 @@ void SynchronizedReportSchedulerImpl::CancelReport() mTimerDelegate->CancelTimer(this); } -/// @brief Checks if the timer is active for the given ReadHandler. Since all read handlers are scheduled on the same timer, we -/// check if the node is in the list and if the timer is active for the ReportScheduler +/// @brief Checks if the timer is active for the ReportScheduler bool SynchronizedReportSchedulerImpl::IsReportScheduled() { return mTimerDelegate->IsTimerActive(this); @@ -72,17 +75,18 @@ bool SynchronizedReportSchedulerImpl::IsReportScheduled() /// @return NO_ERROR if the smallest maximum interval was found, error otherwise, INVALID LIST LENGTH if the list is empty CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMaxInterval() { - VerifyOrReturnError(!mReadHandlerList.Empty(), CHIP_ERROR_INVALID_LIST_LENGTH); + VerifyOrReturnError(mNodesPool.Allocated(), CHIP_ERROR_INVALID_LIST_LENGTH); System::Clock::Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); System::Clock::Timestamp earliest = now + Seconds16::max(); - for (auto & iter : mReadHandlerList) - { - if (iter.GetMaxTimestamp() < earliest && iter.GetMaxTimestamp() > now) + mNodesPool.ForEachActiveObject([&earliest, now](ReadHandlerNode * node) { + if (node->GetMaxTimestamp() < earliest && node->GetMaxTimestamp() > now) { - earliest = iter.GetMaxTimestamp(); + earliest = node->GetMaxTimestamp(); } - } + + return Loop::Continue; + }); mNextMaxTimestamp = earliest; @@ -95,20 +99,19 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMaxInterval() /// @return NO_ERROR if the highest minimum timestamp was found, error otherwise, INVALID LIST LENGTH if the list is empty CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMinInterval() { - VerifyOrReturnError(!mReadHandlerList.Empty(), CHIP_ERROR_INVALID_LIST_LENGTH); + VerifyOrReturnError(mNodesPool.Allocated(), CHIP_ERROR_INVALID_LIST_LENGTH); System::Clock::Timestamp latest = mTimerDelegate->GetCurrentMonotonicTimestamp(); - for (auto & iter : mReadHandlerList) - { - if (iter.GetMinTimestamp() > latest && IsReadHandlerReportable(iter.GetReadHandler())) + mNodesPool.ForEachActiveObject([&latest, this](ReadHandlerNode * node) { + if (node->GetMinTimestamp() > latest && this->IsReadHandlerReportable(node->GetReadHandler()) && + node->GetMinTimestamp() <= this->mNextMaxTimestamp) { // We do not want the new min to be set above the max for any handler - if (iter.GetMinTimestamp() <= mNextMaxTimestamp) - { - latest = iter.GetMinTimestamp(); - } + latest = node->GetMinTimestamp(); } - } + + return Loop::Continue; + }); mNextMinTimestamp = latest; @@ -117,7 +120,7 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMinInterval() CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aNode) { - VerifyOrReturnError(mReadHandlerList.Contains(aNode), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nullptr != FindReadHandlerNode(aNode->GetReadHandler()), CHIP_ERROR_INVALID_ARGUMENT); ReturnErrorOnFailure(FindNextMaxInterval()); ReturnErrorOnFailure(FindNextMinInterval()); bool reportableNow = false; @@ -125,22 +128,23 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); - for (auto & iter : mReadHandlerList) - { - if (!iter.IsEngineRunScheduled()) + mNodesPool.ForEachActiveObject([&reportableNow, &reportableAtMin, this](ReadHandlerNode * node) { + if (!node->IsEngineRunScheduled()) { - if (iter.IsReportableNow()) + if (node->IsReportableNow()) { reportableNow = true; - break; + return Loop::Break; } - if (IsReadHandlerReportable(iter.GetReadHandler()) && iter.GetMinTimestamp() <= mNextMaxTimestamp) + if (this->IsReadHandlerReportable(node->GetReadHandler()) && node->GetMinTimestamp() <= this->mNextMaxTimestamp) { reportableAtMin = true; } } - } + + return Loop::Continue; + }); // Find out if any handler is reportable now @@ -159,36 +163,36 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & } // Updates the synching time of each handler - for (auto & iter : mReadHandlerList) - { + mNodesPool.ForEachActiveObject([now, timeout](ReadHandlerNode * node) { // Prevent modifying the sync if the handler is currently reportable, sync's purpose is to allow handler to become // reportable earlier than their max interval - if (!iter.IsReportableNow()) + if (!node->IsReportableNow()) { - iter.SetSyncTimestamp(Milliseconds64(now + timeout)); + node->SetSyncTimestamp(Milliseconds64(now + timeout)); } - } + + return Loop::Continue; + }); return CHIP_NO_ERROR; } /// @brief Callback called when the report timer expires to schedule an engine run regardless of the state of the ReadHandlers, as /// the engine already verifies that read handlers are reportable before sending a report -void SynchronizedReportSchedulerImpl::ReportTimerCallback() +void SynchronizedReportSchedulerImpl::TimerFired() { - ReportSchedulerImpl::ReportTimerCallback(); + InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); - Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); - ChipLogProgress(DataManagement, "Engine run at time: %" PRIu64 " for Handlers:", now.count()); - for (auto & iter : mReadHandlerList) - { - if (iter.IsReportableNow()) + mNodesPool.ForEachActiveObject([](ReadHandlerNode * node) { + if (node->IsReportableNow()) { - iter.SetEngineRunScheduled(true); - ChipLogProgress(DataManagement, "Handler: %p with min: %" PRIu64 " and max: %" PRIu64 " and sync: %" PRIu64, (&iter), - iter.GetMinTimestamp().count(), iter.GetMaxTimestamp().count(), iter.GetSyncTimestamp().count()); + node->SetEngineRunScheduled(true); + ChipLogProgress(DataManagement, "Handler: %p with min: %" PRIu64 " and max: %" PRIu64 " and sync: %" PRIu64, (node), + node->GetMinTimestamp().count(), node->GetMaxTimestamp().count(), node->GetSyncTimestamp().count()); } - } + + return Loop::Continue; + }); } } // namespace reporting diff --git a/src/app/reporting/SynchronizedReportSchedulerImpl.h b/src/app/reporting/SynchronizedReportSchedulerImpl.h index 18ee69520e5651..9fd4a8ca0aa684 100644 --- a/src/app/reporting/SynchronizedReportSchedulerImpl.h +++ b/src/app/reporting/SynchronizedReportSchedulerImpl.h @@ -30,17 +30,17 @@ using Milliseconds64 = System::Clock::Milliseconds64; using ReadHandlerNode = ReportScheduler::ReadHandlerNode; using TimerDelegate = ReportScheduler::TimerDelegate; -class SynchronizedReportSchedulerImpl : public ReportSchedulerImpl +class SynchronizedReportSchedulerImpl : public ReportSchedulerImpl, public TimerContext { public: void OnReadHandlerDestroyed(ReadHandler * aReadHandler) override; SynchronizedReportSchedulerImpl(TimerDelegate * aTimerDelegate) : ReportSchedulerImpl(aTimerDelegate) {} - ~SynchronizedReportSchedulerImpl() {} + ~SynchronizedReportSchedulerImpl() override { UnregisterAllHandlers(); } bool IsReportScheduled(); - void ReportTimerCallback() override; + void TimerFired() override; protected: CHIP_ERROR ScheduleReport(System::Clock::Timeout timeout, ReadHandlerNode * node) override; diff --git a/src/app/reporting/tests/MockReportScheduler.cpp b/src/app/reporting/tests/MockReportScheduler.cpp new file mode 100644 index 00000000000000..e42cf22e973e00 --- /dev/null +++ b/src/app/reporting/tests/MockReportScheduler.cpp @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace chip { +namespace app { +namespace reporting { + +/// @brief Static instance of the default report scheduler and synchronized report scheduler meant for injection into IM engine in +/// tests + +static chip::app::DefaultTimerDelegate sTimerDelegate; +static ReportSchedulerImpl sTestDefaultReportScheduler(&sTimerDelegate); +static SynchronizedReportSchedulerImpl sTestReportScheduler(&sTimerDelegate); + +ReportSchedulerImpl * GetDefaultReportScheduler() +{ + return &sTestDefaultReportScheduler; +} + +SynchronizedReportSchedulerImpl * GetSynchronizedReportScheduler() +{ + return &sTestReportScheduler; +} + +} // namespace reporting +} // namespace app +} // namespace chip diff --git a/src/app/reporting/tests/MockReportScheduler.h b/src/app/reporting/tests/MockReportScheduler.h new file mode 100644 index 00000000000000..9b3b3474a4fd49 --- /dev/null +++ b/src/app/reporting/tests/MockReportScheduler.h @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace app { +namespace reporting { + +ReportSchedulerImpl * GetDefaultReportScheduler(); + +SynchronizedReportSchedulerImpl * GetSynchronizedReportScheduler(); + +} // namespace reporting +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 0dfa76bd6ad59b..96847ace574100 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -316,8 +316,8 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) &mCertificateValidityPolicy, mGroupsProvider); SuccessOrExit(err); - err = chip::app::InteractionModelEngine::GetInstance()->Init(&mExchangeMgr, &GetFabricTable(), &mCASESessionManager, - mSubscriptionResumptionStorage); + err = chip::app::InteractionModelEngine::GetInstance()->Init(&mExchangeMgr, &GetFabricTable(), &mReportScheduler, + &mCASESessionManager, mSubscriptionResumptionStorage); SuccessOrExit(err); // This code is necessary to restart listening to existing groups after a reboot diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 9c4a3ee499dca3..c41a9e3127e0d9 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -63,7 +63,13 @@ #if CONFIG_NETWORK_LAYER_BLE #include #endif +#include #include +#if CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED +#include +#else +#include +#endif #if CHIP_CONFIG_ENABLE_ICD_SERVER #include // nogncheck @@ -254,6 +260,7 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams static PersistentStorageOperationalKeystore sPersistentStorageOperationalKeystore; static Credentials::PersistentStorageOpCertStore sPersistentStorageOpCertStore; static Credentials::GroupDataProviderImpl sGroupDataProvider; + #if CHIP_CONFIG_ENABLE_SESSION_RESUMPTION static SimpleSessionResumptionStorage sSessionResumptionStorage; #endif @@ -333,6 +340,8 @@ class Server app::DefaultAttributePersistenceProvider & GetDefaultAttributePersister() { return mAttributePersister; } + app::reporting::ReportScheduler & GetReportScheduler() { return mReportScheduler; } + /** * This function causes the ShutDown event to be generated async on the * Matter event loop. Should be called before stopping the event loop. @@ -351,7 +360,7 @@ class Server static Server & GetInstance() { return sServer; } private: - Server() = default; + Server() : mTimerDelegate(), mReportScheduler(&mTimerDelegate) {} static Server sServer; @@ -585,6 +594,12 @@ class Server app::DefaultAttributePersistenceProvider mAttributePersister; GroupDataProviderListener mListener; ServerFabricDelegate mFabricDelegate; + app::DefaultTimerDelegate mTimerDelegate; +#if CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED + app::reporting::SynchronizedReportSchedulerImpl mReportScheduler; +#else + app::reporting::ReportSchedulerImpl mReportScheduler; +#endif Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; diff --git a/src/app/tests/AppTestContext.cpp b/src/app/tests/AppTestContext.cpp index 3a63c3e21c5952..abac16a531b090 100644 --- a/src/app/tests/AppTestContext.cpp +++ b/src/app/tests/AppTestContext.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -40,7 +41,8 @@ namespace Test { CHIP_ERROR AppContext::Init() { ReturnErrorOnFailure(Super::Init()); - ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable())); + ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable(), + app::reporting::GetDefaultReportScheduler())); Access::SetAccessControl(gPermissiveAccessControl); ReturnErrorOnFailure( diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index e5f39d95ab10dd..5378b199aa17f7 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -24,6 +24,7 @@ static_library("helpers") { output_dir = "${root_out_dir}/lib" sources = [ + "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "AppTestContext.cpp", "AppTestContext.h", "integration/RequiredPrivilegeStubs.cpp", diff --git a/src/app/tests/TestAclAttribute.cpp b/src/app/tests/TestAclAttribute.cpp index cd7d774ef3429a..1f0e0c13f17023 100644 --- a/src/app/tests/TestAclAttribute.cpp +++ b/src/app/tests/TestAclAttribute.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -170,7 +171,7 @@ void TestAclAttribute::TestACLDeniedAttribute(nlTestSuite * apSuite, void * apCo MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); { diff --git a/src/app/tests/TestAclEvent.cpp b/src/app/tests/TestAclEvent.cpp index be9b61affdb3ca..fcb477761aa342 100644 --- a/src/app/tests/TestAclEvent.cpp +++ b/src/app/tests/TestAclEvent.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -235,7 +236,7 @@ void TestAclEvent::TestReadRoundtripWithEventStatusIBInEventReport(nlTestSuite * GenerateEvents(apSuite, apContext); auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); // A custom AccessControl::Delegate has been installed that grants privilege to any cluster except the test cluster. diff --git a/src/app/tests/TestInteractionModelEngine.cpp b/src/app/tests/TestInteractionModelEngine.cpp index 6fc5b1da27dcbe..a9a21071d25475 100644 --- a/src/app/tests/TestInteractionModelEngine.cpp +++ b/src/app/tests/TestInteractionModelEngine.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -70,7 +71,8 @@ void TestInteractionModelEngine::TestAttributePathParamsPushRelease(nlTestSuite { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ObjectList * attributePathParamsList = nullptr; AttributePathParams attributePathParams1; @@ -107,7 +109,8 @@ void TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute(nlTestSuit { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ObjectList * attributePathParamsList = nullptr; AttributePathParams attributePathParams1; @@ -231,7 +234,8 @@ void TestInteractionModelEngine::TestSubscriptionResumptionTimer(nlTestSuite * a { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); uint32_t timeTillNextResubscriptionMs; diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 9ec3d59330c216..f886d6ead65bd8 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -45,7 +47,6 @@ #include #include #include - #include namespace { @@ -253,8 +254,13 @@ class NullReadHandlerCallback : public chip::app::ReadHandler::ManagementCallbac } // namespace +using ReportScheduler = chip::app::reporting::ReportScheduler; +using ReportSchedulerImpl = chip::app::reporting::ReportSchedulerImpl; +using ReadHandlerNode = chip::app::reporting::ReportScheduler::ReadHandlerNode; + namespace chip { namespace app { + CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, AttributeValueEncoder::AttributeEncodeState * apEncoderState) @@ -512,12 +518,13 @@ void TestReadInteraction::TestReadHandler(nlTestSuite * apSuite, void * apContex NullReadHandlerCallback nullCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); { Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false); - ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); GenerateReportData(apSuite, apContext, reportDatabuf, false /*aNeedInvalidReport*/, false /* aSuppressResponse*/); err = readHandler.SendReportData(std::move(reportDatabuf), false); @@ -655,12 +662,13 @@ void TestReadInteraction::TestReadHandlerInvalidAttributePath(nlTestSuite * apSu NullReadHandlerCallback nullCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); { Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false); - ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); GenerateReportData(apSuite, apContext, reportDatabuf, false /*aNeedInvalidReport*/, false /* aSuppressResponse*/); err = readHandler.SendReportData(std::move(reportDatabuf), false); @@ -817,7 +825,7 @@ void TestReadInteraction::TestReadRoundtrip(nlTestSuite * apSuite, void * apCont MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -899,7 +907,7 @@ void TestReadInteraction::TestReadRoundtripWithDataVersionFilter(nlTestSuite * a MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -955,7 +963,7 @@ void TestReadInteraction::TestReadRoundtripWithNoMatchPathDataVersionFilter(nlTe MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[2]; @@ -1015,7 +1023,7 @@ void TestReadInteraction::TestReadRoundtripWithMultiSamePathDifferentDataVersion MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1076,7 +1084,7 @@ void TestReadInteraction::TestReadRoundtripWithSameDifferentPathsDataVersionFilt MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1137,7 +1145,7 @@ void TestReadInteraction::TestReadWildcard(nlTestSuite * apSuite, void * apConte MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1187,7 +1195,7 @@ void TestReadInteraction::TestReadChunking(nlTestSuite * apSuite, void * apConte MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1239,7 +1247,7 @@ void TestReadInteraction::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void GenerateEvents(apSuite, apContext); auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[2]; @@ -1390,7 +1398,7 @@ void TestReadInteraction::TestReadInvalidAttributePathRoundtrip(nlTestSuite * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1431,13 +1439,14 @@ void TestReadInteraction::TestProcessSubscribeRequest(nlTestSuite * apSuite, voi SubscribeRequestMessage::Builder subscribeRequestBuilder; MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false); { - ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); writer.Init(std::move(subscribeRequestbuf)); err = subscribeRequestBuilder.Init(&writer); @@ -1492,8 +1501,9 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1587,7 +1597,7 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a dirtyPath5.mAttributeId = 4; // Test report with 2 different path - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mGotEventResponse = false; delegate.mNumAttributeResponse = 0; @@ -1599,12 +1609,15 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse == true); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test report with 2 different path, and 1 same path - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; err = engine->GetReportingEngine().SetDirty(dirtyPath1); @@ -1616,11 +1629,14 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test report with 3 different path, and one path is overlapped with another - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; err = engine->GetReportingEngine().SetDirty(dirtyPath1); @@ -1632,11 +1648,14 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test report with 3 different path, all are not overlapped, one path is not interested for current subscription - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; err = engine->GetReportingEngine().SetDirty(dirtyPath1); @@ -1648,12 +1667,19 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test empty report - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); + // Manually trigger the callback that would schedule the next report as it would normally have been called if the time had + // elapsed as simulated above + reportScheduler->RunNodeCallbackForHandler(delegate.mpReadHandler); + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().IsRunScheduled()); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -1683,8 +1709,9 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite MockInteractionModelApp delegate; MockInteractionModelApp nonUrgentDelegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mGotEventResponse); @@ -1740,13 +1767,12 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite GenerateEvents(apSuite, apContext); - NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + NL_TEST_ASSERT(apSuite, reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) > startTime); NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; delegate.mGotReport = false; - NL_TEST_ASSERT(apSuite, - nonUrgentDelegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + NL_TEST_ASSERT(apSuite, reportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler) > startTime); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); nonUrgentDelegate.mGotEventResponse = false; nonUrgentDelegate.mGotReport = false; @@ -1780,16 +1806,19 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mGotEventResponse); - // Since we just sent a report for our urgent subscription, we should have our min interval timer - // running again. - NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + // Since we just sent a report for our urgent subscription, the min interval of the urgent subcription should have been + // updated + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) > + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; - // For our non-urgent subscription, we did not send anything, so we - // should not have a min interval timer running there. + // For our non-urgent subscription, we did not send anything, so the min interval should of the non urgent subcription + // should be in the past NL_TEST_ASSERT(apSuite, - !nonUrgentDelegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + reportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler) < + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); // Wait for the min interval timer to fire. @@ -1807,18 +1836,24 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mGotEventResponse); - // min-interval timer should have fired, and our handler should still - // not be dirty or even reportable. - NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + // The min-interval should have elapsed for urgen subscription, and our handler should still + // not be dirty or reportable. + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) < + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsReportable()); // And the non-urgent one should not have changed state either, since // it's waiting for the max-interval. NL_TEST_ASSERT(apSuite, - !nonUrgentDelegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + reportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler) < + System::SystemClock().GetMonotonicTimestamp()); + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMaxTimestampForHandler(nonUrgentDelegate.mpReadHandler) > + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportable()); // There should be no reporting run scheduled. This is very important; // otherwise we can get a false-positive pass below because the run was @@ -1830,11 +1865,12 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite // Urgent read handler should now be dirty, and reportable. NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsReportable()); + NL_TEST_ASSERT(apSuite, reportScheduler->IsReadHandlerReportable(delegate.mpReadHandler)); // Non-urgent read handler should not be reportable. NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportable()); // Still no reporting should have happened. NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1870,8 +1906,9 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap GenerateEvents(apSuite, apContext); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1917,7 +1954,7 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap // Set a concrete path dirty { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -1931,6 +1968,9 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); // We subscribed wildcard path twice, so we will receive two reports here. NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); @@ -1938,7 +1978,7 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap // Set a endpoint dirty { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; delegate.mNumArrayItems = 0; @@ -1960,6 +2000,9 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap ctx.DrainAndServiceIO(); } while (last != delegate.mNumAttributeResponse); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); // Mock endpoint3 has 13 attributes in total, and we subscribed twice. // And attribute 3/2/4 is a list with 6 elements and list chunking @@ -1991,8 +2034,9 @@ void TestReadInteraction::TestSubscribePartialOverlap(nlTestSuite * apSuite, voi NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2030,7 +2074,7 @@ void TestReadInteraction::TestSubscribePartialOverlap(nlTestSuite * apSuite, voi // Set a partial overlapped path dirty { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -2043,6 +2087,9 @@ void TestReadInteraction::TestSubscribePartialOverlap(nlTestSuite * apSuite, voi ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); NL_TEST_ASSERT(apSuite, delegate.mReceivedAttributePaths[0].mEndpointId == Test::kMockEndpoint2); @@ -2067,8 +2114,9 @@ void TestReadInteraction::TestSubscribeSetDirtyFullyOverlap(nlTestSuite * apSuit NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2107,7 +2155,7 @@ void TestReadInteraction::TestSubscribeSetDirtyFullyOverlap(nlTestSuite * apSuit // Set a full overlapped path dirty and expect to receive one E2C3A1 { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -2117,6 +2165,9 @@ void TestReadInteraction::TestSubscribeSetDirtyFullyOverlap(nlTestSuite * apSuit ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); NL_TEST_ASSERT(apSuite, delegate.mReceivedAttributePaths[0].mEndpointId == Test::kMockEndpoint2); @@ -2141,7 +2192,9 @@ void TestReadInteraction::TestSubscribeEarlyShutdown(nlTestSuite * apSuite, void // Initialize Interaction Model Engine NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); - NL_TEST_ASSERT(apSuite, engine.Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()) == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, + engine.Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()) == + CHIP_NO_ERROR); // Subscribe to the attribute AttributePathParams attributePathParams; @@ -2194,8 +2247,9 @@ void TestReadInteraction::TestSubscribeInvalidAttributePathRoundtrip(nlTestSuite GenerateEvents(apSuite, apContext); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2228,8 +2282,11 @@ void TestReadInteraction::TestSubscribeInvalidAttributePathRoundtrip(nlTestSuite NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); + // Manually trigger the callback that would schedule the next report as it would normally have been called if the time had + // elapsed as simulated above + reportScheduler->RunNodeCallbackForHandler(delegate.mpReadHandler); NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().IsRunScheduled()); ctx.DrainAndServiceIO(); @@ -2293,7 +2350,7 @@ void TestReadInteraction::TestSubscribeInvalidInterval(nlTestSuite * apSuite, vo MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2337,8 +2394,9 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2404,7 +2462,7 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu dirtyPath2.mAttributeId = 2; // Test report with 2 different path - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -2415,11 +2473,14 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; ctx.ExpireSessionBobToAlice(); @@ -2459,7 +2520,7 @@ void TestReadInteraction::TestSubscribeRoundtripStatusReportTimeout(nlTestSuite MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2537,7 +2598,7 @@ void TestReadInteraction::TestReadChunkingStatusReportTimeout(nlTestSuite * apSu MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2591,7 +2652,7 @@ void TestReadInteraction::TestReadReportFailure(nlTestSuite * apSuite, void * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2639,7 +2700,7 @@ void TestReadInteraction::TestSubscribeRoundtripChunkStatusReportTimeout(nlTestS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2709,8 +2770,9 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout(nlT NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2766,8 +2828,8 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout(nlT dirtyPath1.mEndpointId = Test::kMockEndpoint3; dirtyPath1.mAttributeId = Test::MockAttributeId(4); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); err = engine->GetReportingEngine().SetDirty(dirtyPath1); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); delegate.mGotReport = false; @@ -2811,8 +2873,9 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSui NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2868,8 +2931,8 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSui dirtyPath1.mEndpointId = Test::kMockEndpoint3; dirtyPath1.mAttributeId = Test::MockAttributeId(4); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); err = engine->GetReportingEngine().SetDirty(dirtyPath1); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); delegate.mGotReport = false; @@ -2913,7 +2976,7 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReport(nlTestSuite * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -3067,7 +3130,7 @@ void TestReadInteraction::TestReadClientReceiveInvalidMessage(nlTestSuite * apSu MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3153,7 +3216,7 @@ void TestReadInteraction::TestSubscribeClientReceiveInvalidStatusResponse(nlTest MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3246,7 +3309,7 @@ void TestReadInteraction::TestSubscribeClientReceiveWellFormedStatusResponse(nlT MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3338,7 +3401,7 @@ void TestReadInteraction::TestSubscribeClientReceiveInvalidReportMessage(nlTestS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3430,7 +3493,7 @@ void TestReadInteraction::TestSubscribeClientReceiveUnsolicitedInvalidReportMess MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3502,7 +3565,7 @@ void TestReadInteraction::TestSubscribeClientReceiveInvalidSubscribeResponseMess MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3596,7 +3659,7 @@ void TestReadInteraction::TestSubscribeClientReceiveUnsolicitedReportMessageWith MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3675,7 +3738,7 @@ void TestReadInteraction::TestReadChunkingInvalidSubscriptionId(nlTestSuite * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -3766,7 +3829,7 @@ void TestReadInteraction::TestReadHandlerMalformedSubscribeRequest(nlTestSuite * MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3811,7 +3874,7 @@ void TestReadInteraction::TestReadHandlerMalformedReadRequest1(nlTestSuite * apS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3856,7 +3919,7 @@ void TestReadInteraction::TestReadHandlerMalformedReadRequest2(nlTestSuite * apS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3903,7 +3966,7 @@ void TestReadInteraction::TestSubscribeSendUnknownMessage(nlTestSuite * apSuite, MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[1]; @@ -3979,7 +4042,7 @@ void TestReadInteraction::TestSubscribeSendInvalidStatusReport(nlTestSuite * apS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[1]; @@ -4054,7 +4117,7 @@ void TestReadInteraction::TestReadHandlerInvalidSubscribeRequest(nlTestSuite * a MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -4100,7 +4163,7 @@ void TestReadInteraction::TestSubscribeInvalidateFabric(nlTestSuite * apSuite, v MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -4159,7 +4222,7 @@ void TestReadInteraction::TestShutdownSubscription(nlTestSuite * apSuite, void * MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -4211,8 +4274,9 @@ void TestReadInteraction::TestSubscriptionReportWithDefunctSession(nlTestSuite * NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); AttributePathParams subscribePath(Test::kMockEndpoint3, Test::MockClusterId(2), Test::MockAttributeId(1)); @@ -4249,13 +4313,14 @@ void TestReadInteraction::TestSubscriptionReportWithDefunctSession(nlTestSuite * NL_TEST_ASSERT(apSuite, SessionHandle(*readHandler->GetSession()) == ctx.GetSessionAliceToBob()); // Test that we send reports as needed. - readHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(readHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; engine->GetReportingEngine().SetDirty(subscribePath); ctx.DrainAndServiceIO(); - + NL_TEST_ASSERT(apSuite, + !reportScheduler->CheckFlagsForHandler(readHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe) == 1); @@ -4265,7 +4330,7 @@ void TestReadInteraction::TestSubscriptionReportWithDefunctSession(nlTestSuite * // Test that if the session is defunct we don't send reports and clean // up properly. readHandler->GetSession()->MarkAsDefunct(); - readHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(readHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; engine->GetReportingEngine().SetDirty(subscribePath); diff --git a/src/app/tests/TestReportScheduler.cpp b/src/app/tests/TestReportScheduler.cpp index c789865bcc50b2..9d19f85636e15e 100644 --- a/src/app/tests/TestReportScheduler.cpp +++ b/src/app/tests/TestReportScheduler.cpp @@ -138,16 +138,16 @@ class TestTimerDelegate : public ReportScheduler::TimerDelegate // Normaly we would call the callback here, thus scheduling an engine run, but we don't need it for this test as we simulate // all the callbacks related to report emissions. The actual callback should look like this: // - // ReadHandlerNode * node = static_cast(aAppState); - // node->RunCallback(); + TimerContext * context = static_cast(aAppState); + context->TimerFired(); ChipLogProgress(DataManagement, "Simluating engine run for Handler: %p", aAppState); } - virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) override + virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) override { return insertPair(static_cast(context), aTimeout + mMockSystemTimestamp); } - virtual void CancelTimer(void * context) override { removePair(static_cast(context)); } - virtual bool IsTimerActive(void * context) override + virtual void CancelTimer(TimerContext * context) override { removePair(static_cast(context)); } + virtual bool IsTimerActive(TimerContext * context) override { size_t position; NodeTimeoutPair * pair = FindPair(static_cast(context), position); @@ -184,30 +184,29 @@ class TestTimerSynchronizedDelegate : public ReportScheduler::TimerDelegate public: static void TimerCallbackInterface(System::Layer * aLayer, void * aAppState) { - SynchronizedReportSchedulerImpl * scheduler = static_cast(aAppState); - scheduler->ReportTimerCallback(); + TimerContext * context = static_cast(aAppState); + context->TimerFired(); } - virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) override + virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) override { - SynchronizedReportSchedulerImpl * scheduler = static_cast(context); - if (nullptr == scheduler) + if (nullptr == context) { return CHIP_ERROR_INCORRECT_STATE; } - mSyncScheduler = scheduler; - mTimerTimeout = mMockSystemTimestamp + aTimeout; + mTimerContext = context; + mTimerTimeout = mMockSystemTimestamp + aTimeout; return CHIP_NO_ERROR; } - virtual void CancelTimer(void * context) override + virtual void CancelTimer(TimerContext * context) override { - VerifyOrReturn(nullptr != mSyncScheduler); - mSyncScheduler = nullptr; - mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); + VerifyOrReturn(nullptr != mTimerContext); + mTimerContext = nullptr; + mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); } - virtual bool IsTimerActive(void * context) override + virtual bool IsTimerActive(TimerContext * context) override { - return (nullptr != mSyncScheduler) && (mTimerTimeout > mMockSystemTimestamp); + return (nullptr != mTimerContext) && (mTimerTimeout > mMockSystemTimestamp); } virtual System::Clock::Timestamp GetCurrentMonotonicTimestamp() override { return mMockSystemTimestamp; } @@ -223,7 +222,7 @@ class TestTimerSynchronizedDelegate : public ReportScheduler::TimerDelegate mMockSystemTimestamp++; if (mMockSystemTimestamp == mTimerTimeout) { - TimerCallbackInterface(nullptr, mSyncScheduler); + TimerCallbackInterface(nullptr, mTimerContext); } } @@ -231,14 +230,14 @@ class TestTimerSynchronizedDelegate : public ReportScheduler::TimerDelegate { if (mMockSystemTimestamp == mTimerTimeout) { - TimerCallbackInterface(nullptr, mSyncScheduler); + TimerCallbackInterface(nullptr, mTimerContext); } } } - SynchronizedReportSchedulerImpl * mSyncScheduler = nullptr; - System::Clock::Timeout mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); - System::Clock::Timestamp mMockSystemTimestamp = System::Clock::Milliseconds64(0); + TimerContext * mTimerContext = nullptr; + System::Clock::Timeout mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); + System::Clock::Timestamp mMockSystemTimestamp = System::Clock::Milliseconds64(0); }; TestTimerDelegate sTestTimerDelegate; @@ -250,6 +249,25 @@ SynchronizedReportSchedulerImpl syncScheduler(&sTestTimerSynchronizedDelegate); class TestReportScheduler { public: + static ReadHandler * GetReadHandlerFromPool(ReportScheduler * scheduler, uint32_t target) + { + uint32_t i = 0; + ReadHandler * ret = nullptr; + + scheduler->mNodesPool.ForEachActiveObject([target, &i, &ret](ReadHandlerNode * node) { + if (i == target) + { + ret = node->GetReadHandler(); + return Loop::Break; + } + + i++; + return Loop::Continue; + }); + + return ret; + } + static void TestReadHandlerList(nlTestSuite * aSuite, void * aContext) { TestContext & ctx = *static_cast(aContext); @@ -266,11 +284,9 @@ class TestReportScheduler for (size_t i = 0; i < kNumMaxReadHandlers; i++) { ReadHandler * readHandler = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); NL_TEST_ASSERT(aSuite, nullptr != readHandler); VerifyOrReturn(nullptr != readHandler); - // Register ReadHandler using callback method - sScheduler.OnReadHandlerCreated(readHandler); NL_TEST_ASSERT(aSuite, nullptr != sScheduler.FindReadHandlerNode(readHandler)); } @@ -279,26 +295,26 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1); // Test unregister first ReadHandler - ReadHandler * firstReadHandler = sScheduler.mReadHandlerList.begin()->GetReadHandler(); + uint32_t target = 0; + ReadHandler * firstReadHandler = GetReadHandlerFromPool(&sScheduler, target); + + NL_TEST_ASSERT(aSuite, nullptr != firstReadHandler); sScheduler.OnReadHandlerDestroyed(firstReadHandler); NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 1); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(firstReadHandler)); // Test unregister middle ReadHandler - auto iter = sScheduler.mReadHandlerList.begin(); - for (size_t i = 0; i < static_cast(kNumMaxReadHandlers / 2); i++) - { - iter++; - } - ReadHandler * middleReadHandler = iter->GetReadHandler(); + target = static_cast(sScheduler.GetNumReadHandlers() / 2); + ReadHandler * middleReadHandler = GetReadHandlerFromPool(&sScheduler, target); + + NL_TEST_ASSERT(aSuite, nullptr != middleReadHandler); sScheduler.OnReadHandlerDestroyed(middleReadHandler); NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 2); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(middleReadHandler)); // Test unregister last ReadHandler - iter = sScheduler.mReadHandlerList.end(); - iter--; - ReadHandler * lastReadHandler = iter->GetReadHandler(); + target = static_cast(sScheduler.GetNumReadHandlers() - 1); + ReadHandler * lastReadHandler = GetReadHandlerFromPool(&sScheduler, target); sScheduler.OnReadHandlerDestroyed(lastReadHandler); NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 3); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(lastReadHandler)); @@ -330,32 +346,34 @@ class TestReportScheduler sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0)); // Dirty read handler, will be triggered at min interval + // Test OnReadHandler created ReadHandler * readHandler1 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMaxReportingInterval(2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(1)); - // Do those manually to avoid scheduling an engine run - readHandler1->mState = ReadHandler::HandlerState::GeneratingReports; - sScheduler.OnReadHandlerCreated(readHandler1); + ReadHandlerNode * node = sScheduler.FindReadHandlerNode(readHandler1); + node->SetIntervalTimeStamps(readHandler1); + readHandler1->MoveToState(ReadHandler::HandlerState::GeneratingReports); readHandler1->ForceDirtyState(); // Clean read handler, will be triggered at max interval ReadHandler * readHandler2 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMinReportingIntervalForTests(0)); - // Do those manually to avoid scheduling an engine run - readHandler2->mState = ReadHandler::HandlerState::GeneratingReports; - sScheduler.OnReadHandlerCreated(readHandler2); + node = sScheduler.FindReadHandlerNode(readHandler2); + node->SetIntervalTimeStamps(readHandler2); + readHandler2->MoveToState(ReadHandler::HandlerState::GeneratingReports); // Clean read handler, will be triggered at max interval, but will be cancelled before ReadHandler * readHandler3 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMinReportingIntervalForTests(0)); - // Do those manually to avoid scheduling an engine run - readHandler3->mState = ReadHandler::HandlerState::GeneratingReports; - sScheduler.OnReadHandlerCreated(readHandler3); + node = sScheduler.FindReadHandlerNode(readHandler3); + node->SetIntervalTimeStamps(readHandler3); + readHandler3->MoveToState(ReadHandler::HandlerState::GeneratingReports); // Confirms that none of the ReadHandlers are currently reportable NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler1)); @@ -384,6 +402,10 @@ class TestReportScheduler // and it is in generating report state NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler3)); + // Clear dirty flag on readHandler1 and confirm it is still reportable by time + readHandler1->ClearForceDirtyFlag(); + NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler1)); + sScheduler.UnregisterAllHandlers(); readHandlerPool.ReleaseAll(); exchangeCtx->Close(); @@ -404,26 +426,31 @@ class TestReportScheduler sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0)); ReadHandler * readHandler = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMaxReportingInterval(2)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMinReportingIntervalForTests(1)); - // Do those manually to avoid scheduling an engine run - readHandler->mState = ReadHandler::HandlerState::GeneratingReports; - readHandler->SetObserver(&sScheduler); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); + // Test OnReadHandler created registered the ReadHandler in the scheduler + NL_TEST_ASSERT(aSuite, nullptr != sScheduler.FindReadHandlerNode(readHandler)); - // Test OnReadHandlerCreated - readHandler->mObserver->OnReadHandlerCreated(readHandler); // Should have registered the read handler in the scheduler and scheduled a report NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 1); - NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMaxReportingInterval(2)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMinReportingIntervalForTests(1)); ReadHandlerNode * node = sScheduler.FindReadHandlerNode(readHandler); + node->SetIntervalTimeStamps(readHandler); + + // Test OnReportingIntervalsChanged modified the intervals and re-scheduled a report + NL_TEST_ASSERT(aSuite, node->GetMinTimestamp().count() == 1000); + NL_TEST_ASSERT(aSuite, node->GetMaxTimestamp().count() == 2000); + + // Do those manually to avoid scheduling an engine run + readHandler->MoveToState(ReadHandler::HandlerState::GeneratingReports); + NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler)); + NL_TEST_ASSERT(aSuite, nullptr != node); VerifyOrReturn(nullptr != node); NL_TEST_ASSERT(aSuite, node->GetReadHandler() == readHandler); // Test OnBecameReportable readHandler->ForceDirtyState(); - readHandler->mObserver->OnBecameReportable(readHandler); // Should have changed the scheduled timeout to the handler's min interval, to check, we wait for the min interval to // expire // Simulate system clock increment @@ -457,7 +484,6 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 0); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(readHandler)); - sScheduler.OnReadHandlerDestroyed(readHandler); readHandlerPool.ReleaseAll(); exchangeCtx->Close(); NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); @@ -474,31 +500,28 @@ class TestReportScheduler // Read handler pool ObjectPool readHandlerPool; - // Initilaize the mock system time + // Initialize the mock system time sTestTimerSynchronizedDelegate.SetMockSystemTimestamp(System::Clock::Milliseconds64(0)); ReadHandler * readHandler1 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMaxReportingInterval(2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(0)); + ReadHandlerNode * node1 = syncScheduler.FindReadHandlerNode(readHandler1); + node1->SetIntervalTimeStamps(readHandler1); readHandler1->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler1->SetObserver(&syncScheduler); - readHandler1->mObserver->OnReadHandlerCreated(readHandler1); ReadHandler * readHandler2 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMinReportingIntervalForTests(1)); + ReadHandlerNode * node2 = syncScheduler.FindReadHandlerNode(readHandler2); + node2->SetIntervalTimeStamps(readHandler2); readHandler2->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler2->SetObserver(&syncScheduler); - readHandler2->mObserver->OnReadHandlerCreated(readHandler2); // Confirm all handler are currently registered in the scheduler NL_TEST_ASSERT(aSuite, syncScheduler.GetNumReadHandlers() == 2); - ReadHandlerNode * node1 = syncScheduler.FindReadHandlerNode(readHandler1); - ReadHandlerNode * node2 = syncScheduler.FindReadHandlerNode(readHandler2); - // Confirm that a report emission is scheduled NL_TEST_ASSERT(aSuite, syncScheduler.IsReportScheduled()); @@ -532,24 +555,17 @@ class TestReportScheduler // Confirm behavior when a read handler becomes dirty readHandler2->ForceDirtyState(); + // OnBecomeReportable should have been called on ForceDirtyState because readHandler callbacks are now integrated NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler2)); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler1)); // Simulate wait enough for min timestamp of readHandler2 to be reached (1s) sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(1000)); NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler2)); - NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler1)); - readHandler2->mObserver->OnBecameReportable(readHandler2); + NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); // confirm report scheduled now - NL_TEST_ASSERT(aSuite, - syncScheduler.mTestNextReportTimestamp == sTestTimerSynchronizedDelegate.GetCurrentMonotonicTimestamp()); - // Increment the timestamp by 0 here to trigger an engine run as the mock timer is only calling the timeout callback if we - // increment the mock timestamp - sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(0)); - - // since the min interval on readHandler1 is 0, it should also be reportable now by sync mechanism - NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); + NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node2->GetMinTimestamp()); NL_TEST_ASSERT(aSuite, node1->GetSyncTimestamp() == node2->GetMinTimestamp()); // Confirm that the next report emission is scheduled on the min timestamp of readHandler2 as it is the highest reportable @@ -569,16 +585,13 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, node1->GetMaxTimestamp() > sTestTimerSynchronizedDelegate.GetCurrentMonotonicTimestamp()); NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node1->GetMaxTimestamp()); - // Simulate readHandler1 becoming dirty after less than 1 seconds + // Simulate readHandler1 becoming dirty after less than 1 seconds, since it is reportable now, this will Schedule an Engin + // run immediately sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(900)); readHandler1->ForceDirtyState(); NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler2)); - readHandler1->mObserver->OnBecameReportable(readHandler1); - // Validate next report scheduled on the min timestamp of readHandler1 (readHandler 2 is not currently reportable) - NL_TEST_ASSERT(aSuite, - syncScheduler.mTestNextReportTimestamp == sTestTimerSynchronizedDelegate.GetCurrentMonotonicTimestamp()); // Simulate a report emission for readHandler1 readHandler1->ClearForceDirtyFlag(); readHandler1->mObserver->OnSubscriptionAction(readHandler1); @@ -604,16 +617,15 @@ class TestReportScheduler sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(1000)); ReadHandler * readHandler3 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMinReportingIntervalForTests(2)); + ReadHandlerNode * node3 = syncScheduler.FindReadHandlerNode(readHandler3); + node3->SetIntervalTimeStamps(readHandler3); readHandler3->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler3->SetObserver(&syncScheduler); - readHandler3->mObserver->OnReadHandlerCreated(readHandler3); // Confirm all handler are currently registered in the scheduler NL_TEST_ASSERT(aSuite, syncScheduler.GetNumReadHandlers() == 3); - ReadHandlerNode * node3 = syncScheduler.FindReadHandlerNode(readHandler3); // Since the min interval on readHandler3 is 2, it should be above the current max timestamp, therefore the next report // should still happen on the max timestamp of readHandler1 and the sync should be done on future reports @@ -662,16 +674,15 @@ class TestReportScheduler // Now simulate a new readHandler being added with a max forcing a conflict ReadHandler * readHandler4 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler4->SetMaxReportingInterval(1)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler4->SetMinReportingIntervalForTests(0)); + ReadHandlerNode * node4 = syncScheduler.FindReadHandlerNode(readHandler4); + node4->SetIntervalTimeStamps(readHandler4); readHandler4->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler4->SetObserver(&syncScheduler); - readHandler4->mObserver->OnReadHandlerCreated(readHandler4); // Confirm all handler are currently registered in the scheduler NL_TEST_ASSERT(aSuite, syncScheduler.GetNumReadHandlers() == 4); - ReadHandlerNode * node4 = syncScheduler.FindReadHandlerNode(readHandler4); // Confirm next report is scheduled on the max timestamp of readHandler4 and other handlers 1 and 2 are synced NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node4->GetMaxTimestamp()); @@ -752,8 +763,8 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(0)); readHandler1->MoveToState(ReadHandler::HandlerState::GeneratingReports); syncScheduler.OnReadHandlerCreated(readHandler1); + // Forcing the dirty flag to make the scheduler call Engine::ScheduleRun() immediately readHandler1->ForceDirtyState(); - syncScheduler.OnBecameReportable(readHandler1); NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); readHandler2->MoveToState(ReadHandler::HandlerState::Idle); @@ -762,14 +773,11 @@ class TestReportScheduler readHandler2->MoveToState(ReadHandler::HandlerState::GeneratingReports); syncScheduler.OnReadHandlerCreated(readHandler2); readHandler2->ForceDirtyState(); - syncScheduler.OnBecameReportable(readHandler2); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler2)); node1 = syncScheduler.FindReadHandlerNode(readHandler1); node2 = syncScheduler.FindReadHandlerNode(readHandler2); - // Verify report is scheduled immediately as readHandler1 is dirty and its min == 0 - NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node1->GetMinTimestamp()); readHandler1->ClearForceDirtyFlag(); // report got emited so clear dirty flag syncScheduler.OnSubscriptionAction(readHandler1); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler1)); diff --git a/src/app/tests/TestReportingEngine.cpp b/src/app/tests/TestReportingEngine.cpp index 6921ea66d711b0..ddf793ec8880ae 100644 --- a/src/app/tests/TestReportingEngine.cpp +++ b/src/app/tests/TestReportingEngine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -130,7 +131,8 @@ void TestReportingEngine::TestBuildAndSendSingleReportData(nlTestSuite * apSuite ReadRequestMessage::Builder readRequestBuilder; DummyDelegate dummy; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); TestExchangeDelegate delegate; Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(&delegate); @@ -156,7 +158,8 @@ void TestReportingEngine::TestBuildAndSendSingleReportData(nlTestSuite * apSuite NL_TEST_ASSERT(apSuite, readRequestBuilder.GetError() == CHIP_NO_ERROR); err = writer.Finalize(&readRequestbuf); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - app::ReadHandler readHandler(dummy, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + app::ReadHandler readHandler(dummy, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); readHandler.OnInitialRequest(std::move(readRequestbuf)); err = InteractionModelEngine::GetInstance()->GetReportingEngine().BuildAndSendSingleReportData(&readHandler); @@ -169,7 +172,8 @@ void TestReportingEngine::TestMergeOverlappedAttributePath(nlTestSuite * apSuite { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); AttributePathParams * clusterInfo = InteractionModelEngine::GetInstance()->GetReportingEngine().mGlobalDirtySet.CreateObject(); @@ -240,7 +244,8 @@ void TestReportingEngine::TestMergeAttributePathWhenDirtySetPoolExhausted(nlTest { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); InteractionModelEngine::GetInstance()->GetReportingEngine().mGlobalDirtySet.ReleaseAll(); diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp index 95cb83eaf21d65..a8f5d27979c152 100644 --- a/src/app/tests/TestWriteInteraction.cpp +++ b/src/app/tests/TestWriteInteraction.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -366,7 +367,7 @@ void TestWriteInteraction::TestWriteRoundtripWithClusterObjects(nlTestSuite * ap TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -434,7 +435,7 @@ void TestWriteInteraction::TestWriteRoundtripWithClusterObjectsVersionMatch(nlTe TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -483,7 +484,7 @@ void TestWriteInteraction::TestWriteRoundtripWithClusterObjectsVersionMismatch(n TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -534,7 +535,7 @@ void TestWriteInteraction::TestWriteRoundtrip(nlTestSuite * apSuite, void * apCo TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -575,7 +576,7 @@ void TestWriteInteraction::TestWriteHandlerReceiveInvalidMessage(nlTestSuite * a TestWriteClientCallback writeCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional::Missing(), @@ -643,7 +644,7 @@ void TestWriteInteraction::TestWriteHandlerInvalidateFabric(nlTestSuite * apSuit TestWriteClientCallback writeCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional::Missing(), @@ -725,7 +726,7 @@ void TestWriteInteraction::TestWriteInvalidMessage1(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -796,7 +797,7 @@ void TestWriteInteraction::TestWriteInvalidMessage2(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -866,7 +867,7 @@ void TestWriteInteraction::TestWriteInvalidMessage3(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -937,7 +938,7 @@ void TestWriteInteraction::TestWriteInvalidMessage4(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); diff --git a/src/app/tests/integration/BUILD.gn b/src/app/tests/integration/BUILD.gn index 05e06d897ca690..0381691b43ffbd 100644 --- a/src/app/tests/integration/BUILD.gn +++ b/src/app/tests/integration/BUILD.gn @@ -21,6 +21,7 @@ assert(chip_build_tools) executable("chip-im-initiator") { sources = [ + "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "RequiredPrivilegeStubs.cpp", "chip_im_initiator.cpp", "common.cpp", @@ -41,6 +42,7 @@ executable("chip-im-initiator") { executable("chip-im-responder") { sources = [ + "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "MockEvents.cpp", "RequiredPrivilegeStubs.cpp", "chip_im_responder.cpp", diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index 24ed65a26869f9..1353e211c0ac36 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -731,7 +732,8 @@ int main(int argc, char * argv[]) err = gMessageCounterManager.Init(&gExchangeManager); SuccessOrExit(err); - err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable); + err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable, + chip::app::reporting::GetDefaultReportScheduler()); SuccessOrExit(err); // Start the CHIP connection to the CHIP im responder. diff --git a/src/app/tests/integration/chip_im_responder.cpp b/src/app/tests/integration/chip_im_responder.cpp index f7b003754da86a..b090425bebf093 100644 --- a/src/app/tests/integration/chip_im_responder.cpp +++ b/src/app/tests/integration/chip_im_responder.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -214,7 +215,8 @@ int main(int argc, char * argv[]) err = gMessageCounterManager.Init(&gExchangeManager); SuccessOrExit(err); - err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable); + err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable, + chip::app::reporting::GetDefaultReportScheduler()); SuccessOrExit(err); err = InitializeEventLogging(&gExchangeManager); diff --git a/src/controller/CHIPDeviceControllerFactory.cpp b/src/controller/CHIPDeviceControllerFactory.cpp index d2edbd840894dc..f39a3c7a65ed0e 100644 --- a/src/controller/CHIPDeviceControllerFactory.cpp +++ b/src/controller/CHIPDeviceControllerFactory.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include #include @@ -170,6 +172,8 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.exchangeMgr = chip::Platform::New(); stateParams.messageCounterManager = chip::Platform::New(); stateParams.groupDataProvider = params.groupDataProvider; + stateParams.timerDelegate = chip::Platform::New(); + stateParams.reportScheduler = chip::Platform::New(stateParams.timerDelegate); stateParams.sessionKeystore = params.sessionKeystore; // if no fabricTable was provided, create one and track it in stateParams for cleanup @@ -270,8 +274,8 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.caseSessionManager = Platform::New(); ReturnErrorOnFailure(stateParams.caseSessionManager->Init(stateParams.systemLayer, sessionManagerConfig)); - ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(stateParams.exchangeMgr, stateParams.fabricTable, - stateParams.caseSessionManager)); + ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init( + stateParams.exchangeMgr, stateParams.fabricTable, stateParams.reportScheduler, stateParams.caseSessionManager)); // store the system state mSystemState = chip::Platform::New(std::move(stateParams)); @@ -487,6 +491,18 @@ void DeviceControllerSystemState::Shutdown() mSessionMgr = nullptr; } + if (mReportScheduler != nullptr) + { + chip::Platform::Delete(mReportScheduler); + mReportScheduler = nullptr; + } + + if (mTimerDelegate != nullptr) + { + chip::Platform::Delete(mTimerDelegate); + mTimerDelegate = nullptr; + } + if (mTempFabricTable != nullptr) { mTempFabricTable->Shutdown(); diff --git a/src/controller/CHIPDeviceControllerFactory.h b/src/controller/CHIPDeviceControllerFactory.h index 11f84e6a42c58b..f807f39e1fd570 100644 --- a/src/controller/CHIPDeviceControllerFactory.h +++ b/src/controller/CHIPDeviceControllerFactory.h @@ -114,6 +114,7 @@ struct FactoryInitParams PersistentStorageDelegate * fabricIndependentStorage = nullptr; Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; Credentials::GroupDataProvider * groupDataProvider = nullptr; + app::reporting::ReportScheduler::TimerDelegate * timerDelegate = nullptr; Crypto::SessionKeystore * sessionKeystore = nullptr; Inet::EndPointManager * tcpEndPointManager = nullptr; Inet::EndPointManager * udpEndPointManager = nullptr; diff --git a/src/controller/CHIPDeviceControllerSystemState.h b/src/controller/CHIPDeviceControllerSystemState.h index e191411d0016fa..37f85b1cd90784 100644 --- a/src/controller/CHIPDeviceControllerSystemState.h +++ b/src/controller/CHIPDeviceControllerSystemState.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -98,6 +99,8 @@ struct DeviceControllerSystemStateParams SessionSetupPool * sessionSetupPool = nullptr; CASEClientPool * caseClientPool = nullptr; FabricTable::Delegate * fabricTableDelegate = nullptr; + chip::app::reporting::ReportScheduler::TimerDelegate * timerDelegate = nullptr; + chip::app::reporting::ReportScheduler * reportScheduler = nullptr; }; // A representation of the internal state maintained by the DeviceControllerFactory. @@ -127,9 +130,9 @@ class DeviceControllerSystemState mUnsolicitedStatusHandler(params.unsolicitedStatusHandler), mExchangeMgr(params.exchangeMgr), mMessageCounterManager(params.messageCounterManager), mFabrics(params.fabricTable), mCASEServer(params.caseServer), mCASESessionManager(params.caseSessionManager), mSessionSetupPool(params.sessionSetupPool), - mCASEClientPool(params.caseClientPool), mGroupDataProvider(params.groupDataProvider), - mSessionKeystore(params.sessionKeystore), mFabricTableDelegate(params.fabricTableDelegate), - mSessionResumptionStorage(std::move(params.sessionResumptionStorage)) + mCASEClientPool(params.caseClientPool), mGroupDataProvider(params.groupDataProvider), mTimerDelegate(params.timerDelegate), + mReportScheduler(params.reportScheduler), mSessionKeystore(params.sessionKeystore), + mFabricTableDelegate(params.fabricTableDelegate), mSessionResumptionStorage(std::move(params.sessionResumptionStorage)) { #if CONFIG_NETWORK_LAYER_BLE mBleLayer = params.bleLayer; @@ -168,7 +171,8 @@ class DeviceControllerSystemState return mSystemLayer != nullptr && mUDPEndPointManager != nullptr && mTransportMgr != nullptr && mSessionMgr != nullptr && mUnsolicitedStatusHandler != nullptr && mExchangeMgr != nullptr && mMessageCounterManager != nullptr && mFabrics != nullptr && mCASESessionManager != nullptr && mSessionSetupPool != nullptr && mCASEClientPool != nullptr && - mGroupDataProvider != nullptr && mSessionKeystore != nullptr; + mGroupDataProvider != nullptr && mReportScheduler != nullptr && mTimerDelegate != nullptr && + mSessionKeystore != nullptr; }; System::Layer * SystemLayer() const { return mSystemLayer; }; @@ -184,6 +188,8 @@ class DeviceControllerSystemState #endif CASESessionManager * CASESessionMgr() const { return mCASESessionManager; } Credentials::GroupDataProvider * GetGroupDataProvider() const { return mGroupDataProvider; } + chip::app::reporting::ReportScheduler * GetReportScheduler() const { return mReportScheduler; } + Crypto::SessionKeystore * GetSessionKeystore() const { return mSessionKeystore; } void SetTempFabricTable(FabricTable * tempFabricTable, bool enableServerInteractions) { @@ -211,6 +217,8 @@ class DeviceControllerSystemState SessionSetupPool * mSessionSetupPool = nullptr; CASEClientPool * mCASEClientPool = nullptr; Credentials::GroupDataProvider * mGroupDataProvider = nullptr; + app::reporting::ReportScheduler::TimerDelegate * mTimerDelegate = nullptr; + app::reporting::ReportScheduler * mReportScheduler = nullptr; Crypto::SessionKeystore * mSessionKeystore = nullptr; FabricTable::Delegate * mFabricTableDelegate = nullptr; Platform::UniquePtr mSessionResumptionStorage; diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index f83951ba3775c7..d1e6dc45cd8c7b 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1556,5 +1556,17 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS /** - * @} + * @def CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED + * + * @brief Controls whether the synchronized report scheduler is used. + * + * The use of the synchronous reports feature aims to reduce the number of times an ICD needs to wake up to emit reports to its + * various subscribers. + */ +#ifndef CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED +#define CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED 0 +#endif + +/** + * @} */ From 3efdd368f71ebad966f158f38384c897c53db5b4 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 24 Jul 2023 21:00:13 -0400 Subject: [PATCH 6/6] Update documentation with zap supporting amd64 (#28210) * ZAP supports arm64, doc this * Fix file name --- docs/code_generation.md | 4 ---- docs/guides/BUILDING.md | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/code_generation.md b/docs/code_generation.md index 318cfedd359954..e12bf40f100cff 100644 --- a/docs/code_generation.md +++ b/docs/code_generation.md @@ -40,10 +40,6 @@ ZAP is generally installed as a third-party tool via CIPD during the build environment bootstrap (see `scripts/setup/zap.json`), which makes `zap-cli` available in `$PATH` when running in a build environment. -**NOTE**: zap packages are currently NOT available for `arm64` (like when -compiling on Raspberry PI.). In these cases one should check out zap from source -and set `$ZAP_DEVELOPMENT_PATH` as described below. - When matter scripts need to invoke `zap-cli` (for code generation) or `zap` (to start the UI tool), they make use of the following environment variables to figure out where the zap tool is located (in order of precedence): diff --git a/docs/guides/BUILDING.md b/docs/guides/BUILDING.md index 26ff8d59485e13..359b35e3d6b2d4 100644 --- a/docs/guides/BUILDING.md +++ b/docs/guides/BUILDING.md @@ -169,8 +169,8 @@ The ZAP tool scripting uses the following detection, in order of importance: - Use this if you are developing ZAP locally and would like to run ZAP with your changes. -- `$ZAP_INSTALL_PATH` to point to where `zap-linux.zip` or `zap-mac.zip` was - unpacked. +- `$ZAP_INSTALL_PATH` to point to where `zap-linux-x64.zip`, + `zap-linux-arm64.zip` or `zap-mac-x64.zip` was unpacked. - This allows you to not need to place `zap` or `zap-cli` (or both) in `$PATH`.