diff --git a/.github/labeler.yml b/.github/labeler.yml index 525a93e0d37048..7e02fe6d368df3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -119,6 +119,15 @@ test driver: - src/test_driver/* - src/test_driver/**/* +# Cert tests touched: add current milestone delta-tracking label. +# TODO: Change after Aug 15, 2024 +matter-1.4-te2-script-change: + - changed-files: + - any-glob-to-any-file: + - src/python_testing/* + - src/python_testing/**/* + - src/app/tests/suites/certification/* + ############################################################ # Source Code ############################################################ diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index 6dff2dadc3d533..be2320f975beae 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -75,7 +75,12 @@ jobs: name: Tizen runs-on: ubuntu-latest - if: github.actor != 'restyled-io[bot]' + # NOTE: job temporarely disabled as it seems flaky. The flake does not result in usable + # logs so the current theory is that we run out of space. This is unusual as + # larger docker images succeed at bootstrap, however it needs more investigation + # to detect an exact/real root cause. + if: false + # if: github.actor != 'restyled-io[bot]' container: image: ghcr.io/project-chip/chip-build-tizen-qemu:54 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c19eccb9a30457..996b8a6178a57c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -511,6 +511,7 @@ jobs: scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_AccessChecker.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CC_2_2.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CC_10_1.py' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CADMIN_1_9.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CGEN_2_4.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_CNET_1_4.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_DA_1_2.py' @@ -548,6 +549,7 @@ jobs: scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_FAN_3_5.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_2_1.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_3_1.py' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_3_3.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDManagementCluster.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_IDM_1_2.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_IDM_1_4.py' diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 867da750b2a628..1c99735fc6d66d 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -6273,7 +6273,7 @@ cluster RelativeHumidityMeasurement = 1029 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/all-clusters-app/esp32/sdkconfig_m5stack.defaults b/examples/all-clusters-app/esp32/sdkconfig_m5stack.defaults index 8c30112f96d500..03691548ea91db 100644 --- a/examples/all-clusters-app/esp32/sdkconfig_m5stack.defaults +++ b/examples/all-clusters-app/esp32/sdkconfig_m5stack.defaults @@ -74,8 +74,8 @@ CONFIG_BUILD_CHIP_TESTS=y # Move functions from IRAM to flash CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y -# Reduce the event loggin buffer to reduce the DRAM usage -# TODO: remove this once the underlying issue is fixed +# Reduce the event logging buffer to reduce the DRAM usage +# TODO: [ESP32] Fix the DRAM overflow in esp32 apps #34717 CONFIG_EVENT_LOGGING_CRIT_BUFFER_SIZE=512 CONFIG_EVENT_LOGGING_INFO_BUFFER_SIZE=512 CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE=512 diff --git a/examples/all-clusters-app/esp32/sdkconfig_m5stack_rpc.defaults b/examples/all-clusters-app/esp32/sdkconfig_m5stack_rpc.defaults index f7aba926a4df67..0a426dffd0066e 100644 --- a/examples/all-clusters-app/esp32/sdkconfig_m5stack_rpc.defaults +++ b/examples/all-clusters-app/esp32/sdkconfig_m5stack_rpc.defaults @@ -86,8 +86,8 @@ CONFIG_BTDM_CTRL_BLE_MAX_CONN=1 CONFIG_BT_NIMBLE_ROLE_CENTRAL=n CONFIG_BT_NIMBLE_ROLE_OBSERVER=n -# Reduce the event loggin buffer to reduce the DRAM overflow -# TODO: remove this once the underlying issue is fixed +# Reduce the event logging buffer to reduce the DRAM overflow +# TODO: [ESP32] Fix the DRAM overflow in esp32 apps #34717 CONFIG_EVENT_LOGGING_CRIT_BUFFER_SIZE=512 CONFIG_EVENT_LOGGING_INFO_BUFFER_SIZE=512 CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE=512 diff --git a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter index 86f4335fe6c883..47093a5857a2ab 100644 --- a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter +++ b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter @@ -4710,7 +4710,7 @@ cluster RelativeHumidityMeasurement = 1029 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; @@ -6904,7 +6904,7 @@ endpoint 1 { ram attribute occupancySensorType; ram attribute occupancySensorTypeBitmap; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 4; + ram attribute clusterRevision default = 5; } server cluster WakeOnLan { @@ -7257,7 +7257,7 @@ endpoint 2 { ram attribute occupancySensorType; ram attribute occupancySensorTypeBitmap; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 4; + ram attribute clusterRevision default = 5; } } endpoint 65534 { diff --git a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.zap b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.zap index 560e95a7ec180a..2482ca381ee7af 100644 --- a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.zap +++ b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.zap @@ -8014,7 +8014,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -11936,7 +11936,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/chef/devices/noip_rootnode_dimmablelight_bCwGYSDpoe.matter b/examples/chef/devices/noip_rootnode_dimmablelight_bCwGYSDpoe.matter index 865ab9946a28d8..87b1f69b2601cc 100644 --- a/examples/chef/devices/noip_rootnode_dimmablelight_bCwGYSDpoe.matter +++ b/examples/chef/devices/noip_rootnode_dimmablelight_bCwGYSDpoe.matter @@ -1905,7 +1905,7 @@ cluster FixedLabel = 64 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/chef/devices/rootnode_dimmablelight_bCwGYSDpoe.matter b/examples/chef/devices/rootnode_dimmablelight_bCwGYSDpoe.matter index 0c139383678084..2e3fc88091d900 100644 --- a/examples/chef/devices/rootnode_dimmablelight_bCwGYSDpoe.matter +++ b/examples/chef/devices/rootnode_dimmablelight_bCwGYSDpoe.matter @@ -1800,7 +1800,7 @@ cluster FixedLabel = 64 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/chef/devices/rootnode_dimmablepluginunit_f8a9a0b9d4.matter b/examples/chef/devices/rootnode_dimmablepluginunit_f8a9a0b9d4.matter index 7c1c3356948605..031b6365db2517 100644 --- a/examples/chef/devices/rootnode_dimmablepluginunit_f8a9a0b9d4.matter +++ b/examples/chef/devices/rootnode_dimmablepluginunit_f8a9a0b9d4.matter @@ -1956,7 +1956,7 @@ provisional cluster ScenesManagement = 98 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.matter b/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.matter index 6dc939d3aa2592..3babdb7f9a57d0 100644 --- a/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.matter +++ b/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.matter @@ -1603,7 +1603,7 @@ cluster FixedLabel = 64 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; @@ -1919,7 +1919,7 @@ endpoint 1 { callback attribute acceptedCommandList; callback attribute attributeList; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 2; + ram attribute clusterRevision default = 5; } } diff --git a/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.zap b/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.zap index 5fa3595ec45ee5..94cd749abacb5c 100644 --- a/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.zap +++ b/examples/chef/devices/rootnode_occupancysensor_iHyVgifZuo.zap @@ -2993,7 +2993,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "2", + "defaultValue": "5", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3022,4 +3022,4 @@ "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} diff --git a/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter b/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter index c65191a9e0b508..094673005fd5d2 100644 --- a/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter +++ b/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter @@ -2101,7 +2101,7 @@ cluster RelativeHumidityMeasurement = 1029 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/chef/sample_app_util/test_files/sample_zap_file.zap b/examples/chef/sample_app_util/test_files/sample_zap_file.zap index 420eaa37ef5615..87dd68c26e1d74 100644 --- a/examples/chef/sample_app_util/test_files/sample_zap_file.zap +++ b/examples/chef/sample_app_util/test_files/sample_zap_file.zap @@ -5654,7 +5654,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5683,4 +5683,4 @@ "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} diff --git a/examples/chip-tool/commands/common/RemoteDataModelLogger.cpp b/examples/chip-tool/commands/common/RemoteDataModelLogger.cpp index 294673512882b3..3c6342752a0a61 100644 --- a/examples/chip-tool/commands/common/RemoteDataModelLogger.cpp +++ b/examples/chip-tool/commands/common/RemoteDataModelLogger.cpp @@ -160,8 +160,7 @@ CHIP_ERROR LogErrorAsJSON(const CHIP_ERROR & error) VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); Json::Value value; - chip::app::StatusIB status; - status.InitFromChipError(error); + chip::app::StatusIB status(error); return LogError(value, status); } diff --git a/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.matter b/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.matter index f721aa58e0fe15..532fc6fff1531c 100644 --- a/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.matter +++ b/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.matter @@ -1902,7 +1902,7 @@ cluster BooleanState = 69 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; @@ -2356,7 +2356,7 @@ endpoint 1 { ram attribute occupancySensorType; ram attribute occupancySensorTypeBitmap; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 4; + ram attribute clusterRevision default = 5; } } diff --git a/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.zap b/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.zap index 9d2dd1e830ff36..8815cb496a719c 100644 --- a/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.zap +++ b/examples/contact-sensor-app/contact-sensor-common/contact-sensor-app.zap @@ -4721,7 +4721,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4750,4 +4750,4 @@ "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} diff --git a/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp b/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp index 6334d42506d53f..0e51a1503ea3e1 100644 --- a/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp +++ b/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp @@ -160,8 +160,7 @@ CHIP_ERROR LogErrorAsJSON(const CHIP_ERROR & error) VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); Json::Value value; - chip::app::StatusIB status; - status.InitFromChipError(error); + chip::app::StatusIB status(error); return LogError(value, status); } diff --git a/examples/lighting-app/lighting-common/lighting-app.matter b/examples/lighting-app/lighting-common/lighting-app.matter index 13c3138eb22676..063304d69a222c 100644 --- a/examples/lighting-app/lighting-common/lighting-app.matter +++ b/examples/lighting-app/lighting-common/lighting-app.matter @@ -2614,7 +2614,7 @@ cluster ColorControl = 768 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; @@ -3180,7 +3180,7 @@ endpoint 1 { ram attribute occupancySensorType; ram attribute occupancySensorTypeBitmap; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 4; + ram attribute clusterRevision default = 5; } } diff --git a/examples/lighting-app/lighting-common/lighting-app.zap b/examples/lighting-app/lighting-common/lighting-app.zap index 04abf7714a1a9c..6592c66dfea564 100644 --- a/examples/lighting-app/lighting-common/lighting-app.zap +++ b/examples/lighting-app/lighting-common/lighting-app.zap @@ -5903,7 +5903,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/network-manager-app/linux/BUILD.gn b/examples/network-manager-app/linux/BUILD.gn index ea973edfbfd15c..0d260d6c5d560c 100644 --- a/examples/network-manager-app/linux/BUILD.gn +++ b/examples/network-manager-app/linux/BUILD.gn @@ -19,6 +19,7 @@ executable("matter-network-manager-app") { sources = [ "include/CHIPProjectAppConfig.h", "main.cpp", + "tbrm.cpp", ] deps = [ diff --git a/examples/network-manager-app/linux/tbrm.cpp b/examples/network-manager-app/linux/tbrm.cpp new file mode 100644 index 00000000000000..908666ee61d8a0 --- /dev/null +++ b/examples/network-manager-app/linux/tbrm.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace chip; +using namespace chip::literals; +using namespace chip::app; +using namespace chip::app::Clusters; + +namespace { +class FakeBorderRouterDelegate final : public ThreadBorderRouterManagement::Delegate +{ + CHIP_ERROR Init(AttributeChangeCallback * attributeChangeCallback) override + { + mAttributeChangeCallback = attributeChangeCallback; + return CHIP_NO_ERROR; + } + + bool GetPanChangeSupported() override { return true; } + + void GetBorderRouterName(MutableCharSpan & borderRouterName) override + { + CopyCharSpanToMutableCharSpan("netman-br"_span, borderRouterName); + } + + CHIP_ERROR GetBorderAgentId(MutableByteSpan & borderAgentId) override + { + static constexpr uint8_t kBorderAgentId[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; + VerifyOrReturnError(borderAgentId.size() == 16, CHIP_ERROR_INVALID_ARGUMENT); + return CopySpanToMutableSpan(ByteSpan(kBorderAgentId), borderAgentId); + } + + uint16_t GetThreadVersion() override { return /* Thread 1.3.1 */ 5; } + + bool GetInterfaceEnabled() override { return !mActiveDataset.IsEmpty(); } + + CHIP_ERROR GetDataset(Thread::OperationalDataset & dataset, DatasetType type) override + { + Thread::OperationalDataset * source; + switch (type) + { + case DatasetType::kActive: + source = &mActiveDataset; + break; + case DatasetType::kPending: + source = &mPendingDataset; + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + VerifyOrReturnError(!source->IsEmpty(), CHIP_ERROR_NOT_FOUND); + return dataset.Init(source->AsByteSpan()); + } + + void SetActiveDataset(const Thread::OperationalDataset & activeDataset, uint32_t sequenceNum, + ActivateDatasetCallback * callback) override + { + if (mActivateDatasetCallback != nullptr) + { + callback->OnActivateDatasetComplete(sequenceNum, CHIP_ERROR_INCORRECT_STATE); + return; + } + + CHIP_ERROR err = mActiveDataset.Init(activeDataset.AsByteSpan()); + if (err != CHIP_NO_ERROR) + { + callback->OnActivateDatasetComplete(sequenceNum, err); + return; + } + + mActivateDatasetCallback = callback; + mActivateDatasetSequence = sequenceNum; + DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(3000), CompleteDatasetActivation, this); + } + + CHIP_ERROR CommitActiveDataset() override { return CHIP_NO_ERROR; } + CHIP_ERROR RevertActiveDataset() override { return CHIP_ERROR_NOT_IMPLEMENTED; } + CHIP_ERROR SetPendingDataset(const Thread::OperationalDataset & pendingDataset) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + +private: + static void CompleteDatasetActivation(System::Layer *, void * context) + { + auto * self = static_cast(context); + auto * callback = self->mActivateDatasetCallback; + auto sequenceNum = self->mActivateDatasetSequence; + self->mActivateDatasetCallback = nullptr; + callback->OnActivateDatasetComplete(sequenceNum, CHIP_NO_ERROR); + } + + AttributeChangeCallback * mAttributeChangeCallback; + Thread::OperationalDataset mActiveDataset; + Thread::OperationalDataset mPendingDataset; + + ActivateDatasetCallback * mActivateDatasetCallback = nullptr; + uint32_t mActivateDatasetSequence; +}; + +FakeBorderRouterDelegate gBorderRouterDelegate{}; +} // namespace + +std::optional gThreadBorderRouterManagementServer; +void emberAfThreadBorderRouterManagementClusterInitCallback(EndpointId endpoint) +{ + VerifyOrDie(!gThreadBorderRouterManagementServer); + gThreadBorderRouterManagementServer.emplace(endpoint, &gBorderRouterDelegate, Server::GetInstance().GetFailSafeContext()) + .Init(); +} diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter index d3a951ddc9f624..173b2a2c923601 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.matter +++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter @@ -1491,6 +1491,49 @@ provisional cluster WiFiNetworkManagement = 1105 { command access(invoke: manage) NetworkPassphraseRequest(): NetworkPassphraseResponse = 0; } +/** Manage the Thread network of Thread Border Router */ +provisional cluster ThreadBorderRouterManagement = 1106 { + revision 1; + + bitmap Feature : bitmap32 { + kPANChange = 0x1; + } + + provisional readonly attribute char_string<63> borderRouterName = 0; + provisional readonly attribute octet_string<254> borderAgentID = 1; + provisional readonly attribute int16u threadVersion = 2; + provisional readonly attribute boolean interfaceEnabled = 3; + provisional readonly attribute nullable int64u activeDatasetTimestamp = 4; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + response struct DatasetResponse = 2 { + octet_string<254> dataset = 0; + } + + request struct SetActiveDatasetRequestRequest { + octet_string<254> activeDataset = 0; + optional int64u breadcrumb = 1; + } + + request struct SetPendingDatasetRequestRequest { + octet_string<254> pendingDataset = 0; + } + + /** Command to request the active operational dataset of the Thread network to which the border router is connected. This command must be sent over a valid CASE session */ + command access(invoke: manage) GetActiveDatasetRequest(): DatasetResponse = 0; + /** Command to request the pending dataset of the Thread network to which the border router is connected. This command must be sent over a valid CASE session */ + command access(invoke: manage) GetPendingDatasetRequest(): DatasetResponse = 1; + /** Command to set or update the active Dataset of the Thread network to which the Border Router is connected. */ + command access(invoke: manage) SetActiveDatasetRequest(SetActiveDatasetRequestRequest): DefaultSuccess = 3; + /** Command set or update the pending Dataset of the Thread network to which the Border Router is connected. */ + command access(invoke: manage) SetPendingDatasetRequest(SetPendingDatasetRequestRequest): DefaultSuccess = 4; +} + /** Manages the names and credentials of Thread networks visible to the user. */ provisional cluster ThreadNetworkDirectory = 1107 { revision 1; @@ -1816,6 +1859,25 @@ endpoint 1 { handle command NetworkPassphraseResponse; } + server cluster ThreadBorderRouterManagement { + callback attribute borderRouterName; + callback attribute borderAgentID; + callback attribute threadVersion; + callback attribute interfaceEnabled; + callback attribute activeDatasetTimestamp; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + ram attribute clusterRevision default = 1; + + handle command GetActiveDatasetRequest; + handle command GetPendingDatasetRequest; + handle command DatasetResponse; + handle command SetActiveDatasetRequest; + } + server cluster ThreadNetworkDirectory { callback attribute preferredExtendedPanID; callback attribute threadNetworks; diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap index 64113c969dd774..0a14bcb26d7b03 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.zap +++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap @@ -3217,6 +3217,7 @@ "define": "WIFI_NETWORK_MANAGEMENT_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "NetworkPassphraseRequest", @@ -3366,6 +3367,227 @@ } ] }, + { + "name": "Thread Border Router Management", + "code": 1106, + "mfgCode": null, + "define": "THREAD_BORDER_ROUTER_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "apiMaturity": "provisional", + "commands": [ + { + "name": "GetActiveDatasetRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetPendingDatasetRequest", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "DatasetResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "SetActiveDatasetRequest", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "BorderRouterName", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BorderAgentID", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "octet_string", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ThreadVersion", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "InterfaceEnabled", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveDatasetTimestamp", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, { "name": "Thread Network Directory", "code": 1107, @@ -3373,6 +3595,7 @@ "define": "THREAD_NETWORK_DIRECTORY_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "AddNetwork", diff --git a/examples/placeholder/linux/apps/app1/config.matter b/examples/placeholder/linux/apps/app1/config.matter index 44c2750954c902..b4e96362bbb7c4 100644 --- a/examples/placeholder/linux/apps/app1/config.matter +++ b/examples/placeholder/linux/apps/app1/config.matter @@ -6613,7 +6613,7 @@ cluster RelativeHumidityMeasurement = 1029 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; @@ -9566,7 +9566,7 @@ endpoint 1 { ram attribute physicalContactUnoccupiedToOccupiedDelay default = 0x00; ram attribute physicalContactUnoccupiedToOccupiedThreshold default = 1; ram attribute featureMap default = 0; - callback attribute clusterRevision default = 4; + callback attribute clusterRevision default = 5; } } diff --git a/examples/placeholder/linux/apps/app1/config.zap b/examples/placeholder/linux/apps/app1/config.zap index e16b695e63f9e4..1a21bd9533419b 100644 --- a/examples/placeholder/linux/apps/app1/config.zap +++ b/examples/placeholder/linux/apps/app1/config.zap @@ -15100,7 +15100,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/placeholder/linux/apps/app2/config.matter b/examples/placeholder/linux/apps/app2/config.matter index 2d1d39836a51c7..65d42505d62e5c 100644 --- a/examples/placeholder/linux/apps/app2/config.matter +++ b/examples/placeholder/linux/apps/app2/config.matter @@ -6570,7 +6570,7 @@ cluster RelativeHumidityMeasurement = 1029 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; @@ -9505,7 +9505,7 @@ endpoint 1 { ram attribute physicalContactUnoccupiedToOccupiedDelay default = 0x00; ram attribute physicalContactUnoccupiedToOccupiedThreshold default = 1; ram attribute featureMap default = 0; - callback attribute clusterRevision default = 4; + callback attribute clusterRevision default = 5; } } diff --git a/examples/placeholder/linux/apps/app2/config.zap b/examples/placeholder/linux/apps/app2/config.zap index 56e97fa89a0ab1..9bc81ae46fea4c 100644 --- a/examples/placeholder/linux/apps/app2/config.zap +++ b/examples/placeholder/linux/apps/app2/config.zap @@ -14860,7 +14860,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/pump-app/pump-common/pump-app.matter b/examples/pump-app/pump-common/pump-app.matter index 172582ef4d1234..ca2ba887cfaeee 100644 --- a/examples/pump-app/pump-common/pump-app.matter +++ b/examples/pump-app/pump-common/pump-app.matter @@ -1906,7 +1906,7 @@ cluster FlowMeasurement = 1028 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/pump-app/silabs/data_model/pump-thread-app.matter b/examples/pump-app/silabs/data_model/pump-thread-app.matter index dae1ee04e0f3ac..15daa818e304a3 100644 --- a/examples/pump-app/silabs/data_model/pump-thread-app.matter +++ b/examples/pump-app/silabs/data_model/pump-thread-app.matter @@ -1906,7 +1906,7 @@ cluster FlowMeasurement = 1028 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/pump-app/silabs/data_model/pump-wifi-app.matter b/examples/pump-app/silabs/data_model/pump-wifi-app.matter index dae1ee04e0f3ac..15daa818e304a3 100644 --- a/examples/pump-app/silabs/data_model/pump-wifi-app.matter +++ b/examples/pump-app/silabs/data_model/pump-wifi-app.matter @@ -1906,7 +1906,7 @@ cluster FlowMeasurement = 1028 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java index 1a04c5294d2f30..f0feb70d529677 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java @@ -58,38 +58,43 @@ public Set getSupportedClusters(final Resources resources, fin SupportedCluster cluster = new SupportedCluster(); while (reader.hasNext()) { String name = reader.nextName(); - if (name.equals(KEY_CLUSTER_ID)) { - cluster.clusterIdentifier = reader.nextInt(); - } else if (name.equals(KEY_FEATURE_FLAGS)) { - cluster.features = reader.nextInt(); - } else if (name.equals(KEY_OPTIONAL_COMMANDS)) { - List commands = new ArrayList<>(); - reader.beginArray(); - while (reader.hasNext()) { - commands.add(reader.nextInt()); + try { + if (name.equals(KEY_CLUSTER_ID)) { + cluster.clusterIdentifier = reader.nextInt(); + } else if (name.equals(KEY_FEATURE_FLAGS)) { + cluster.features = reader.nextInt(); + } else if (name.equals(KEY_OPTIONAL_COMMANDS)) { + List commands = new ArrayList<>(); + reader.beginArray(); + while (reader.hasNext()) { + commands.add(reader.nextInt()); + } + reader.endArray(); + int[] commandIds = new int[commands.size()]; + int i = 0; + for (Integer command : commands) { + commandIds[i++] = command; + } + cluster.optionalCommandIdentifiers = commandIds; + } else if (name.equals(KEY_OPTIONAL_ATTRIBUTES)) { + List attributes = new ArrayList<>(); + reader.beginArray(); + while (reader.hasNext()) { + attributes.add(reader.nextInt()); + } + reader.endArray(); + int[] attributeIds = new int[attributes.size()]; + int i = 0; + for (Integer command : attributes) { + attributeIds[i++] = command; + } + cluster.optionalAttributesIdentifiers = attributeIds; + } else { + reader.skipValue(); } - reader.endArray(); - int[] commandIds = new int[commands.size()]; - int i = 0; - for (Integer command : commands) { - commandIds[i++] = command; - } - cluster.optionalCommandIdentifiers = commandIds; - } else if (name.equals(KEY_OPTIONAL_ATTRIBUTES)) { - List attributes = new ArrayList<>(); - reader.beginArray(); - while (reader.hasNext()) { - attributes.add(reader.nextInt()); - } - reader.endArray(); - int[] attributeIds = new int[attributes.size()]; - int i = 0; - for (Integer command : attributes) { - attributeIds[i++] = command; - } - cluster.optionalAttributesIdentifiers = attributeIds; - } else { - reader.skipValue(); + } catch (NumberFormatException | IllegalStateException e) { + Log.e(TAG, "Invalid number format in JSON for key: " + name, e); + reader.skipValue(); // Skip the invalid entry } } supportedClusters.add(cluster); diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp index 199ec7761be0c8..8d459588a7a8d6 100644 --- a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp +++ b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp @@ -133,6 +133,26 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust JniUtfString respStr(env, resp); ChipLogProgress(Zcl, "ContentAppCommandDelegate::InvokeCommand got response %s", respStr.c_str()); + Json::CharReaderBuilder readerBuilder; + std::string errors; + + std::unique_ptr testReader(readerBuilder.newCharReader()); + + if (!testReader->parse(respStr.c_str(), respStr.c_str() + std::strlen(respStr.c_str()), &value, &errors)) + { + ChipLogError(Zcl, "Failed to parse JSON: %s\n", errors.c_str()); + env->DeleteLocalRef(resp); + return chip::Protocols::InteractionModel::Status::Failure; + } + + // Validate and access JSON data safely + if (!value.isObject()) + { + ChipLogError(Zcl, "Invalid JSON structure: not an object"); + env->DeleteLocalRef(resp); + return chip::Protocols::InteractionModel::Status::Failure; + } + Json::Reader reader; if (!reader.parse(respStr.c_str(), value)) { @@ -166,7 +186,26 @@ void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::Hand { handlerContext.SetCommandHandled(); Json::Reader reader; + + Json::CharReaderBuilder readerBuilder; + std::string errors; + Json::Value value; + std::unique_ptr testReader(readerBuilder.newCharReader()); + + if (!testReader->parse(response, response + std::strlen(response), &value, &errors)) + { + ChipLogError(Zcl, "Failed to parse JSON: %s\n", errors.c_str()); + return; + } + + // Validate and access JSON data safely + if (!value.isObject()) + { + ChipLogError(Zcl, "Invalid JSON structure: not an object"); + return; + } + if (!reader.parse(response, value)) { return; diff --git a/scripts/tests/chiptest/__init__.py b/scripts/tests/chiptest/__init__.py index 29aa9d47dce42d..d85e82fec6fa6c 100644 --- a/scripts/tests/chiptest/__init__.py +++ b/scripts/tests/chiptest/__init__.py @@ -289,7 +289,7 @@ def target_for_name(name: str): return TestTarget.MWO if name.startswith("Test_TC_RVCRUNM_") or name.startswith("Test_TC_RVCCLEANM_") or name.startswith("Test_TC_RVCOPSTATE_"): return TestTarget.RVC - if name.startswith("Test_TC_THNETDIR_") or name.startswith("Test_TC_WIFINM_"): + if name.startswith("Test_TC_TBRM_") or name.startswith("Test_TC_THNETDIR_") or name.startswith("Test_TC_WIFINM_"): return TestTarget.NETWORK_MANAGER return TestTarget.ALL_CLUSTERS diff --git a/src/app/CommandSender.h b/src/app/CommandSender.h index 489c6aa9cd2965..ba98c7178a5c83 100644 --- a/src/app/CommandSender.h +++ b/src/app/CommandSender.h @@ -100,8 +100,8 @@ class CommandSender final : public Messaging::ExchangeDelegate * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout. * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server. * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific - * status response from the server. In that case, - * StatusIB::InitFromChipError can be used to extract the status. + * status response from the server. In that case, constructing + * a StatusIB from the error can be used to extract the status. * - CHIP_ERROR*: All other cases. */ CHIP_ERROR error; diff --git a/src/app/CommandSenderLegacyCallback.h b/src/app/CommandSenderLegacyCallback.h index f6c63c1fd96292..2645dcbdee58df 100644 --- a/src/app/CommandSenderLegacyCallback.h +++ b/src/app/CommandSenderLegacyCallback.h @@ -65,8 +65,8 @@ class CommandSenderLegacyCallback * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout. * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server. * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific or path-specific - * status response from the server. In that case, - * StatusIB::InitFromChipError can be used to extract the status. + * status response from the server. In that case, constructing a + * StatusIB from the error can be used to extract the status. * - Note: a CommandSender using `CommandSender::Callback` only supports sending * a single InvokeRequest. As a result, only one path-specific error is expected * to ever be sent to the OnError callback. diff --git a/src/app/MessageDef/StatusIB.cpp b/src/app/MessageDef/StatusIB.cpp index 55fec8b92661de..d612f549aeeae1 100644 --- a/src/app/MessageDef/StatusIB.cpp +++ b/src/app/MessageDef/StatusIB.cpp @@ -149,7 +149,7 @@ CHIP_ERROR StatusIB::ToChipError() const return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(mStatus)); } -void StatusIB::InitFromChipError(CHIP_ERROR aError) +StatusIB::StatusIB(CHIP_ERROR aError) { if (aError.IsPart(ChipError::SdkPart::kIMClusterStatus)) { @@ -204,8 +204,7 @@ bool FormatStatusIBError(char * buf, uint16_t bufSize, CHIP_ERROR err) constexpr size_t formattedSize = max(sizeof(generalFormat) + statusNameMaxLength, sizeof(clusterFormat)); char formattedString[formattedSize]; - StatusIB status; - status.InitFromChipError(err); + StatusIB status(err); if (status.mClusterStatus.HasValue()) { snprintf(formattedString, formattedSize, clusterFormat, status.mClusterStatus.Value()); diff --git a/src/app/MessageDef/StatusIB.h b/src/app/MessageDef/StatusIB.h index 08137045ffcd7b..291a68c7fd295d 100644 --- a/src/app/MessageDef/StatusIB.h +++ b/src/app/MessageDef/StatusIB.h @@ -60,7 +60,7 @@ struct StatusIB } } - explicit StatusIB(CHIP_ERROR error) { InitFromChipError(error); } + explicit StatusIB(CHIP_ERROR error); enum class Tag : uint8_t { @@ -105,13 +105,6 @@ struct StatusIB */ CHIP_ERROR ToChipError() const; - /** - * Extract a CHIP_ERROR into this StatusIB. If IsIMStatus() is false for - * the error, this might do a best-effort attempt to come up with a - * corresponding StatusIB, defaulting to a generic Status::Failure. - */ - void InitFromChipError(CHIP_ERROR aError); - /** * Test whether this status is a success. */ diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 9bfb628b47e252..57852499c6ccbf 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -195,8 +195,8 @@ class ReadClient : public Messaging::ExchangeDelegate * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout. * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server. * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific - * status response from the server. In that case, - * StatusIB::InitFromChipError can be used to extract the status. + * status response from the server. In that case, constructing + * a StatusIB from the error can be used to extract the status. * - CHIP_ERROR*: All other cases. * * This object MUST continue to exist after this call is completed. The application shall wait until it diff --git a/src/app/TimedRequest.h b/src/app/TimedRequest.h index edfe1d78cf8b6a..729c1266b4d401 100644 --- a/src/app/TimedRequest.h +++ b/src/app/TimedRequest.h @@ -38,8 +38,8 @@ class TimedRequest // but came in after we sent a timed request). // // If the response is a failure StatusResponse, its status will be - // encapsulated in the CHIP_ERROR this returns. In that case, - // StatusIB::InitFromChipError can be used to extract the status. + // encapsulated in the CHIP_ERROR this returns. In that case, constructing + // a StatusIB from the error can be used to extract the status. static CHIP_ERROR HandleResponse(const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload); }; diff --git a/src/app/WriteClient.h b/src/app/WriteClient.h index b4d803981be3d8..b056d238bf7e6e 100644 --- a/src/app/WriteClient.h +++ b/src/app/WriteClient.h @@ -88,8 +88,8 @@ class WriteClient : public Messaging::ExchangeDelegate * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout. * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server. * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific - * status response from the server. In that case, - * StatusIB::InitFromChipError can be used to extract the status. + * status response from the server. In that case, constructing + * a StatusIB from the error can be used to extract the status. * - CHIP_ERROR*: All other cases. * * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it diff --git a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp index 26f1c96cd65fa6..4d7f7b8ddf754e 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp @@ -66,9 +66,10 @@ CHIP_ERROR AttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeVa // TODO(#33223) To improve safety we could make GetEncodableLocationDescriptorStruct a private // memeber method where we explicitly delete member method for the parameter that matches // (LocationDescriptorStruct && aLocationDescriptor). -Structs::LocationDescriptorStruct::Type GetEncodableLocationDescriptorStruct(const LocationDescriptorStruct & aLocationDescriptor) +Globals::Structs::LocationDescriptorStruct::Type +GetEncodableLocationDescriptorStruct(const LocationDescriptorStruct & aLocationDescriptor) { - Structs::LocationDescriptorStruct::Type locationDescriptor; + Globals::Structs::LocationDescriptorStruct::Type locationDescriptor; // This would imply data is either not properly validated before being // stored here or corruption has occurred. VerifyOrDie(!aLocationDescriptor.mLocationName.empty()); @@ -199,7 +200,8 @@ EcosystemLocationStruct::Builder & EcosystemLocationStruct::Builder::SetFloorNum return *this; } -EcosystemLocationStruct::Builder & EcosystemLocationStruct::Builder::SetAreaTypeTag(std::optional aAreaTypeTag) +EcosystemLocationStruct::Builder & +EcosystemLocationStruct::Builder::SetAreaTypeTag(std::optional aAreaTypeTag) { VerifyOrDie(!mIsAlreadyBuilt); mLocationDescriptor.mAreaType = aAreaTypeTag; diff --git a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h index f8407861992d3c..58c64262166403 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h @@ -96,7 +96,7 @@ struct LocationDescriptorStruct { std::string mLocationName; std::optional mFloorNumber; - std::optional mAreaType; + std::optional mAreaType; }; // This intentionally mirrors Structs::EcosystemLocationStruct::Type but has ownership @@ -111,7 +111,7 @@ class EcosystemLocationStruct Builder & SetLocationName(std::string aLocationName); Builder & SetFloorNumber(std::optional aFloorNumber); - Builder & SetAreaTypeTag(std::optional aAreaTypeTag); + Builder & SetAreaTypeTag(std::optional aAreaTypeTag); Builder & SetLocationDescriptorLastEdit(uint64_t aLocationDescriptorLastEditEpochUs); // Upon success this object will have moved all ownership of underlying diff --git a/src/app/clusters/on-off-server/on-off-server.cpp b/src/app/clusters/on-off-server/on-off-server.cpp index 98dfaf9da02c3e..d18aa09383a0d5 100644 --- a/src/app/clusters/on-off-server/on-off-server.cpp +++ b/src/app/clusters/on-off-server/on-off-server.cpp @@ -45,6 +45,7 @@ #endif // MATTER_DM_PLUGIN_MODE_BASE #include +#include #include using namespace chip; @@ -52,6 +53,8 @@ using namespace chip::app::Clusters; using namespace chip::app::Clusters::OnOff; using chip::Protocols::InteractionModel::Status; +using BootReasonType = GeneralDiagnostics::BootReasonEnum; + namespace { #ifdef MATTER_DM_PLUGIN_MODE_BASE @@ -95,6 +98,22 @@ bool IsKnownEnumValue(EnumType value) return (EnsureKnownEnumValue(value) != EnumType::kUnknownEnumValue); } +BootReasonType GetBootReason() +{ + BootReasonType bootReason = BootReasonType::kUnspecified; + + CHIP_ERROR error = DeviceLayer::GetDiagnosticDataProvider().GetBootReason(bootReason); + if (error != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Unable to retrieve boot reason: %" CHIP_ERROR_FORMAT, error.Format()); + bootReason = BootReasonType::kUnspecified; + } + + ChipLogProgress(Zcl, "Boot reason: %u", to_underlying(bootReason)); + + return bootReason; +} + } // namespace #ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL @@ -537,35 +556,36 @@ Status OnOffServer::getOnOffValueForStartUp(chip::EndpointId endpoint, bool & on { app::DataModel::Nullable startUpOnOff; Status status = Attributes::StartUpOnOff::Get(endpoint, startUpOnOff); - if (status == Status::Success) + VerifyOrReturnError(status == Status::Success, status); + + bool currentOnOff = false; + status = Attributes::OnOff::Get(endpoint, ¤tOnOff); + VerifyOrReturnError(status == Status::Success, status); + + if (startUpOnOff.IsNull() || GetBootReason() == BootReasonType::kSoftwareUpdateCompleted) { - // Initialise updated value to 0 - bool updatedOnOff = false; - status = Attributes::OnOff::Get(endpoint, &updatedOnOff); - if (status == Status::Success) - { - if (!startUpOnOff.IsNull()) - { - switch (startUpOnOff.Value()) - { - case OnOff::StartUpOnOffEnum::kOff: - updatedOnOff = false; // Off - break; - case OnOff::StartUpOnOffEnum::kOn: - updatedOnOff = true; // On - break; - case OnOff::StartUpOnOffEnum::kToggle: - updatedOnOff = !updatedOnOff; - break; - default: - // All other values 0x03- 0xFE are reserved - no action. - break; - } - } - onOffValueForStartUp = updatedOnOff; - } + onOffValueForStartUp = currentOnOff; + return Status::Success; } - return status; + + switch (startUpOnOff.Value()) + { + case OnOff::StartUpOnOffEnum::kOff: + onOffValueForStartUp = false; // Off + break; + case OnOff::StartUpOnOffEnum::kOn: + onOffValueForStartUp = true; // On + break; + case OnOff::StartUpOnOffEnum::kToggle: + onOffValueForStartUp = !currentOnOff; + break; + default: + // All other values 0x03- 0xFE are reserved - no action. + onOffValueForStartUp = currentOnOff; + break; + } + + return Status::Success; } bool OnOffServer::offCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath) diff --git a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h index a2b6d7949ffdff..639e7b13e77ac0 100644 --- a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h +++ b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h @@ -17,17 +17,13 @@ #pragma once -#include "thread-br-delegate.h" - #include #include -#include -#include #include #include #include +#include #include -#include #include #include diff --git a/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp b/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp index 6daaffce9ce91c..c272e0acdd7461 100644 --- a/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp +++ b/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp @@ -46,7 +46,8 @@ CHIP_ERROR DefaultThreadNetworkDirectoryStorage::StoreIndex() { VerifyOrDie(mInitialized); StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryIndex(); - return mStorage.SyncSetKeyValue(key.KeyName(), mExtendedPanIds, mCount * ExtendedPanId::size()); + static_assert(kCapacity * ExtendedPanId::size() <= UINT16_MAX); // kCapacity >= mCount + return mStorage.SyncSetKeyValue(key.KeyName(), mExtendedPanIds, static_cast(mCount * ExtendedPanId::size())); } bool DefaultThreadNetworkDirectoryStorage::FindNetwork(const ExtendedPanId & exPanId, index_t & outIndex) diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp index e3b9823efeef9a..cea75c8d0c1ccc 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp @@ -229,8 +229,8 @@ bool CodegenDataModelProvider::EmberCommandListIterator::Exists(const CommandId return (*mCurrentHint == toCheck); } -CHIP_ERROR CodegenDataModelProvider::Invoke(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments, - CommandHandler * handler) +DataModel::ActionReturnStatus CodegenDataModelProvider::Invoke(const DataModel::InvokeRequest & request, + TLV::TLVReader & input_arguments, CommandHandler * handler) { // TODO: CommandHandlerInterface support is currently // residing in InteractionModelEngine itself. We may want to separate this out diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider.h b/src/app/codegen-data-model-provider/CodegenDataModelProvider.h index 21dc0cc87e1a31..b486b953976328 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider.h +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider.h @@ -16,6 +16,7 @@ */ #pragma once +#include "app/data-model-provider/ActionReturnStatus.h" #include #include @@ -68,10 +69,12 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider /// Generic model implementations CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - CHIP_ERROR ReadAttribute(const DataModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; - CHIP_ERROR WriteAttribute(const DataModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; - CHIP_ERROR Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - CommandHandler * handler) override; + DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & encoder) override; + DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request, + AttributeValueDecoder & decoder) override; + DataModel::ActionReturnStatus Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + CommandHandler * handler) override; /// attribute tree iteration EndpointId FirstEndpoint() override; diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp index d27d18964dccf7..6ff4b730812d16 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp @@ -47,7 +47,9 @@ namespace chip { namespace app { namespace { + using namespace chip::app::Compatibility::Internal; +using Protocols::InteractionModel::Status; /// Attempts to read via an attribute access interface (AAI) /// @@ -258,7 +260,8 @@ CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * meta /// - validate ACL (only for non-internal requests) /// - Try to read attribute via the AttributeAccessInterface /// - Try to read the value from ember RAM storage -CHIP_ERROR CodegenDataModelProvider::ReadAttribute(const DataModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) +DataModel::ActionReturnStatus CodegenDataModelProvider::ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & encoder) { ChipLogDetail(DataManagement, "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", @@ -290,12 +293,12 @@ CHIP_ERROR CodegenDataModelProvider::ReadAttribute(const DataModel::ReadAttribut auto metadata = Ember::FindAttributeMetadata(request.path); // Explicit failure in finding a suitable metadata - if (const CHIP_ERROR * err = std::get_if(&metadata)) + if (const Status * status = std::get_if(&metadata)) { - VerifyOrDie((*err == CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)) || // - (*err == CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)) || // - (*err == CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute))); - return *err; + VerifyOrDie((*status == Status::UnsupportedEndpoint) || // + (*status == Status::UnsupportedCluster) || // + (*status == Status::UnsupportedAttribute)); + return *status; } // Read via AAI diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp index 962355e8e47b08..925d5cce61bc87 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp @@ -43,6 +43,7 @@ namespace app { namespace { using namespace chip::app::Compatibility::Internal; +using Protocols::InteractionModel::Status; /// Attempts to write via an attribute access interface (AAI) /// @@ -266,8 +267,8 @@ CHIP_ERROR DecodeValueIntoEmberBuffer(AttributeValueDecoder & decoder, const Emb } // namespace -CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttributeRequest & request, - AttributeValueDecoder & decoder) +DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttributeRequest & request, + AttributeValueDecoder & decoder) { ChipLogDetail(DataManagement, "Writing attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI, ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId)); @@ -275,7 +276,7 @@ CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttrib // ACL check for non-internal requests if (!request.operationFlags.Has(DataModel::OperationFlags::kInternal)) { - ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); + ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), Status::UnsupportedAccess); Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId }; CHIP_ERROR err = Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath, @@ -286,18 +287,19 @@ CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttrib ReturnErrorCodeIf(err != CHIP_ERROR_ACCESS_DENIED, err); // TODO: when wildcard/group writes are supported, handle them to discard rather than fail with status - return CHIP_IM_GLOBAL_STATUS(UnsupportedAccess); + return Status::UnsupportedAccess; } } auto metadata = Ember::FindAttributeMetadata(request.path); - if (const CHIP_ERROR * err = std::get_if(&metadata)) + // Explicit failure in finding a suitable metadata + if (const Status * status = std::get_if(&metadata)) { - VerifyOrDie((*err == CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)) || // - (*err == CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)) || // - (*err == CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute))); - return *err; + VerifyOrDie((*status == Status::UnsupportedEndpoint) || // + (*status == Status::UnsupportedCluster) || // + (*status == Status::UnsupportedAttribute)); + return *status; } const EmberAfAttributeMetadata ** attributeMetadata = std::get_if(&metadata); @@ -316,15 +318,15 @@ CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttrib // Internal is allowed to bypass timed writes and read-only. if (!request.operationFlags.Has(DataModel::OperationFlags::kInternal)) { - VerifyOrReturnError(!isReadOnly, CHIP_IM_GLOBAL_STATUS(UnsupportedWrite)); + VerifyOrReturnError(!isReadOnly, Status::UnsupportedWrite); VerifyOrReturnError(!(*attributeMetadata)->MustUseTimedWrite() || request.writeFlags.Has(DataModel::WriteFlags::kTimed), - CHIP_IM_GLOBAL_STATUS(NeedsTimedInteraction)); + Status::NeedsTimedInteraction); } // Extra check: internal requests can bypass the read only check, however global attributes // have no underlying storage, so write still cannot be done - VerifyOrReturnError(attributeMetadata != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedWrite)); + VerifyOrReturnError(attributeMetadata != nullptr, Status::UnsupportedWrite); if (request.path.mDataVersion.HasValue()) { @@ -333,14 +335,14 @@ CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttrib { ChipLogError(DataManagement, "Unable to get cluster info for Endpoint 0x%x, Cluster " ChipLogFormatMEI, request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId)); - return CHIP_IM_GLOBAL_STATUS(DataVersionMismatch); + return Status::DataVersionMismatch; } if (request.path.mDataVersion.Value() != clusterInfo->dataVersion) { ChipLogError(DataManagement, "Write Version mismatch for Endpoint 0x%x, Cluster " ChipLogFormatMEI, request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId)); - return CHIP_IM_GLOBAL_STATUS(DataVersionMismatch); + return Status::DataVersionMismatch; } } @@ -367,7 +369,7 @@ CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttrib if (dataBuffer.size() > (*attributeMetadata)->size) { ChipLogDetail(Zcl, "Data to write exceeds the attribute size claimed."); - return CHIP_IM_GLOBAL_STATUS(InvalidValue); + return Status::InvalidValue; } if (request.operationFlags.Has(DataModel::OperationFlags::kInternal)) @@ -386,7 +388,7 @@ CHIP_ERROR CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttrib if (status != Protocols::InteractionModel::Status::Success) { - return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status); + return status; } // TODO: this WILL requre updates diff --git a/src/app/codegen-data-model-provider/EmberMetadata.cpp b/src/app/codegen-data-model-provider/EmberMetadata.cpp index b8d998d29f0b7e..69f9e3e889369c 100644 --- a/src/app/codegen-data-model-provider/EmberMetadata.cpp +++ b/src/app/codegen-data-model-provider/EmberMetadata.cpp @@ -24,9 +24,11 @@ namespace chip { namespace app { namespace Ember { +using Protocols::InteractionModel::Status; + std::variant FindAttributeMetadata(const ConcreteAttributePath & aPath) { @@ -41,8 +43,8 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) const EmberAfCluster * cluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); if (cluster == nullptr) { - return (emberAfFindEndpointType(aPath.mEndpointId) == nullptr) ? CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint) - : CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); + return (emberAfFindEndpointType(aPath.mEndpointId) == nullptr) ? Status::UnsupportedEndpoint + : Status::UnsupportedCluster; } return cluster; @@ -57,18 +59,18 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); if (type == nullptr) { - return CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint); + return Status::UnsupportedEndpoint; } const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); if (cluster == nullptr) { - return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); + return Status::UnsupportedCluster; } // Since we know the attribute is unsupported and the endpoint/cluster are // OK, this is the only option left. - return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + return Status::UnsupportedAttribute; } return metadata; diff --git a/src/app/codegen-data-model-provider/EmberMetadata.h b/src/app/codegen-data-model-provider/EmberMetadata.h index f8f41312f1f7c9..64c0bfe736a25f 100644 --- a/src/app/codegen-data-model-provider/EmberMetadata.h +++ b/src/app/codegen-data-model-provider/EmberMetadata.h @@ -18,6 +18,7 @@ #include #include +#include #include @@ -31,13 +32,13 @@ namespace Ember { /// Possible return values: /// - EmberAfCluster (NEVER null) - Only for GlobalAttributesNotInMetaData /// - EmberAfAttributeMetadata (NEVER null) - if the attribute is known to ember datastore -/// - CHIP_ERROR, only specifically for unknown attributes, may only be one of: -/// - CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint); -/// - CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); -/// - CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); -std::variant FindAttributeMetadata(const ConcreteAttributePath & aPath); diff --git a/src/app/codegen-data-model-provider/tests/BUILD.gn b/src/app/codegen-data-model-provider/tests/BUILD.gn index 451a276c0367d2..247685a91a0e7b 100644 --- a/src/app/codegen-data-model-provider/tests/BUILD.gn +++ b/src/app/codegen-data-model-provider/tests/BUILD.gn @@ -57,5 +57,8 @@ chip_test_suite("tests") { cflags = [ "-Wconversion" ] - public_deps = [ ":mock_model" ] + public_deps = [ + ":mock_model", + "${chip_root}/src/app/data-model-provider:string-builder-adapters", + ] } diff --git a/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp index 947d0aecd9684d..a8862bd9036963 100644 --- a/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp @@ -19,9 +19,6 @@ #include -#include "app/ConcreteCommandPath.h" -#include - #include #include #include @@ -35,9 +32,12 @@ #include #include #include +#include #include #include +#include #include +#include #include #include #include @@ -58,6 +58,7 @@ #include #include #include +#include using namespace chip; using namespace chip::Test; @@ -65,6 +66,8 @@ using namespace chip::app; using namespace chip::app::DataModel; using namespace chip::app::Clusters::Globals::Attributes; +using chip::Protocols::InteractionModel::Status; + namespace { constexpr FabricIndex kTestFabrixIndex = kMinValidFabricIndex; @@ -787,7 +790,7 @@ void TestEmberScalarTypeWrite(const typename NumericAttributeTraits::WorkingT AttributeValueDecoder decoder = test.DecoderFor(value); // write should succeed - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_NO_ERROR); + ASSERT_TRUE(model.WriteAttribute(test.request, decoder).IsSuccess()); // Validate data after write chip::ByteSpan writtenData = Test::GetEmberBuffer(); @@ -844,7 +847,7 @@ void TestEmberScalarNullWrite() AttributeValueDecoder decoder = test.DecoderFor(NullableType()); // write should succeed - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_NO_ERROR); + ASSERT_TRUE(model.WriteAttribute(test.request, decoder).IsSuccess()); // Validate data after write chip::ByteSpan writtenData = Test::GetEmberBuffer(); @@ -1298,7 +1301,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::UnsupportedAccess); } TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) @@ -1311,14 +1314,14 @@ TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) TestReadRequest testRequest(kAdminSubjectDescriptor, ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), AttributeList::Id)); std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::UnsupportedEndpoint); } { TestReadRequest testRequest(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, AttributeList::Id)); std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::UnsupportedCluster); } } @@ -1334,7 +1337,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::UnsupportedAttribute); } // Invalid cluster @@ -1343,7 +1346,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ConcreteAttributePath(kMockEndpoint1, MockClusterId(100), MockAttributeId(1))); std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::UnsupportedCluster); } // Invalid endpoint @@ -1352,7 +1355,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), MockAttributeId(1))); std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::UnsupportedEndpoint); } } @@ -1494,7 +1497,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) // Actual read via an encoder std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Failure)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::Failure); } { @@ -1507,7 +1510,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) // Actual read via an encoder std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Busy)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), Status::Busy); } // reset things to success to not affect other tests @@ -1994,7 +1997,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteAclDeny) TestWriteRequest test(kDenySubjectDescriptor, ConcreteDataAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::UnsupportedAccess); ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); } @@ -2065,7 +2068,7 @@ TEST(TestCodegenModelViaMocks, EmberTestWriteReservedNullPlaceholderToNullable) AttributeValueDecoder decoder = test.DecoderFor(0xFFFFFFFF); // write should fail: we are trying to write null which is out of range - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::ConstraintError); } TEST(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNonNullable) @@ -2173,7 +2176,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongStringOutOfBounds) AttributeValueDecoder decoder = test.DecoderFor( "this is a very long string that will be longer than the default attribute size for our mocks"_span); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(InvalidValue)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::InvalidValue); } TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongString) @@ -2292,7 +2295,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteTimedWrite) TestWriteRequest test(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), kAttributeIdTimedWrite)); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(NeedsTimedInteraction)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::NeedsTimedInteraction); // writing as timed should be fine test.request.writeFlags.Set(WriteFlags::kTimed); @@ -2308,7 +2311,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteReadOnlyAttribute) TestWriteRequest test(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), kAttributeIdReadOnly)); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(UnsupportedWrite)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::UnsupportedWrite); // Internal writes bypass the read only requirement test.request.operationFlags.Set(OperationFlags::kInternal); @@ -2335,7 +2338,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteDataVersion) AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(DataVersionMismatch)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::DataVersionMismatch); // Write passes if we set the right version for the data test.request.path.mDataVersion = MakeOptional(GetVersion()); @@ -2351,18 +2354,18 @@ TEST(TestCodegenModelViaMocks, WriteToInvalidPath) { TestWriteRequest test(kAdminSubjectDescriptor, ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1234), 1234)); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::UnsupportedEndpoint); } { TestWriteRequest test(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint1, MockClusterId(1234), 1234)); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::UnsupportedCluster); } { TestWriteRequest test(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), 1234)); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::UnsupportedAttribute); } } @@ -2374,7 +2377,7 @@ TEST(TestCodegenModelViaMocks, WriteToGlobalAttribute) TestWriteRequest test(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), AttributeList::Id)); AttributeValueDecoder decoder = test.DecoderFor(1234); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(UnsupportedWrite)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::UnsupportedWrite); } TEST(TestCodegenModelViaMocks, EmberWriteFailure) @@ -2390,12 +2393,12 @@ TEST(TestCodegenModelViaMocks, EmberWriteFailure) { AttributeValueDecoder decoder = test.DecoderFor(1234); chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(Failure)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::Failure); } { AttributeValueDecoder decoder = test.DecoderFor(1234); chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(Busy)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::Busy); } // reset things to success to not affect other tests chip::Test::SetEmberReadOutput(ByteSpan()); @@ -2524,6 +2527,6 @@ TEST(TestCodegenModelViaMocks, EmberWriteInvalidDataType) // Embed specifically DOES NOT support structures. // Without AAI, we expect a data type error (translated to failure) - ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_IM_GLOBAL_STATUS(Failure)); + ASSERT_EQ(model.WriteAttribute(test.request, decoder), Status::Failure); ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); } diff --git a/src/app/data-model-provider/ActionReturnStatus.cpp b/src/app/data-model-provider/ActionReturnStatus.cpp new file mode 100644 index 00000000000000..7857246a0e861f --- /dev/null +++ b/src/app/data-model-provider/ActionReturnStatus.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace app { +namespace DataModel { + +using Protocols::InteractionModel::ClusterStatusCode; +using Protocols::InteractionModel::Status; + +namespace { + +bool StatusIsTheSameAsError(const ClusterStatusCode & status, const CHIP_ERROR & err) +{ + auto cluster_code = status.GetClusterSpecificCode(); + if (!cluster_code.HasValue()) + { + // there exist Status::Success, however that may not be encoded + // as a CHIP_ERROR_IM_GLOBAL_STATUS_VALUE as it is just as well a CHIP_NO_ERROR. + // handle that separately + if ((status.GetStatus() == Status::Success) && (err == CHIP_NO_ERROR)) + { + return true; + } + + return err == CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status.GetStatus()); + } + + if (status.GetStatus() != Status::Failure) + { + return false; + } + + return err == CHIP_ERROR_IM_CLUSTER_STATUS_VALUE(cluster_code.Value()); +} + +} // namespace + +bool ActionReturnStatus::operator==(const ActionReturnStatus & other) const +{ + if (mReturnStatus == other.mReturnStatus) + { + return true; + } + + const ClusterStatusCode * thisStatus = std::get_if(&mReturnStatus); + const ClusterStatusCode * otherStatus = std::get_if(&other.mReturnStatus); + + const CHIP_ERROR * thisErr = std::get_if(&mReturnStatus); + const CHIP_ERROR * otherErr = std::get_if(&other.mReturnStatus); + + if (thisStatus && otherErr) + { + return StatusIsTheSameAsError(*thisStatus, *otherErr); + } + + if (otherStatus && thisErr) + { + return StatusIsTheSameAsError(*otherStatus, *thisErr); + } + + return false; +} + +CHIP_ERROR ActionReturnStatus::GetUnderlyingError() const +{ + + if (const CHIP_ERROR * err = std::get_if(&mReturnStatus)) + { + return *err; + } + + if (const ClusterStatusCode * status = std::get_if(&mReturnStatus)) + { + if (status->IsSuccess()) + { + return CHIP_NO_ERROR; + } + + chip::Optional code = status->GetClusterSpecificCode(); + + return code.HasValue() ? CHIP_ERROR_IM_CLUSTER_STATUS_VALUE(code.Value()) + : CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status->GetStatus()); + } + + chipDie(); +} + +ClusterStatusCode ActionReturnStatus::GetStatusCode() const +{ + if (const ClusterStatusCode * status = std::get_if(&mReturnStatus)) + { + return *status; + } + + if (const CHIP_ERROR * err = std::get_if(&mReturnStatus)) + { + return ClusterStatusCode(*err); + } + + // all std::variant cases exhausted + chipDie(); +} + +bool ActionReturnStatus::IsSuccess() const +{ + if (const CHIP_ERROR * err = std::get_if(&mReturnStatus)) + { + return (*err == CHIP_NO_ERROR); + } + + if (const ClusterStatusCode * status = std::get_if(&mReturnStatus)) + { + return status->IsSuccess(); + } + + // all std::variant cases exhausted + chipDie(); +} + +bool ActionReturnStatus::IsOutOfSpaceEncodingResponse() const +{ + if (const CHIP_ERROR * err = std::get_if(&mReturnStatus)) + { + return (*err == CHIP_ERROR_NO_MEMORY) || (*err == CHIP_ERROR_BUFFER_TOO_SMALL); + } + + return false; +} + +const char * ActionReturnStatus::c_str() const +{ + + // Generally size should be sufficient. + // len("Status<123>, Code 255") == 21 (and then 22 for null terminator. We have slack.) + static chip::StringBuilder<32> sFormatBuffer; + + if (const CHIP_ERROR * err = std::get_if(&mReturnStatus)) + { +#if CHIP_CONFIG_ERROR_FORMAT_AS_STRING + return err->Format(); // any length +#else + sFormatBuffer.Reset().AddFormat("%" CHIP_ERROR_FORMAT, err->Format()); + return sFormatBuffer.c_str(); +#endif + } + + if (const ClusterStatusCode * status = std::get_if(&mReturnStatus)) + { +#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT + sFormatBuffer.AddFormat("%s(%d)", Protocols::InteractionModel::StatusName(status->GetStatus()), + static_cast(status->GetStatus())); +#else + if (status->IsSuccess()) + { + sFormatBuffer.Add("Success"); + } + else + { + sFormatBuffer.AddFormat("Status<%d>", static_cast(status->GetStatus())); + } +#endif + + chip::Optional clusterCode = status->GetClusterSpecificCode(); + if (clusterCode.HasValue()) + { + sFormatBuffer.AddFormat(", Code %d", static_cast(clusterCode.Value())); + } + return sFormatBuffer.c_str(); + } + + // all std::variant cases exhausted + chipDie(); +} + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/data-model-provider/ActionReturnStatus.h b/src/app/data-model-provider/ActionReturnStatus.h new file mode 100644 index 00000000000000..516541483ad068 --- /dev/null +++ b/src/app/data-model-provider/ActionReturnStatus.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include + +namespace chip { +namespace app { +namespace DataModel { + +/// An ActionReturnStatus encodes the result of a read/write/invoke. +/// +/// Generally such actions result in a StatusIB in the interaction model, +/// which is a code (InteractionModel::Status) and may have an associated +/// cluster-specific code. +/// +/// However some actions specifically may return additional information for +/// chunking, hence the existence of this class: +/// +/// - encapsulates a ClusterStatusCode for an actual action result +/// - encapsulates a underlying CHIP_ERROR for reporting purposes +/// - has a way to check for "chunking needed" status. +/// +/// The class is directly constructible from statuses (non-exlicit) to make +/// returning of values easy. +class ActionReturnStatus +{ +public: + ActionReturnStatus(CHIP_ERROR error) : mReturnStatus(error) {} + ActionReturnStatus(Protocols::InteractionModel::Status status) : + mReturnStatus(Protocols::InteractionModel::ClusterStatusCode(status)) + {} + ActionReturnStatus(Protocols::InteractionModel::ClusterStatusCode status) : mReturnStatus(status) {} + + /// Constructs a status code. Either returns the underlying code directly + /// or converts the underlying CHIP_ERROR into a cluster status code. + Protocols::InteractionModel::ClusterStatusCode GetStatusCode() const; + + /// Gets the underlying CHIP_ERROR if it exists, otherwise it will + /// return a CHIP_ERROR corresponding to the underlying return status. + /// + /// Success statusess will result in CHIP_NO_ERROR (i.e. cluster specitic success codes are lost) + CHIP_ERROR GetUnderlyingError() const; + + /// If this is a CHIP_NO_ERROR or a Status::Success + bool IsSuccess() const; + + /// Considers if the underlying error is an error or not (CHIP_NO_ERROR is the only non-erro) + /// or if the underlying statuscode is not an error (success and cluster specific successes + /// are not an error). + bool IsError() const { return !IsSuccess(); } + + /// Checks if the underlying error is an out of space condition (i.e. something that + /// chunking can handle by sending partial list data). + /// + /// Generally this is when the return is based on CHIP_ERROR_NO_MEMORY or CHIP_ERROR_BUFFER_TOO_SMALL + bool IsOutOfSpaceEncodingResponse() const; + + // NOTE: operator== will treat a CHIP_GLOBAL_IM_ERROR and a raw cluster status as equal if the statuses match, + // even though a CHIP_ERROR has some formatting info like file/line + bool operator==(const ActionReturnStatus & other) const; + bool operator!=(const ActionReturnStatus & other) const { return !(*this == other); } + + /// Get the formatted string of this status. + /// + /// NOTE: this is NOT thread safe in the general case, however the safety guarantees + /// are similar to chip::ErrorStr which also assumes a static buffer. + /// + /// Use this in the chip main event loop (and since that is a single thread, + /// there should be no races) + const char * c_str() const; + +private: + std::variant mReturnStatus; +}; + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/data-model-provider/BUILD.gn b/src/app/data-model-provider/BUILD.gn index 5093cce2f4124d..40f34db127d501 100644 --- a/src/app/data-model-provider/BUILD.gn +++ b/src/app/data-model-provider/BUILD.gn @@ -12,10 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. import("//build_overrides/chip.gni") +import("//build_overrides/pigweed.gni") source_set("data-model-provider") { sources = [ "ActionContext.h", + "ActionReturnStatus.cpp", + "ActionReturnStatus.h", "Context.h", "EventsGenerator.h", "MetadataTypes.cpp", @@ -40,3 +43,16 @@ source_set("data-model-provider") { "${chip_root}/src/messaging", ] } + +source_set("string-builder-adapters") { + sources = [ + "StringBuilderAdapters.cpp", + "StringBuilderAdapters.h", + ] + + public_deps = [ + ":data-model-provider", + "$dir_pw_string", + "${chip_root}/src/lib/core:string-builder-adapters", + ] +} diff --git a/src/app/data-model-provider/Provider.h b/src/app/data-model-provider/Provider.h index f5d550c806fd89..f38568319eb425 100644 --- a/src/app/data-model-provider/Provider.h +++ b/src/app/data-model-provider/Provider.h @@ -16,6 +16,7 @@ */ #pragma once +#include "lib/core/CHIPError.h" #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include #include @@ -66,18 +68,12 @@ class Provider : public ProviderMetadataTree /// > Else if reading from the attribute in the path requires a privilege that is not /// granted to access the cluster in the path, then the path SHALL be discarded. /// - /// Return codes: - /// CHIP_ERROR_NO_MEMORY or CHIP_ERROR_BUFFER_TOO_SMALL: + /// Return value notes: + /// ActionReturnStatus::IsOutOfSpaceEncodingResponse /// - Indicates that list encoding had insufficient buffer space to encode elements. /// - encoder::GetState().AllowPartialData() determines if these errors are permanent (no partial /// data allowed) or further encoding can be retried (AllowPartialData true for list encoding) - /// CHIP_IM_GLOBAL_STATUS(code): - /// - error codes that are translatable in IM status codes (otherwise we expect Failure to be reported) - /// - to check for this, CHIP_ERROR provides: - /// - ::IsPart(ChipError::SdkPart::kIMGlobalStatus) -> bool - /// - ::GetSdkCode() -> uint8_t to translate to the actual code - /// other internal falures - virtual CHIP_ERROR ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) = 0; + virtual ActionReturnStatus ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) = 0; /// Requests a write of an attribute. /// @@ -89,19 +85,21 @@ class Provider : public ProviderMetadataTree /// - ACL validation (see notes on OperationFlags::kInternal) /// - Validation of readability/writability (also controlled by OperationFlags::kInternal) /// - Validation of timed interaction required (also controlled by OperationFlags::kInternal) - /// - /// Return codes - /// CHIP_IM_GLOBAL_STATUS(code): - /// - error codes that are translatable to specific IM codes - /// - in particular, the following codes are interesting/expected - /// - `UnsupportedWrite` for attempts to write read-only data - /// - `UnsupportedAccess` for ACL failures - /// - `NeedsTimedInteraction` for writes that are not timed however are required to be so - virtual CHIP_ERROR WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) = 0; + virtual ActionReturnStatus WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) = 0; /// `handler` is used to send back the reply. - /// - returning a value other than CHIP_NO_ERROR implies an error reply (error and data are mutually exclusive) - virtual CHIP_ERROR Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) = 0; + /// - returning a value other than Success implies an error reply (error and data are mutually exclusive) + /// + /// Returning anything other than CHIP_NO_ERROR or Status::Success (i.e. success without a return code) + /// means that the invoke will be considered to be returning the given path-specific status WITHOUT any data (any data + /// that was sent via CommandHandler is to be rolled back/discarded). + /// + /// This is because only one of the following may be encoded in a response: + /// - data (as CommandDataIB) which is assumed a "response as a success" + /// - status (as a CommandStatusIB) which is considered a final status, usually an error however + /// cluster-specific success statuses also exist. + virtual ActionReturnStatus Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + CommandHandler * handler) = 0; private: InteractionModelContext mContext = { nullptr }; diff --git a/src/app/data-model-provider/StringBuilderAdapters.cpp b/src/app/data-model-provider/StringBuilderAdapters.cpp new file mode 100644 index 00000000000000..93ad9863fc9762 --- /dev/null +++ b/src/app/data-model-provider/StringBuilderAdapters.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include + +namespace pw { + +template <> +StatusWithSize ToString(const chip::app::DataModel::ActionReturnStatus & status, + pw::span buffer) +{ + return pw::string::Format(buffer, "ActionReturnStatus<%s>", status.c_str()); +} + +} // namespace pw diff --git a/src/app/data-model-provider/StringBuilderAdapters.h b/src/app/data-model-provider/StringBuilderAdapters.h new file mode 100644 index 00000000000000..32da18f43f1681 --- /dev/null +++ b/src/app/data-model-provider/StringBuilderAdapters.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +/// This header includes pigweed stringbuilder adaptations for various chip types. +/// You can see https://pigweed.dev/pw_string/guide.html as a reference. +/// +/// In particular, pigweed code generally looks like: +/// +/// pw::StringBuffer<42> sb; +/// sb << "Here is a value: "; +/// sb << value; +/// +/// Where specific formatters exist for "value". In particular these are used when +/// reporting unit test assertions such as ASSERT_EQ/EXPECT_EQ so if you write code +/// like: +/// +/// ASSERT_EQ(SomeCall(), CHIP_NO_ERROR); +/// +/// On failure without adapters, the objects are reported as "24-byte object at 0x....." +/// which is not as helpful as a full formatted output. + +#include + +#include + +namespace pw { + +template <> +StatusWithSize ToString(const chip::app::DataModel::ActionReturnStatus & status, + pw::span buffer); + +} // namespace pw diff --git a/src/app/data-model-provider/tests/BUILD.gn b/src/app/data-model-provider/tests/BUILD.gn index 23107b9b5b1fd8..9829a2f4622514 100644 --- a/src/app/data-model-provider/tests/BUILD.gn +++ b/src/app/data-model-provider/tests/BUILD.gn @@ -17,12 +17,16 @@ import("${chip_root}/build/chip/chip_test_suite.gni") chip_test_suite("tests") { output_name = "libIMInterfaceTests" - test_sources = [ "TestEventEmitting.cpp" ] + test_sources = [ + "TestActionReturnStatus.cpp", + "TestEventEmitting.cpp", + ] cflags = [ "-Wconversion" ] public_deps = [ "${chip_root}/src/app/data-model-provider", + "${chip_root}/src/app/data-model-provider:string-builder-adapters", "${chip_root}/src/lib/core:string-builder-adapters", ] } diff --git a/src/app/data-model-provider/tests/TestActionReturnStatus.cpp b/src/app/data-model-provider/tests/TestActionReturnStatus.cpp new file mode 100644 index 00000000000000..a194c0838d7d12 --- /dev/null +++ b/src/app/data-model-provider/tests/TestActionReturnStatus.cpp @@ -0,0 +1,113 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lib/core/CHIPError.h" +#include "protocols/interaction_model/StatusCode.h" +#include "pw_unit_test/framework_backend.h" +#include +#include +#include + +#include + +using chip::app::DataModel::ActionReturnStatus; +using chip::Protocols::InteractionModel::ClusterStatusCode; +using chip::Protocols::InteractionModel::Status; + +TEST(TestActionReturnStatus, TestEquality) +{ + // equality should happen between equivalent statuses and chip_errors + ASSERT_EQ(ActionReturnStatus(Status::UnsupportedRead), Status::UnsupportedRead); + ASSERT_EQ(ActionReturnStatus(Status::UnsupportedWrite), CHIP_IM_GLOBAL_STATUS(UnsupportedWrite)); + + ASSERT_EQ(ActionReturnStatus(CHIP_IM_GLOBAL_STATUS(Busy)), Status::Busy); + ASSERT_EQ(ActionReturnStatus(CHIP_IM_GLOBAL_STATUS(Busy)), CHIP_IM_GLOBAL_STATUS(Busy)); + + ASSERT_EQ(ActionReturnStatus(CHIP_IM_CLUSTER_STATUS(123)), CHIP_IM_CLUSTER_STATUS(123)); + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(123)), CHIP_IM_CLUSTER_STATUS(123)); + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(123)), ClusterStatusCode::ClusterSpecificFailure(123)); + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(123)), ClusterStatusCode::ClusterSpecificSuccess(123)); + + // Successes (without cluster-specific codes) are equivalent + ASSERT_EQ(ActionReturnStatus(Status::Success), Status::Success); + ASSERT_EQ(ActionReturnStatus(CHIP_NO_ERROR), Status::Success); + ASSERT_EQ(ActionReturnStatus(Status::Success), CHIP_NO_ERROR); + + // status specific success is has more data, so there is no equality (i.e. an action return + // with specific success codes has more data than a simple success) + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(123)), CHIP_NO_ERROR); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(123)), Status::Success); + ASSERT_NE(ActionReturnStatus(CHIP_NO_ERROR), ClusterStatusCode::ClusterSpecificSuccess(123)); + ASSERT_NE(ActionReturnStatus(Status::Success), ClusterStatusCode::ClusterSpecificSuccess(123)); + + // things that are just not equal + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(11)), ClusterStatusCode::ClusterSpecificSuccess(22)); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(11)), ClusterStatusCode::ClusterSpecificSuccess(11)); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(11)), ClusterStatusCode::ClusterSpecificFailure(22)); + ASSERT_NE(ActionReturnStatus(CHIP_NO_ERROR), CHIP_ERROR_NOT_FOUND); + ASSERT_NE(ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT), CHIP_ERROR_NOT_FOUND); + ASSERT_NE(ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT), CHIP_NO_ERROR); + ASSERT_NE(ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT), Status::Success); + ASSERT_NE(ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT), Status::UnsupportedRead); + ASSERT_NE(ActionReturnStatus(Status::Success), Status::UnsupportedRead); + ASSERT_NE(ActionReturnStatus(Status::Success), CHIP_ERROR_INVALID_ARGUMENT); + ASSERT_NE(ActionReturnStatus(CHIP_ERROR_NOT_FOUND), Status::Failure); + ASSERT_NE(ActionReturnStatus(Status::Failure), CHIP_NO_ERROR); + ASSERT_NE(ActionReturnStatus(Status::Failure), CHIP_ERROR_INVALID_ARGUMENT); + ASSERT_NE(ActionReturnStatus(Status::Failure), ClusterStatusCode::ClusterSpecificSuccess(1)); + ASSERT_NE(ActionReturnStatus(Status::Failure), ClusterStatusCode::ClusterSpecificFailure(2)); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(1)), CHIP_NO_ERROR); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(2)), CHIP_NO_ERROR); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(3)), Status::Failure); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(4)), Status::Failure); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(3)), Status::Success); + ASSERT_NE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(4)), Status::Success); +} + +TEST(TestActionReturnStatus, TestIsError) +{ + ASSERT_TRUE(ActionReturnStatus(CHIP_IM_CLUSTER_STATUS(123)).IsError()); + ASSERT_TRUE(ActionReturnStatus(CHIP_ERROR_INTERNAL).IsError()); + ASSERT_TRUE(ActionReturnStatus(CHIP_ERROR_NO_MEMORY).IsError()); + ASSERT_TRUE(ActionReturnStatus(Status::UnsupportedRead).IsError()); + ASSERT_TRUE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(123)).IsError()); + + ASSERT_FALSE(ActionReturnStatus(Status::Success).IsError()); + ASSERT_FALSE(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(123)).IsError()); + ASSERT_FALSE(ActionReturnStatus(CHIP_NO_ERROR).IsError()); +} + +TEST(TestActionReturnStatus, TestUnderlyingError) +{ + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(123)).GetUnderlyingError(), CHIP_IM_CLUSTER_STATUS(123)); + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(123)).GetUnderlyingError(), CHIP_NO_ERROR); + ASSERT_EQ(ActionReturnStatus(Status::Busy).GetUnderlyingError(), CHIP_IM_GLOBAL_STATUS(Busy)); + ASSERT_EQ(ActionReturnStatus(CHIP_ERROR_INTERNAL).GetUnderlyingError(), CHIP_ERROR_INTERNAL); +} + +TEST(TestActionReturnStatus, TestStatusCode) +{ + ASSERT_EQ(ActionReturnStatus(CHIP_ERROR_INTERNAL).GetStatusCode(), ClusterStatusCode(Status::Failure)); + ASSERT_EQ(ActionReturnStatus(Status::Busy).GetStatusCode(), ClusterStatusCode(Status::Busy)); + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificSuccess(123)).GetStatusCode(), + ClusterStatusCode::ClusterSpecificSuccess(123)); + ASSERT_EQ(ActionReturnStatus(ClusterStatusCode::ClusterSpecificFailure(123)).GetStatusCode(), + ClusterStatusCode::ClusterSpecificFailure(123)); + ASSERT_EQ(ActionReturnStatus(CHIP_IM_CLUSTER_STATUS(0x12)).GetStatusCode(), ClusterStatusCode::ClusterSpecificFailure(0x12)); + ASSERT_EQ(ActionReturnStatus(CHIP_IM_GLOBAL_STATUS(Timeout)).GetStatusCode(), ClusterStatusCode(Status::Timeout)); +} diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index faf76e031e177e..b5621e94bd9f47 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -23,6 +23,7 @@ * */ +#include "app/data-model-provider/ActionReturnStatus.h" #include #if CHIP_CONFIG_ENABLE_ICD_SERVER #include // nogncheck @@ -182,15 +183,20 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu ConcreteReadAttributePath pathForRetrieval(readPath); // Load the saved state from previous encoding session for chunking of one single attribute (list chunking). AttributeEncodeState encodeState = apReadHandler->GetAttributeEncodeState(); - err = Impl::RetrieveClusterData(mpImEngine->GetDataModelProvider(), apReadHandler->GetSubjectDescriptor(), - apReadHandler->IsFabricFiltered(), attributeReportIBs, pathForRetrieval, &encodeState); - if (err != CHIP_NO_ERROR) + DataModel::ActionReturnStatus status = + Impl::RetrieveClusterData(mpImEngine->GetDataModelProvider(), apReadHandler->GetSubjectDescriptor(), + apReadHandler->IsFabricFiltered(), attributeReportIBs, pathForRetrieval, &encodeState); + if (status.IsError()) { + // Operation error set, since this will affect early return or override on status encoding + // it will also be used for error reporting below. + err = status.GetUnderlyingError(); + // If error is not an "out of writer space" error, rollback and encode status. // Otherwise, if partial data allowed, save the encode state. // Otherwise roll back. If we have already encoded some chunks, we are done; otherwise encode status. - if (encodeState.AllowPartialData() && IsOutOfWriterSpaceError(err)) + if (encodeState.AllowPartialData() && status.IsOutOfSpaceEncodingResponse()) { ChipLogDetail(DataManagement, "List does not fit in packet, chunk between list items for clusterId: " ChipLogFormatMEI @@ -210,7 +216,7 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu attributeReportIBs.Rollback(attributeBackup); apReadHandler->SetAttributeEncodeState(AttributeEncodeState()); - if (!IsOutOfWriterSpaceError(err)) + if (!status.IsOutOfSpaceEncodingResponse()) { ChipLogError(DataManagement, "Fail to retrieve data, roll back and encode status on clusterId: " ChipLogFormatMEI @@ -218,7 +224,7 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu ChipLogValueMEI(pathForRetrieval.mClusterId), ChipLogValueMEI(pathForRetrieval.mAttributeId), err.Format()); // Try to encode our error as a status response. - err = attributeReportIBs.EncodeAttributeStatus(pathForRetrieval, StatusIB(err)); + err = attributeReportIBs.EncodeAttributeStatus(pathForRetrieval, StatusIB(status.GetStatusCode())); if (err != CHIP_NO_ERROR) { // OK, just roll back again and give up; if we still ran out of space we diff --git a/src/app/reporting/Read-Checked.cpp b/src/app/reporting/Read-Checked.cpp index 3b434d5da5f624..cd821bc67ae4f6 100644 --- a/src/app/reporting/Read-Checked.cpp +++ b/src/app/reporting/Read-Checked.cpp @@ -14,6 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/data-model-provider/ActionReturnStatus.h" +#include "lib/support/StringBuilder.h" #include #include @@ -26,6 +28,8 @@ namespace reporting { namespace CheckedImpl { namespace { +using DataModel::ActionReturnStatus; + /// Checkpoints and saves the state (including error state) for a /// AttributeReportIBs::Builder class ScopedAttributeReportIBsBuilderState @@ -50,16 +54,16 @@ class ScopedAttributeReportIBsBuilderState } // namespace -CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, - bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, - const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState) +ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, + bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, + const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState) { ChipLogDetail(DataManagement, " Cluster %" PRIx32 ", Attribute %" PRIx32 " is dirty", path.mClusterId, path.mAttributeId); DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Read, DataModelCallbacks::OperationOrder::Pre, path); - CHIP_ERROR errEmber = CHIP_NO_ERROR; + ActionReturnStatus statusEmber(CHIP_NO_ERROR); uint32_t lengthWrittenEmber = 0; // a copy for DM logic only. Ember changes state directly @@ -68,21 +72,22 @@ CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::Su { ScopedAttributeReportIBsBuilderState builderState(reportBuilder); // temporary only - errEmber = + statusEmber = EmberImpl::RetrieveClusterData(dataModel, subjectDescriptor, isFabricFiltered, reportBuilder, path, encoderState); lengthWrittenEmber = reportBuilder.GetWriter()->GetLengthWritten(); } - CHIP_ERROR errDM = DataModelImpl::RetrieveClusterData(dataModel, subjectDescriptor, isFabricFiltered, reportBuilder, path, - encoderState != nullptr ? &stateDm : nullptr); + ActionReturnStatus statusDm = DataModelImpl::RetrieveClusterData(dataModel, subjectDescriptor, isFabricFiltered, reportBuilder, + path, encoderState != nullptr ? &stateDm : nullptr); - if (errEmber != errDM) + if (statusEmber != statusDm) { + StringBuilder<128> buffer; // Note log + chipDie instead of VerifyOrDie so that breakpoints (and usage of rr) // is easier to debug. ChipLogError(Test, "Different return codes between ember and DM"); - ChipLogError(Test, " Ember error: %" CHIP_ERROR_FORMAT, errEmber.Format()); - ChipLogError(Test, " DM error: %" CHIP_ERROR_FORMAT, errDM.Format()); + ChipLogError(Test, " Ember status: %s", statusEmber.c_str()); + ChipLogError(Test, " DM status: %s", statusDm.c_str()); // For time-dependent data, we may have size differences here: one data fitting in buffer // while another not, resulting in different errors (success vs out of space). @@ -120,7 +125,7 @@ CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::Su // For chunked reads, the encoder state MUST be identical (since this is what controls // where chunking resumes). - if ((errEmber == CHIP_ERROR_NO_MEMORY) || (errEmber == CHIP_ERROR_BUFFER_TOO_SMALL)) + if (statusEmber.IsOutOfSpaceEncodingResponse()) { // Encoder state MUST match on partial reads (used by chunking) // specifically ReadViaAccessInterface in ember-compatibility-functions only @@ -151,7 +156,7 @@ CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::Su DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Read, DataModelCallbacks::OperationOrder::Post, path); - return errDM; + return statusDm; } } // namespace CheckedImpl diff --git a/src/app/reporting/Read-Checked.h b/src/app/reporting/Read-Checked.h index 2652d4f98c86d7..742b1e2fef8c3d 100644 --- a/src/app/reporting/Read-Checked.h +++ b/src/app/reporting/Read-Checked.h @@ -16,6 +16,7 @@ */ #pragma once +#include "app/data-model-provider/ActionReturnStatus.h" #include #include #include @@ -27,9 +28,10 @@ namespace app { namespace reporting { namespace CheckedImpl { -CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, - bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, - const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState); +DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, + const Access::SubjectDescriptor & subjectDescriptor, bool isFabricFiltered, + AttributeReportIBs::Builder & reportBuilder, + const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState); } // namespace CheckedImpl } // namespace reporting diff --git a/src/app/reporting/Read-DataModel.cpp b/src/app/reporting/Read-DataModel.cpp index 9364d5c5fde9ce..7df19e2c89ecfc 100644 --- a/src/app/reporting/Read-DataModel.cpp +++ b/src/app/reporting/Read-DataModel.cpp @@ -14,6 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/data-model-provider/ActionReturnStatus.h" +#include "lib/support/logging/TextOnlyLogging.h" #include #include @@ -24,18 +26,11 @@ namespace chip { namespace app { namespace reporting { namespace DataModelImpl { -namespace { -bool IsOutOfSpaceError(CHIP_ERROR err) -{ - return (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL); -} - -} // namespace - -CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, - bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, - const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState) +DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, + const Access::SubjectDescriptor & subjectDescriptor, bool isFabricFiltered, + AttributeReportIBs::Builder & reportBuilder, + const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState) { // Odd ifdef is to only do this if the `Read-Check` does not do it already. #if !CHIP_CONFIG_USE_EMBER_DATA_MODEL @@ -68,9 +63,10 @@ CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::Su reportBuilder.Checkpoint(checkpoint); AttributeValueEncoder attributeValueEncoder(reportBuilder, subjectDescriptor, path, version, isFabricFiltered, encoderState); - CHIP_ERROR err = dataModel->ReadAttribute(readRequest, attributeValueEncoder); - if (err == CHIP_NO_ERROR) + DataModel::ActionReturnStatus status = dataModel->ReadAttribute(readRequest, attributeValueEncoder); + + if (status.IsSuccess()) { // Odd ifdef is to only do this if the `Read-Check` does not do it already. #if !CHIP_CONFIG_USE_EMBER_DATA_MODEL @@ -82,12 +78,12 @@ CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::Su DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Read, DataModelCallbacks::OperationOrder::Post, path); #endif // !CHIP_CONFIG_USE_EMBER_DATA_MODEL - return CHIP_NO_ERROR; + return status; } // Encoder state is relevant for errors in case they are retryable. // - // Generally only IsOutOfSpaceError(err) would be retryable, however we save the state + // Generally only out of space encoding errors would be retryable, however we save the state // for all errors in case this is information that is useful (retry or error position). if (encoderState != nullptr) { @@ -97,11 +93,11 @@ CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::Su // Out of space errors may be chunked data, reporting those cases would be very confusing // as they are not fully errors. Report only others (which presumably are not recoverable // and will be sent to the client as well). - if (!IsOutOfSpaceError(err)) + if (!status.IsOutOfSpaceEncodingResponse()) { - ChipLogError(DataManagement, "Failed to read attribute: %" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str()); } - return err; + return status; } } // namespace DataModelImpl diff --git a/src/app/reporting/Read-DataModel.h b/src/app/reporting/Read-DataModel.h index 346ebb2340f6d7..3a629e66e398d0 100644 --- a/src/app/reporting/Read-DataModel.h +++ b/src/app/reporting/Read-DataModel.h @@ -16,9 +16,11 @@ */ #pragma once +#include "app/data-model-provider/ActionReturnStatus.h" #include #include #include +#include #include #include @@ -27,9 +29,10 @@ namespace app { namespace reporting { namespace DataModelImpl { -CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, - bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, - const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState); +DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, + const Access::SubjectDescriptor & subjectDescriptor, bool isFabricFiltered, + AttributeReportIBs::Builder & reportBuilder, + const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState); } // namespace DataModelImpl } // namespace reporting diff --git a/src/app/reporting/Read-Ember.cpp b/src/app/reporting/Read-Ember.cpp index 3e9ad543a6e2b4..a7bf1023b3d841 100644 --- a/src/app/reporting/Read-Ember.cpp +++ b/src/app/reporting/Read-Ember.cpp @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/data-model-provider/ActionReturnStatus.h" #include #include @@ -25,9 +26,10 @@ namespace app { namespace reporting { namespace EmberImpl { -CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, - bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, - const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState) +DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, + const Access::SubjectDescriptor & subjectDescriptor, bool isFabricFiltered, + AttributeReportIBs::Builder & reportBuilder, + const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState) { // Odd ifdef is to only do this if the `Read-Check` does not do it already. #if !CHIP_CONFIG_USE_DATA_MODEL_INTERFACE diff --git a/src/app/reporting/Read-Ember.h b/src/app/reporting/Read-Ember.h index 5ff3fff4103dc5..129746baf1f9c8 100644 --- a/src/app/reporting/Read-Ember.h +++ b/src/app/reporting/Read-Ember.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -27,9 +28,10 @@ namespace app { namespace reporting { namespace EmberImpl { -CHIP_ERROR RetrieveClusterData(DataModel::Provider * dataModel, const Access::SubjectDescriptor & subjectDescriptor, - bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder, - const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState); +DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, + const Access::SubjectDescriptor & subjectDescriptor, bool isFabricFiltered, + AttributeReportIBs::Builder & reportBuilder, + const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState); } // namespace EmberImpl } // namespace reporting diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 681f16aedc1e6c..1bce08d55e0571 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -140,7 +140,11 @@ source_set("operational-state-test-srcs") { } source_set("thread-border-router-management-test-srcs") { - sources = [ "${chip_root}/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp" ] + sources = [ + "${chip_root}/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp", + "${chip_root}/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h", + "${chip_root}/src/app/clusters/thread-border-router-management-server/thread-br-delegate.h", + ] public_deps = [ "${chip_root}/src/app", diff --git a/src/app/tests/TestStatusIB.cpp b/src/app/tests/TestStatusIB.cpp index 22806168a9e6da..9234a4f2350210 100644 --- a/src/app/tests/TestStatusIB.cpp +++ b/src/app/tests/TestStatusIB.cpp @@ -46,8 +46,7 @@ class TestStatusIB : public ::testing::Test #define VERIFY_ROUNDTRIP(err, status) \ do \ { \ - StatusIB newStatus; \ - newStatus.InitFromChipError(err); \ + StatusIB newStatus(err); \ EXPECT_EQ(newStatus.mStatus, status.mStatus); \ EXPECT_EQ(newStatus.mClusterStatus, status.mClusterStatus); \ } while (0); @@ -86,16 +85,14 @@ TEST_F(TestStatusIB, TestStatusIBToFromChipError) err = status.ToChipError(); EXPECT_NE(err, CHIP_NO_ERROR); { - StatusIB newStatus; - newStatus.InitFromChipError(err); + StatusIB newStatus(err); EXPECT_EQ(newStatus.mStatus, Status::Failure); EXPECT_EQ(newStatus.mClusterStatus, status.mClusterStatus); } err = CHIP_ERROR_NO_MEMORY; { - StatusIB newStatus; - newStatus.InitFromChipError(err); + StatusIB newStatus(err); EXPECT_EQ(newStatus.mStatus, Status::Failure); EXPECT_FALSE(newStatus.mClusterStatus.HasValue()); } diff --git a/src/app/tests/suites/certification/Test_TC_ICDM_3_3.yaml b/src/app/tests/suites/certification/Test_TC_ICDM_3_3.yaml deleted file mode 100644 index dcf55057b7b9a9..00000000000000 --- a/src/app/tests/suites/certification/Test_TC_ICDM_3_3.yaml +++ /dev/null @@ -1,267 +0,0 @@ -# Copyright (c) 2024 Project CHIP Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# Auto-generated scripts for harness use only, please review before automation. The endpoints and cluster names are currently set to default - -name: 217.2.4. [TC-ICDM-3.3] Verify UnregisterClient command with DUT as Server - -PICS: - - ICDM.S - - ICDM.S.C00.Rsp - - ICDM.S.C02.Rsp - -config: - nodeId: 0x12344321 - cluster: "Basic Information" - endpoint: 0 - -tests: - - label: "Preconditions" - verification: | - 1.Commission DUT to TH (can be skipped if done in a preceding test). - 2a.TH reads from the DUT the RegisteredClients attribute. - 2b.If list of registered clients is not empty, unregister existing client(s) - 2c.TH reads from the DUT the RegisteredClients attribute. Verify that the DUT response contains empty list of registered clients. - disabled: true - - - label: - "Step 1: TH sends UnregisterClient command with the CheckInNodeID - (CheckInNodeID1)." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 1 1 0 - - [1702437560.584692][2341:2343] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x8b - [1702437560.584811][2341:2343] CHIP:TOO: Error: IM Error 0x0000058B: General error: 0x8b (NOT_FOUND) - disabled: true - - - label: - "Step 2a: TH sends RegisterClient command. - CheckInNodeID: - registering clients node ID (CheckInNodeID2) - MonitoredSubject: - monitored subject ID (MonitorSubID2) - Key: shared secret between the - client and the ICD (Key2)" - PICS: ICDM.S.C00.Rsp - verification: | - ./chip-tool icdmanagement register-client 2 2 hex:1234567890abcdef1234567890abcdef 1 0 - On TH(chip-tool) verify that DUT responds with status code as success - - [1702437824.926527][2361:2363] CHIP:DMG: Received Command Response Data, Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0001 - [1702437824.926625][2361:2363] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Command 0x0000_0001 - [1702437824.926835][2361:2363] CHIP:TOO: RegisterClientResponse: { - [1702437824.926901][2361:2363] CHIP:TOO: ICDCounter: 2124479668 - [1702437824.926955][2361:2363] CHIP:TOO: } - disabled: true - - - label: "Step 2b: TH reads from the DUT the RegisteredClients attribute." - PICS: ICDM.S.A0003 - verification: | - ./chip-tool icdmanagement read registered-clients 1 0 - - On TH(Chip-tool), Verify that the DUT response contains a list of 1 registered client of given CheckInNodeID, MonitoredSubject, and Key - - [1702437846.906320][2364:2366] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0003 DataVersion: 2633987690 - [1702437846.906504][2364:2366] CHIP:TOO: RegisteredClients: 2 entries - [1702437846.906687][2364:2366] CHIP:TOO: [1]: { - [1702437846.906746][2364:2366] CHIP:TOO: CheckInNodeID: 112233 - [1702437846.906800][2364:2366] CHIP:TOO: MonitoredSubject: 112233 - [1702437846.906880][2364:2366] CHIP:TOO: FabricIndex: 1 - [1702437846.906934][2364:2366] CHIP:TOO: } - [1702437846.907003][2364:2366] CHIP:TOO: [2]: { - [1702437846.907059][2364:2366] CHIP:TOO: CheckInNodeID: 2 - [1702437846.907108][2364:2366] CHIP:TOO: MonitoredSubject: 2 - [1702437846.907160][2364:2366] CHIP:TOO: FabricIndex: 1 - [1702437846.907211][2364:2366] CHIP:TOO: } - disabled: true - - - label: - "Step 3: TH sends UnregisterClient command with the CheckInNodeID - (CheckInNodeID3)." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 3 1 0 - - [1702437560.584692][2341:2343] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x8b - [1702437560.584811][2341:2343] CHIP:TOO: Error: IM Error 0x0000058B: General error: 0x8b (NOT_FOUND) - disabled: true - - - label: - "Step 4a: Setup the TH such that is has administrator privileges for - the ICDM cluster." - verification: | - chip-tool default has admin privilege - disabled: true - - - label: - "Step 4b: TH sends UnregisterClient command with the CheckInNodeID - (CheckInNodeID2)." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 2 1 0 - - [1702438116.143490][2387:2389] CHIP:DMG: InvokeResponseMessage = - [1702438116.143590][2387:2389] CHIP:DMG: { - [1702438116.143648][2387:2389] CHIP:DMG: suppressResponse = false, - [1702438116.143877][2387:2389] CHIP:DMG: InvokeResponseIBs = - [1702438116.144238][2387:2389] CHIP:DMG: [ - [1702438116.144308][2387:2389] CHIP:DMG: InvokeResponseIB = - [1702438116.144414][2387:2389] CHIP:DMG: { - [1702438116.144476][2387:2389] CHIP:DMG: CommandStatusIB = - [1702438116.144575][2387:2389] CHIP:DMG: { - [1702438116.144646][2387:2389] CHIP:DMG: CommandPathIB = - [1702438116.144723][2387:2389] CHIP:DMG: { - [1702438116.144825][2387:2389] CHIP:DMG: EndpointId = 0x0, - [1702438116.144911][2387:2389] CHIP:DMG: ClusterId = 0x46, - [1702438116.144993][2387:2389] CHIP:DMG: CommandId = 0x2, - [1702438116.145097][2387:2389] CHIP:DMG: }, - [1702438116.145186][2387:2389] CHIP:DMG: - [1702438116.145278][2387:2389] CHIP:DMG: StatusIB = - [1702438116.145357][2387:2389] CHIP:DMG: { - [1702438116.145457][2387:2389] CHIP:DMG: status = 0x00 (SUCCESS), - [1702438116.145538][2387:2389] CHIP:DMG: }, - [1702438116.145616][2387:2389] CHIP:DMG: - [1702438116.145683][2387:2389] CHIP:DMG: }, - [1702438116.145782][2387:2389] CHIP:DMG: - [1702438116.145846][2387:2389] CHIP:DMG: }, - [1702438116.145917][2387:2389] CHIP:DMG: - [1702438116.146004][2387:2389] CHIP:DMG: ], - [1702438116.146078][2387:2389] CHIP:DMG: - [1702438116.146155][2387:2389] CHIP:DMG: InteractionModelRevision = 11 - [1702438116.146213][2387:2389] CHIP:DMG: }, - [1702438116.146382][2387:2389] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x0 - disabled: true - - - label: "Step 4c: TH reads from the DUT the RegisteredClients attribute." - PICS: ICDM.S.A0003 - verification: | - ./chip-tool icdmanagement read registered-clients 1 0 - - [1702438139.915311][2390:2392] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0003 DataVersion: 2633987690 - [1702438139.915462][2390:2392] CHIP:TOO: RegisteredClients: 1 entries - [1702438139.915616][2390:2392] CHIP:TOO: [1]: { - [1702438139.915667][2390:2392] CHIP:TOO: CheckInNodeID: 112233 - [1702438139.915708][2390:2392] CHIP:TOO: MonitoredSubject: 112233 - [1702438139.915774][2390:2392] CHIP:TOO: FabricIndex: 1 - [1702438139.915818][2390:2392] CHIP:TOO: } - disabled: true - - - label: - "Step 4d: Clear the THs administrator privileges for the ICDM cluster." - verification: | - ./chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 4, "authMode": 2, "subjects": [112233], "targets": null } ]' 1 0 - disabled: true - - - label: - "Step 5a: TH sends RegisterClient command. - CheckInNodeID: - registering clients node ID (CheckInNodeID5) - MonitoredSubject: - monitored subject ID (MonitorSubID5) - Key: shared secret between the - client and the ICD (Key5) - VerificationKey: verification key - (VerificationKey5)" - PICS: ICDM.S.C00.Rsp - verification: | - ./chip-tool icdmanagement register-client 5 5 hex:5555567890abcdef5555567890abcdef 1 0 --VerificationKey hex:abcdef1234567890abcdef1234567890 - On TH(chip-tool) verify that DUT responds with status code as success - - [1702437824.926527][2361:2363] CHIP:DMG: Received Command Response Data, Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0001 - [1702437824.926625][2361:2363] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Command 0x0000_0001 - [1702437824.926835][2361:2363] CHIP:TOO: RegisterClientResponse: { - [1702437824.926901][2361:2363] CHIP:TOO: ICDCounter: 2124479668 - [1702437824.926955][2361:2363] CHIP:TOO: } - disabled: true - - - label: "Step 5b: TH reads from the DUT the RegisteredClients attribute." - PICS: ICDM.S.A0003 - verification: | - ./chip-tool icdmanagement read registered-clients 1 0 - - On TH(Chip-tool), Verify that the DUT response contains a list of 1 registered client of given CheckInNodeID, MonitoredSubject, and Key - - [1702438233.117193][2401:2403] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0003 DataVersion: 2633987690 - [1702438233.117356][2401:2403] CHIP:TOO: RegisteredClients: 2 entries - [1702438233.117510][2401:2403] CHIP:TOO: [1]: { - [1702438233.117559][2401:2403] CHIP:TOO: CheckInNodeID: 112233 - [1702438233.117603][2401:2403] CHIP:TOO: MonitoredSubject: 112233 - [1702438233.117669][2401:2403] CHIP:TOO: FabricIndex: 1 - [1702438233.117713][2401:2403] CHIP:TOO: } - [1702438233.117772][2401:2403] CHIP:TOO: [2]: { - [1702438233.117816][2401:2403] CHIP:TOO: CheckInNodeID: 5 - [1702438233.117859][2401:2403] CHIP:TOO: MonitoredSubject: 5 - [1702438233.117902][2401:2403] CHIP:TOO: FabricIndex: 1 - [1702438233.117944][2401:2403] CHIP:TOO: } - disabled: true - - - label: - "Step 6: TH sends UnregisterClient command with the CheckInNodeID from - Step 5a and an invalid VerificationKey. - CheckInNodeID: registering - clients node ID (CheckInNodeID5) - VerificationKey: invalid - verification key (VerificationKey6)" - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 5 1 0 --VerificationKey hex:abcdef1234567890 - - [1703268222.346310][2758:2760] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x1 - [1703268222.346412][2758:2760] CHIP:TOO: Error: IM Error 0x00000501: General error: 0x01 (FAILURE) - disabled: true - - - label: - "Step 7: TH sends UnregisterClient command with the CheckInNodeID from - Step 5a and different VerificationKey. - CheckInNodeID: registering - clients node ID (CheckInNodeID5) - VerificationKey: valid verification - key (VerificationKey7)" - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 5 1 0 --VerificationKey hex:abcdef1234567890abcdef1234500000 - - [1703268200.542869][2755:2757] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x1 - [1703268200.543007][2755:2757] CHIP:TOO: Error: IM Error 0x00000501: General error: 0x01 (FAILURE) - disabled: true - - - label: - "Step 8: TH sends UnregisterClient command with the CheckInNodeID and - VerificationKey from Step 5a. - CheckInNodeID: registering clients - node ID (CheckInNodeID5) - VerificationKey: verification key - (VerificationKey5)" - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 5 1 0 --VerificationKey hex:abcdef1234567890abcdef1234567890 - - [1702438116.143490][2387:2389] CHIP:DMG: InvokeResponseMessage = - [1702438116.143590][2387:2389] CHIP:DMG: { - [1702438116.143648][2387:2389] CHIP:DMG: suppressResponse = false, - [1702438116.143877][2387:2389] CHIP:DMG: InvokeResponseIBs = - [1702438116.144238][2387:2389] CHIP:DMG: [ - [1702438116.144308][2387:2389] CHIP:DMG: InvokeResponseIB = - [1702438116.144414][2387:2389] CHIP:DMG: { - [1702438116.144476][2387:2389] CHIP:DMG: CommandStatusIB = - [1702438116.144575][2387:2389] CHIP:DMG: { - [1702438116.144646][2387:2389] CHIP:DMG: CommandPathIB = - [1702438116.144723][2387:2389] CHIP:DMG: { - [1702438116.144825][2387:2389] CHIP:DMG: EndpointId = 0x0, - [1702438116.144911][2387:2389] CHIP:DMG: ClusterId = 0x46, - [1702438116.144993][2387:2389] CHIP:DMG: CommandId = 0x2, - [1702438116.145097][2387:2389] CHIP:DMG: }, - [1702438116.145186][2387:2389] CHIP:DMG: - [1702438116.145278][2387:2389] CHIP:DMG: StatusIB = - [1702438116.145357][2387:2389] CHIP:DMG: { - [1702438116.145457][2387:2389] CHIP:DMG: status = 0x00 (SUCCESS), - [1702438116.145538][2387:2389] CHIP:DMG: }, - [1702438116.145616][2387:2389] CHIP:DMG: - [1702438116.145683][2387:2389] CHIP:DMG: }, - [1702438116.145782][2387:2389] CHIP:DMG: - [1702438116.145846][2387:2389] CHIP:DMG: }, - [1702438116.145917][2387:2389] CHIP:DMG: - [1702438116.146004][2387:2389] CHIP:DMG: ], - [1702438116.146078][2387:2389] CHIP:DMG: - [1702438116.146155][2387:2389] CHIP:DMG: InteractionModelRevision = 11 - [1702438116.146213][2387:2389] CHIP:DMG: }, - [1702438116.146382][2387:2389] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x0 - disabled: true diff --git a/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml b/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml new file mode 100644 index 00000000000000..dd58b93543dfc7 --- /dev/null +++ b/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml @@ -0,0 +1,83 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "[TC-TBRM-2.1] Attributes with Server as DUT" + +PICS: + - TBRM.S + +config: + nodeId: 0x12344321 + cluster: Thread Border Router Management + endpoint: 1 + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: DelayCommands + command: WaitForCommissionee + arguments: + values: + - name: nodeId + value: nodeId + + - label: "TH reads the BorderRouterName attribute from the DUT" + command: readAttribute + attribute: BorderRouterName + response: + constraints: + type: char_string + hasValue: true + minLength: 1 + maxLength: 63 + + - label: "TH reads the BorderAgentID attribute from the DUT" + command: readAttribute + attribute: BorderAgentID + response: + constraints: + type: octet_string + hasValue: true + minLength: 16 + maxLength: 16 + + - label: "TH reads the ThreadVersion attribute from the DUT" + command: readAttribute + attribute: ThreadVersion + response: + constraints: + type: int16u + hasValue: true + minValue: 4 + + - label: "TH reads the InterfaceEnabled attribute from the DUT" + command: readAttribute + attribute: InterfaceEnabled + response: + constraints: + type: boolean + hasValue: true + + - label: "TH reads the ActiveDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: ActiveDatasetTimestamp + response: + constraints: + type: int64u + # TODO: Attribute missing from cluster XML + # - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + # command: readAttribute + # attribute: PendingDatasetTimestamp + # response: + # constraints: + # type: int64u diff --git a/src/app/tests/suites/manualTests.json b/src/app/tests/suites/manualTests.json index 7b99318fe6c9fd..bbb86f7092b715 100644 --- a/src/app/tests/suites/manualTests.json +++ b/src/app/tests/suites/manualTests.json @@ -119,7 +119,6 @@ "Identify": ["Test_TC_I_3_2"], "IcdManagement": [ "Test_TC_ICDM_3_2", - "Test_TC_ICDM_3_3", "Test_TC_ICDM_4_1", "Test_TC_ICDM_5_1" ], diff --git a/src/app/tests/test-interaction-model-api.cpp b/src/app/tests/test-interaction-model-api.cpp index f626d061c2b107..f483f4b3de88a4 100644 --- a/src/app/tests/test-interaction-model-api.cpp +++ b/src/app/tests/test-interaction-model-api.cpp @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/data-model-provider/ActionReturnStatus.h" #include #include @@ -153,7 +154,7 @@ TestImCustomDataModel & TestImCustomDataModel::Instance() return model; } -CHIP_ERROR TestImCustomDataModel::ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) +ActionReturnStatus TestImCustomDataModel::ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) { AttributeEncodeState mutableState(&encoder.GetState()); // provide a state copy to start. @@ -167,13 +168,13 @@ CHIP_ERROR TestImCustomDataModel::ReadAttribute(const ReadAttributeRequest & req return err; } -CHIP_ERROR TestImCustomDataModel::WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) +ActionReturnStatus TestImCustomDataModel::WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) { return CHIP_ERROR_NOT_IMPLEMENTED; } -CHIP_ERROR TestImCustomDataModel::Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - CommandHandler * handler) +ActionReturnStatus TestImCustomDataModel::Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + CommandHandler * handler) { return CHIP_ERROR_NOT_IMPLEMENTED; } diff --git a/src/app/tests/test-interaction-model-api.h b/src/app/tests/test-interaction-model-api.h index ccaf6514890546..6de6c9c34a2e21 100644 --- a/src/app/tests/test-interaction-model-api.h +++ b/src/app/tests/test-interaction-model-api.h @@ -118,10 +118,12 @@ class TestImCustomDataModel : public DataModel::Provider CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - CHIP_ERROR ReadAttribute(const DataModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; - CHIP_ERROR WriteAttribute(const DataModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; - CHIP_ERROR Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - CommandHandler * handler) override; + DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & encoder) override; + DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request, + AttributeValueDecoder & decoder) override; + DataModel::ActionReturnStatus Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + CommandHandler * handler) override; EndpointId FirstEndpoint() override; EndpointId NextEndpoint(EndpointId before) override; diff --git a/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml b/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml index f0266fffecb872..14f4b9edf39958 100644 --- a/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml +++ b/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml @@ -2428,11 +2428,12 @@ limitations under the License. + MA-thread-border-router - HRAP + CHIP Matter Thread Border Router 0x0103 0x0091 diff --git a/src/app/zap-templates/zcl/data-model/chip/occupancy-sensing-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/occupancy-sensing-cluster.xml index 23d1e5bb3948d6..23746d916cfa0f 100644 --- a/src/app/zap-templates/zcl/data-model/chip/occupancy-sensing-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/occupancy-sensing-cluster.xml @@ -81,7 +81,7 @@ limitations under the License. true true - + Occupancy OccupancySensorType diff --git a/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml index b6ee4385fb73ff..d7565e0262c9da 100644 --- a/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml @@ -23,7 +23,7 @@ limitations under the License. - HRAP + Network Infrastructure Thread Border Router Management 0x0452 THREAD_BORDER_ROUTER_MANAGEMENT_CLUSTER diff --git a/src/controller/TypedReadCallback.h b/src/controller/TypedReadCallback.h index 000b5d51d522ce..f65b4f2168bff7 100644 --- a/src/controller/TypedReadCallback.h +++ b/src/controller/TypedReadCallback.h @@ -43,8 +43,8 @@ namespace Controller { * encapsulate a StatusIB). This could be a path-specific error or it * could be a general error for the entire request; the distinction is not * that important, because we only have one path involved. If the - * CHIP_ERROR encapsulates a StatusIB, StatusIB::InitFromChipError can be - * used to extract the status. + * CHIP_ERROR encapsulates a StatusIB, constructing a StatusIB from it will + * extract the status. */ template class TypedReadAttributeCallback final : public app::ReadClient::Callback diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index ff11fe9e09fe86..66141acdeb7406 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -7571,7 +7571,7 @@ cluster RelativeHumidityMeasurement = 1029 { /** Attributes and commands for configuring occupancy sensing, and reporting occupancy status. */ cluster OccupancySensing = 1030 { - revision 4; + revision 5; enum OccupancySensorTypeEnum : enum8 { kPIR = 0; diff --git a/src/controller/data_model/controller-clusters.zap b/src/controller/data_model/controller-clusters.zap index 466f3947d263c1..c82cc3da84f30c 100644 --- a/src/controller/data_model/controller-clusters.zap +++ b/src/controller/data_model/controller-clusters.zap @@ -4440,7 +4440,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "4", + "defaultValue": "5", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/src/controller/tests/data_model/DataModelFixtures.cpp b/src/controller/tests/data_model/DataModelFixtures.cpp index ff22f2063e6e62..05fa16b956bb0c 100644 --- a/src/controller/tests/data_model/DataModelFixtures.cpp +++ b/src/controller/tests/data_model/DataModelFixtures.cpp @@ -17,6 +17,7 @@ */ #include "DataModelFixtures.h" +#include "app/data-model-provider/ActionReturnStatus.h" #include #include @@ -491,7 +492,7 @@ CustomDataModel & CustomDataModel::Instance() return model; } -CHIP_ERROR CustomDataModel::ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) +ActionReturnStatus CustomDataModel::ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) { AttributeEncodeState mutableState(&encoder.GetState()); // provide a state copy to start. @@ -519,12 +520,13 @@ CHIP_ERROR CustomDataModel::ReadAttribute(const ReadAttributeRequest & request, return err; } -CHIP_ERROR CustomDataModel::WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) +ActionReturnStatus CustomDataModel::WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) { return CHIP_ERROR_NOT_IMPLEMENTED; } -CHIP_ERROR CustomDataModel::Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) +ActionReturnStatus CustomDataModel::Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + CommandHandler * handler) { return CHIP_ERROR_NOT_IMPLEMENTED; } diff --git a/src/controller/tests/data_model/DataModelFixtures.h b/src/controller/tests/data_model/DataModelFixtures.h index a36e905c23ce41..cfb2edf5db83f3 100644 --- a/src/controller/tests/data_model/DataModelFixtures.h +++ b/src/controller/tests/data_model/DataModelFixtures.h @@ -115,10 +115,12 @@ class CustomDataModel : public DataModel::Provider CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - CHIP_ERROR ReadAttribute(const DataModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; - CHIP_ERROR WriteAttribute(const DataModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; - CHIP_ERROR Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - CommandHandler * handler) override; + DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & encoder) override; + DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request, + AttributeValueDecoder & decoder) override; + DataModel::ActionReturnStatus Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + CommandHandler * handler) override; EndpointId FirstEndpoint() override; EndpointId NextEndpoint(EndpointId before) override; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRDeviceTypeMetadata.mm b/src/darwin/Framework/CHIP/zap-generated/MTRDeviceTypeMetadata.mm index aa82714a472572..7bab05ab23eccf 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRDeviceTypeMetadata.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRDeviceTypeMetadata.mm @@ -73,6 +73,7 @@ { 0x0000007B, DeviceTypeClass::Simple, "Matter Oven" }, { 0x0000007C, DeviceTypeClass::Simple, "Matter Laundry Dryer" }, { 0x00000090, DeviceTypeClass::Simple, "Matter Network Infrastructure Manager" }, + { 0x00000091, DeviceTypeClass::Simple, "Matter Thread Border Router" }, { 0x00000100, DeviceTypeClass::Simple, "Matter On/Off Light" }, { 0x00000101, DeviceTypeClass::Simple, "Matter Dimmable Light" }, { 0x00000103, DeviceTypeClass::Simple, "Matter On/Off Light Switch" }, @@ -98,7 +99,6 @@ { 0x00000510, DeviceTypeClass::Utility, "Matter Electrical Sensor" }, { 0x00000840, DeviceTypeClass::Simple, "Matter Control Bridge" }, { 0x00000850, DeviceTypeClass::Simple, "Matter On/Off Sensor" }, - { 0x00000091, DeviceTypeClass::Simple, "Matter Thread Border Router" }, }; static_assert(ExtractVendorFromMEI(0xFFF10001) != 0, "Must have class defined for \"Matter Orphan Clusters\" if it's a standard device type"); diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index 23d83d868ea3df..84b0de1a47f3ba 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -453,6 +453,16 @@ using CHIP_ERROR = ::chip::ChipError; // #define CHIP_IM_CLUSTER_STATUS(type) CHIP_SDK_ERROR(::chip::ChipError::SdkPart::kIMClusterStatus, type) +// Defines a runtime-value for a chip-error that contains a cluster-specific error status. +// Must not be used with cluster-specific success status codes. +#if CHIP_CONFIG_ERROR_SOURCE +#define CHIP_ERROR_IM_CLUSTER_STATUS_VALUE(status_value) \ + ::chip::ChipError(::chip::ChipError::SdkPart::kIMClusterStatus, status_value, __FILE__, __LINE__) +#else +#define CHIP_ERROR_IM_CLUSTER_STATUS_VALUE(status_value) \ + ::chip::ChipError(::chip::ChipError::SdkPart::kIMClusterStatus, status_value) +#endif // CHIP_CONFIG_ERROR_SOURCE + // clang-format off /** diff --git a/src/protocols/interaction_model/StatusCode.cpp b/src/protocols/interaction_model/StatusCode.cpp index 36e1274f4e6c1e..b91817a0290e4a 100644 --- a/src/protocols/interaction_model/StatusCode.cpp +++ b/src/protocols/interaction_model/StatusCode.cpp @@ -37,6 +37,30 @@ const char * StatusName(Status status) return "Unallocated"; } #endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT +// +ClusterStatusCode::ClusterStatusCode(CHIP_ERROR err) +{ + if (err.IsPart(ChipError::SdkPart::kIMClusterStatus)) + { + mStatus = Status::Failure; + mClusterSpecificCode = chip::MakeOptional(err.GetSdkCode()); + return; + } + + if (err == CHIP_NO_ERROR) + { + mStatus = Status::Success; + return; + } + + if (err.IsPart(ChipError::SdkPart::kIMGlobalStatus)) + { + mStatus = static_cast(err.GetSdkCode()); + return; + } + + mStatus = Status::Failure; +} } // namespace InteractionModel } // namespace Protocols diff --git a/src/protocols/interaction_model/StatusCode.h b/src/protocols/interaction_model/StatusCode.h index 9326c86653759c..b30ef95ea2d1e2 100644 --- a/src/protocols/interaction_model/StatusCode.h +++ b/src/protocols/interaction_model/StatusCode.h @@ -68,6 +68,7 @@ class ClusterStatusCode { public: explicit ClusterStatusCode(Status status) : mStatus(status) {} + explicit ClusterStatusCode(CHIP_ERROR err); // We only have simple copyable members, so we should be trivially copyable. ClusterStatusCode(const ClusterStatusCode & other) = default; diff --git a/src/python_testing/TCP_Tests.py b/src/python_testing/TCP_Tests.py index cc2f48c86419d9..6703964d216b99 100644 --- a/src/python_testing/TCP_Tests.py +++ b/src/python_testing/TCP_Tests.py @@ -32,8 +32,9 @@ class TCP_Tests(MatterBaseTest): + # TCP Connection Establishment @async_test_body - async def test_TCPConnectionEstablishment(self): + async def test_TC_SC_8_1(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, @@ -43,8 +44,9 @@ async def test_TCPConnectionEstablishment(self): asserts.assert_equal(device.isSessionOverTCPConnection, True, "Session does not have associated TCP connection") asserts.assert_equal(device.isActiveSession, True, "Large Payload Session should be active over TCP connection") + # Large Payload Session Establishment @async_test_body - async def test_LargePayloadSessionEstablishment(self): + async def test_TC_SC_8_2(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, @@ -53,8 +55,9 @@ async def test_LargePayloadSessionEstablishment(self): asserts.fail("Unable to establish a CASE session over TCP to the device") asserts.assert_equal(device.sessionAllowsLargePayload, True, "Session does not have associated TCP connection") + # Session Inactive After TCP Disconnect @async_test_body - async def test_SessionInactiveAfterTCPDisconnect(self): + async def test_TC_SC_8_3(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, @@ -67,8 +70,9 @@ async def test_SessionInactiveAfterTCPDisconnect(self): asserts.assert_equal(device.isActiveSession, False, "Large Payload Session should not be active after TCP connection closure") + # TCP Connect, Disconnect, Then Connect Again @async_test_body - async def test_TCPConnectDisconnectThenConnectAgain(self): + async def test_TC_SC_8_4(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, @@ -90,8 +94,9 @@ async def test_TCPConnectDisconnectThenConnectAgain(self): asserts.assert_equal(device.isSessionOverTCPConnection, True, "Session does not have associated TCP connection") asserts.assert_equal(device.isActiveSession, True, "Large Payload Session should be active over TCP connection") + # OnOff Cluster Toggle Command Over TCP Session @async_test_body - async def test_OnOffToggleCommandOverTCPSession(self): + async def test_TC_SC_8_5(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, @@ -109,8 +114,9 @@ async def test_OnOffToggleCommandOverTCPSession(self): except InteractionModelError: asserts.fail("Unexpected error returned by DUT") + # WildCard Read Over TCP Session @async_test_body - async def test_WildCardReadOverTCPSession(self): + async def test_TC_SC_8_6(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, @@ -126,8 +132,9 @@ async def test_WildCardReadOverTCPSession(self): except InteractionModelError: asserts.fail("Unexpected error returned by DUT") + # Use TCP Session If Available For MRP Interaction @async_test_body - async def test_UseTCPSessionIfAvailableForMRPInteraction(self): + async def test_TC_SC_8_7(self): try: device = await self.default_controller.GetConnectedDevice(nodeid=self.dut_node_id, allowPASE=False, timeoutMs=1000, diff --git a/src/python_testing/TC_CADMIN_1_9.py b/src/python_testing/TC_CADMIN_1_9.py new file mode 100644 index 00000000000000..f37e3723ed1055 --- /dev/null +++ b/src/python_testing/TC_CADMIN_1_9.py @@ -0,0 +1,139 @@ +# +# Copyright (c) 2024 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# === END CI TEST ARGUMENTS === + +import logging +import random +from time import sleep + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.ChipDeviceCtrl import CommissioningParameters +from chip.exceptions import ChipStackError +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CADMIN_1_9(MatterBaseTest): + async def OpenCommissioningWindow(self) -> CommissioningParameters: + try: + cluster = Clusters.GeneralCommissioning + attribute = cluster.Attributes.BasicCommissioningInfo + duration = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=attribute) + params = await self.th1.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=duration.maxCumulativeFailsafeSeconds, iteration=10000, discriminator=self.discriminator, option=1) + return params + + except Exception as e: + logging.exception('Error running OpenCommissioningWindow %s', e) + asserts.assert_true(False, 'Failed to open commissioning window') + + def steps_TC_CADMIN_1_9(self) -> list[TestStep]: + return [ + TestStep(1, "Commissioning, already done", is_commissioning=True), + TestStep( + 2, "TH1 opens commissioning window on DUT with duration set to value for maxCumulativeFailsafeSeconds"), + TestStep(3, "TH2 attempts to connect 20 times to endpoint with incorrect passcode"), + TestStep(4, "TH2 attempts to connect to endpoint with correct passcode"), + TestStep(5, "TH1 opening Commissioning Window one more time to validate ability to do so"), + TestStep(6, "TH1 revoking Commissioning Window"), + ] + + def generate_unique_random_value(self, value): + while True: + random_value = random.randint(10000000, 99999999) + if random_value != value: + return random_value + + async def CommissionOnNetwork( + self, setup_code: int + ): + ctx = asserts.assert_raises(ChipStackError) + with ctx: + await self.th2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=setup_code, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) + errcode = ctx.exception.chip_error + return errcode + + async def CommissionAttempt( + self, setupPinCode: int, expectedErrCode: int): + + if expectedErrCode == 3: + for cycle in range(20): + logging.info("-----------------Current Iteration {}-------------------------".format(cycle+1)) + setup_code = self.generate_unique_random_value(setupPinCode) + errcode = await self.CommissionOnNetwork(setup_code) + logging.info('Commissioning complete done. Successful? {}, errorcode = {}, cycle={}'.format( + errcode.is_success, errcode, (cycle+1))) + asserts.assert_false(errcode.is_success, 'Commissioning complete did not error as expected') + asserts.assert_true(errcode.sdk_code == expectedErrCode, + 'Unexpected error code returned from CommissioningComplete') + + elif expectedErrCode == 50: + logging.info("-----------------Attempting connection expecting timeout-------------------------") + errcode = await self.CommissionOnNetwork(setupPinCode) + logging.info('Commissioning complete done. Successful? {}, errorcode = {}'.format(errcode.is_success, errcode)) + asserts.assert_false(errcode.is_success, 'Commissioning complete did not error as expected') + asserts.assert_true(errcode.sdk_code == expectedErrCode, 'Unexpected error code returned from CommissioningComplete') + + def pics_TC_CADMIN_1_9(self) -> list[str]: + return ["CADMIN.S"] + + @async_test_body + async def test_TC_CADMIN_1_9(self): + self.step(1) + + # Establishing TH1 and TH2 + self.th1 = self.default_controller + self.discriminator = random.randint(0, 4095) + th2_certificate_authority = self.certificate_authority_manager.NewCertificateAuthority() + th2_fabric_admin = th2_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=self.th1.fabricId + 1) + self.th2 = th2_fabric_admin.NewController(nodeId=2, useTestCommissioner=True) + + self.step(2) + params = await self.OpenCommissioningWindow() + setupPinCode = params.setupPinCode + + self.step(3) + await self.CommissionAttempt(setupPinCode, expectedErrCode=0x03) + # TODO: Found if we don't add sleep time after test completes that we get unexpected error code and response after the 21st iteration. + # Link to Bug Filed: https://github.com/project-chip/connectedhomeip/issues/34383 + sleep(1) + + self.step(4) + await self.CommissionAttempt(setupPinCode, expectedErrCode=0x32) + + self.step(5) + params = await self.OpenCommissioningWindow() + + self.step(6) + revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning() + await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=revokeCmd, timedRequestTimeoutMs=6000) + # The failsafe cleanup is scheduled after the command completes, so give it a bit of time to do that + sleep(1) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CC_10_1.py b/src/python_testing/TC_CC_10_1.py index 3f1e40278bb0c6..dee2dfad442485 100644 --- a/src/python_testing/TC_CC_10_1.py +++ b/src/python_testing/TC_CC_10_1.py @@ -502,7 +502,7 @@ async def test_TC_CC_10_1(self): asserts.assert_equal(ColorLoopDirection, 1, "ColorLoopDirection is not 1") self.step("9e") - if self.pics_guard(self.check_pics("CC.S.C44.Rsp")): + if self.pics_guard(self.check_pics("CC.S.F02")): ColorLoopTime = await self.read_single_attribute_check_success(cluster, attributes.ColorLoopTime) asserts.assert_equal(ColorLoopTime, 5, "ColorLoopTime is not 5") diff --git a/src/python_testing/TC_ICDM_2_1.py b/src/python_testing/TC_ICDM_2_1.py index ac20cf2c001fb0..63b27c64d6d8fa 100644 --- a/src/python_testing/TC_ICDM_2_1.py +++ b/src/python_testing/TC_ICDM_2_1.py @@ -109,6 +109,7 @@ def steps_TC_ICDM_2_1(self) -> list[TestStep]: TestStep( 9, "TH reads from the DUT the UserActiveModeTriggerInstruction attribute"), TestStep(10, "TH reads from the DUT the OperatingMode attribute."), + TestStep(11, "TH reads from the DUT the MaximumCheckInBackoff attribute."), ] return steps @@ -254,8 +255,9 @@ async def test_TC_ICDM_2_1(self): "UserActiveModeTriggerInstruction is not in the correct format for the associated UserActiveModeTriggerHint") if uatHintInstructionDepedentBitmap > 0 and uatHintInstructionDepedentBitmap in kUatColorInstructionBitMask: - # TODO: https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/9194 - asserts.assert_true(False, "Nothing to do for now") + pattern = re.compile(r'^[0-9A-F]{6}$') + asserts.assert_true(pattern.match(userActiveModeTriggerInstruction), + "UserActiveModeTriggerInstruction is not in the correct format for the associated UserActiveModeTriggerHint") else: # Check if the UserActiveModeTriggerInstruction was required asserts.assert_false(uatHintInstructionDepedentBitmap in kUatInstructionMandatoryBitMask, @@ -272,6 +274,16 @@ async def test_TC_ICDM_2_1(self): asserts.assert_less( operatingMode, modes.kUnknownEnumValue, "OperatingMode can only have 0 and 1 as valid values") + self.step(11) + if self.pics_guard(self.check_pics("ICDM.S.A0009")): + maximumCheckInBackOff = await self._read_icdm_attribute_expect_success(attributes.MaximumCheckInBackOff) + + asserts.assert_true(self.is_valid_uint32_value(maximumCheckInBackOff), + "MaximumCheckInBackOff attribute is not a valid uint32.") + asserts.assert_greater_equal(maximumCheckInBackOff, idleModeDuration, + "MaximumCheckInBack attribute is not greater or euqal to the IdleModeDuration") + asserts.assert_less_equal(maximumCheckInBackOff, 64800, + "MaximumCheckInBackOff attribute is greater than maximum value (64800).") if __name__ == "__main__": diff --git a/src/python_testing/TC_ICDM_3_1.py b/src/python_testing/TC_ICDM_3_1.py index 938479a9147f5f..f20e2816723a28 100644 --- a/src/python_testing/TC_ICDM_3_1.py +++ b/src/python_testing/TC_ICDM_3_1.py @@ -72,32 +72,22 @@ def desc_TC_ICDM_3_1(self) -> str: def steps_TC_ICDM_3_1(self) -> list[TestStep]: steps = [ - TestStep("0a", "Commissioning, already done", + TestStep(0, "Commissioning, already done", is_commissioning=True), - TestStep( - "0b", "TH reads from the DUT the RegisteredClients attribute. RegisteredClients is empty."), - TestStep( - "1a", "TH reads from the DUT the ClientsSupportedPerFabric attribute."), - TestStep( - "1b", "TH reads from the DUT the ICDCounter attribute."), + TestStep("1a", "TH reads from the DUT the RegisteredClients attribute. RegisteredClients is empty."), + TestStep("1b", "TH reads from the DUT the ClientsSupportedPerFabric attribute."), + TestStep("1c", "TH reads from the DUT the ICDCounter attribute."), TestStep(2, "TH sends RegisterClient command."), TestStep(3, "TH reads from the DUT the RegisteredClients attribute."), - TestStep( - 4, "If len(RegisteredClients) is less than ClientsSupportedPerFabric, TH repeats RegisterClient command with different CheckInNodeID(s) until the number of entries in RegisteredClients equals ClientsSupportedPerFabric."), + TestStep(4, "If len(RegisteredClients) is less than ClientsSupportedPerFabric, TH repeats RegisterClient command with different CheckInNodeID(s) until the number of entries in RegisteredClients equals ClientsSupportedPerFabric."), TestStep(5, "TH reads from the DUT the RegisteredClients attribute."), - TestStep( - 6, "TH sends RegisterClient command with a different CheckInNodeID."), - TestStep( - 7, "TTH sends UnregisterClient command with the CheckInNodeID from Step 6."), - TestStep( - 8, "TH sends UnregisterClient command with the CheckInNodeID from Step 2."), - TestStep( - 9, "TH reads from the DUT the RegisteredClients attribute."), - TestStep( - 10, "Repeat Step 9 with the rest of CheckInNodeIDs from the list of RegisteredClients from Step 4, if any."), + TestStep(6, "TH sends RegisterClient command with a different CheckInNodeID."), + TestStep(7, "TTH sends UnregisterClient command with the CheckInNodeID from Step 6."), + TestStep(8, "TH sends UnregisterClient command with the CheckInNodeID from Step 2."), + TestStep(9, "TH reads from the DUT the RegisteredClients attribute."), + TestStep(10, "Repeat Step 9 with the rest of CheckInNodeIDs from the list of RegisteredClients from Step 4, if any."), TestStep(11, "TH reads from the DUT the RegisteredClients attribute."), - TestStep( - 12, "TH sends UnregisterClient command with the CheckInNodeID from Step 2."), + TestStep(12, "TH sends UnregisterClient command with the CheckInNodeID from Step 2."), ] return steps @@ -120,10 +110,10 @@ async def test_TC_ICDM_3_1(self): attributes = cluster.Attributes # Pre-Condition: Commissioning - self.step("0a") + self.step(0) - # Pre-Condition: Empty RegisteredClients attribute for all registrations - self.step("0b") + # Empty RegisteredClients attribute for all registrations + self.step("1a") registeredClients = await self._read_icdm_attribute_expect_success( attributes.RegisteredClients) @@ -135,56 +125,51 @@ async def test_TC_ICDM_3_1(self): e.status, Status.Success, "Unexpected error returned") pass - self.step("1a") - if self.pics_guard(self.check_pics("ICDM.S.A0005")): - clientsSupportedPerFabric = await self._read_icdm_attribute_expect_success( - attributes.ClientsSupportedPerFabric) - self.step("1b") - if self.pics_guard(self.check_pics("ICDM.S.A0005")): - icdCounter = await self._read_icdm_attribute_expect_success(attributes.ICDCounter) + clientsSupportedPerFabric = await self._read_icdm_attribute_expect_success( + attributes.ClientsSupportedPerFabric) + + self.step("1c") + icdCounter = await self._read_icdm_attribute_expect_success(attributes.ICDCounter) self.step(2) - if self.pics_guard(self.check_pics("ICDM.S.C00.Rsp")): - try: - response = await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=kStep2CheckInNodeId, monitoredSubject=kStep2MonitoredSubjectStep2, key=kStep2Key, clientType=clientTypeEnum.kEphemeral)) - except InteractionModelError as e: - asserts.assert_equal( - e.status, Status.Success, "Unexpected error returned") - pass + try: + response = await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=kStep2CheckInNodeId, monitoredSubject=kStep2MonitoredSubjectStep2, key=kStep2Key, clientType=clientTypeEnum.kEphemeral)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + pass - # Validate response contains the ICDCounter - asserts.assert_greater_equal(response.ICDCounter, icdCounter, - "The ICDCounter in the response does not match the read ICDCounter.") + # Validate response contains the ICDCounter + asserts.assert_greater_equal(response.ICDCounter, icdCounter, + "The ICDCounter in the response does not match the read ICDCounter.") self.step(3) - if self.pics_guard(self.check_pics("ICDM.S.A0003")): - registeredClients = await self._read_icdm_attribute_expect_success( - attributes.RegisteredClients) - # Validate list size - asserts.assert_equal(len(registeredClients), 1, - "The expected length of RegisteredClients is 1. List has the wrong size.") - - # Validate entry values - asserts.assert_equal( - registeredClients[0].checkInNodeID, kStep2CheckInNodeId, "The read attribute does not match the registered value.") - asserts.assert_equal( - registeredClients[0].monitoredSubject, kStep2MonitoredSubjectStep2, "The read attribute does not match the registered value.") - asserts.assert_equal( - registeredClients[0].clientType, clientTypeEnum.kEphemeral, "The read attribute does not match the registered value.") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + # Validate list size + asserts.assert_equal(len(registeredClients), 1, + "The expected length of RegisteredClients is 1. List has the wrong size.") + + # Validate entry values + asserts.assert_equal( + registeredClients[0].checkInNodeID, kStep2CheckInNodeId, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].monitoredSubject, kStep2MonitoredSubjectStep2, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].clientType, clientTypeEnum.kEphemeral, "The read attribute does not match the registered value.") self.step(4) - if self.pics_guard(self.check_pics("ICDM.S.C00.Rsp")): - if (len(registeredClients) < clientsSupportedPerFabric): - newClients = [] - # Generate new clients data - for i in range(clientsSupportedPerFabric - len(registeredClients)): - newClients.append({ - "checkInNodeID": i + 1, - "monitoredSubject": i + 1, - "key": os.urandom(16), - "clientType": clientTypeEnum.kPermanent - }) + if (len(registeredClients) < clientsSupportedPerFabric): + newClients = [] + # Generate new clients data + for i in range(clientsSupportedPerFabric - len(registeredClients)): + newClients.append({ + "checkInNodeID": i + 1, + "monitoredSubject": i + 1, + "key": os.urandom(16), + "clientType": clientTypeEnum.kPermanent + }) for client in newClients: try: @@ -199,84 +184,76 @@ async def test_TC_ICDM_3_1(self): "The ICDCounter in the response does not match the read ICDCounter.") self.step(5) - if self.pics_guard(self.check_pics("ICDM.S.A0003")): - registeredClients = await self._read_icdm_attribute_expect_success( - attributes.RegisteredClients) + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) - # Validate list size - asserts.assert_equal(len(registeredClients[1:]), len(newClients), - "The expected length of RegisteredClients is clientsSupportedPerFabric. List has the wrong size.") + # Validate list size + asserts.assert_equal(len(registeredClients[1:]), len(newClients), + "The expected length of RegisteredClients is clientsSupportedPerFabric. List has the wrong size.") - for client, expectedClient in zip(registeredClients[1:], newClients): - asserts.assert_equal( - client.checkInNodeID, expectedClient["checkInNodeID"], "The read attribute does not match the registered value.") - asserts.assert_equal( - client.monitoredSubject, expectedClient["monitoredSubject"], "The read attribute does not match the registered value.") - asserts.assert_equal( - client.clientType, expectedClient["clientType"], "The read attribute does not match the registered value.") + for client, expectedClient in zip(registeredClients[1:], newClients): + asserts.assert_equal( + client.checkInNodeID, expectedClient["checkInNodeID"], "The read attribute does not match the registered value.") + asserts.assert_equal( + client.monitoredSubject, expectedClient["monitoredSubject"], "The read attribute does not match the registered value.") + asserts.assert_equal( + client.clientType, expectedClient["clientType"], "The read attribute does not match the registered value.") self.step(6) - if self.pics_guard(self.check_pics("ICDM.S.C00.Rsp")): - try: - response = await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=0xFFFF, monitoredSubject=0xFFFF, key=os.urandom(16), clientType=clientTypeEnum.kPermanent)) - except InteractionModelError as e: - asserts.assert_equal( - e.status, Status.ResourceExhausted, "Unexpected error returned") - pass + try: + response = await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=0xFFFF, monitoredSubject=0xFFFF, key=os.urandom(16), clientType=clientTypeEnum.kPermanent)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.ResourceExhausted, "Unexpected error returned") + pass self.step(7) - if self.pics_guard(self.check_pics("ICDM.S.C02.Rsp")): - try: - await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=0xFFFF)) - except InteractionModelError as e: - asserts.assert_equal( - e.status, Status.NotFound, "Unexpected error returned") - pass + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=0xFFFF)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.NotFound, "Unexpected error returned") + pass self.step(8) - if self.pics_guard(self.check_pics("ICDM.S.C02.Rsp")): - try: - await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=kStep2CheckInNodeId)) - except InteractionModelError as e: - asserts.assert_equal( - e.status, Status.Success, "Unexpected error returned") - pass + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=kStep2CheckInNodeId)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + pass self.step(9) - if self.pics_guard(self.check_pics("ICDM.S.A0003")): - registeredClients = await self._read_icdm_attribute_expect_success( - attributes.RegisteredClients) + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) - for client in registeredClients: - asserts.assert_not_equal(client.checkInNodeID, kStep2CheckInNodeId, - "CheckInNodeID was unregistered. It should not be present in the attribute list.") + for client in registeredClients: + asserts.assert_not_equal(client.checkInNodeID, kStep2CheckInNodeId, + "CheckInNodeID was unregistered. It should not be present in the attribute list.") self.step(10) - if self.pics_guard(self.check_pics("ICDM.S.C02.Rsp")): - for client in newClients: - try: - await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client["checkInNodeID"])) - except InteractionModelError as e: - asserts.assert_equal( - e.status, Status.Success, "Unexpected error returned") - pass + for client in newClients: + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client["checkInNodeID"])) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + pass self.step(11) - if self.pics_guard(self.check_pics("ICDM.S.A0003")): - registeredClients = await self._read_icdm_attribute_expect_success( - attributes.RegisteredClients) + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) - asserts.assert_true( - not registeredClients, "This list should empty. An element did not get deleted.") + asserts.assert_true( + not registeredClients, "This list should empty. An element did not get deleted.") self.step(12) - if self.pics_guard(self.check_pics("ICDM.S.C02.Rsp")): - try: - await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=kStep2CheckInNodeId)) - except InteractionModelError as e: - asserts.assert_equal( - e.status, Status.NotFound, "Unexpected error returned") - pass + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=kStep2CheckInNodeId)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.NotFound, "Unexpected error returned") + pass if __name__ == "__main__": diff --git a/src/python_testing/TC_ICDM_3_3.py b/src/python_testing/TC_ICDM_3_3.py new file mode 100644 index 00000000000000..cb8d1bd7bcbbcd --- /dev/null +++ b/src/python_testing/TC_ICDM_3_3.py @@ -0,0 +1,380 @@ + +# +# 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. +# + +# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments +# for details about the block below. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${LIT_ICD_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +from dataclasses import dataclass + +import chip.clusters as Clusters +from chip.interaction_model import InteractionModelError, Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + +logger = logging.getLogger(__name__) + +kRootEndpointId = 0 + +cluster = Clusters.Objects.IcdManagement +commands = cluster.Commands +ClientTypeEnum = cluster.Enums.ClientTypeEnum + + +@dataclass +class Client: + checkInNodeID: int + subjectId: int + key: bytes + clientType: ClientTypeEnum + + +# +# Test Input Data +# +kIncorrectKey = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1a" +kInvalidKey = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e1g" + +client1 = Client( + checkInNodeID=1, + subjectId=1, + key=b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + clientType=ClientTypeEnum.kEphemeral +) + +client2 = Client( + checkInNodeID=2, + subjectId=2, + key=b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + clientType=ClientTypeEnum.kEphemeral +) + +client3 = Client( + checkInNodeID=3, + subjectId=3, + key=b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + clientType=ClientTypeEnum.kEphemeral +) + +client4 = Client( + checkInNodeID=4, + subjectId=4, + key=b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + clientType=ClientTypeEnum.kEphemeral +) + +# Client 5 skipped in the Test Plan steps +client6 = Client( + checkInNodeID=6, + subjectId=6, + key=b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + clientType=ClientTypeEnum.kEphemeral +) + +# Client 7 skipped in the Test Plan steps +client8 = Client( + checkInNodeID=8, + subjectId=8, + key=b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + clientType=ClientTypeEnum.kEphemeral +) + + +class TC_ICDM_3_3(MatterBaseTest): + + # + # Class Helper functions + # + async def _read_icdm_attribute_expect_success(self, attribute): + return await self.read_single_attribute_check_success(endpoint=kRootEndpointId, cluster=cluster, attribute=attribute) + + async def _send_single_icdm_command(self, command): + return await self.send_single_cmd(command, endpoint=kRootEndpointId) + # + # Test Harness Helpers + # + + def desc_TC_ICDM_3_3(self) -> str: + """Returns a description of this test""" + return "[TC-ICDM-3.3] Register/Unregister Clients with DUT as Server" + + def steps_TC_ICDM_3_3(self) -> list[TestStep]: + steps = [ + TestStep(0, "Commissioning, already done", is_commissioning=True), + TestStep("1a", "TH reads from the DUT the RegisteredClients attribute."), + TestStep("1b", "TH sends UnregisterClient command with CheckInNodeID1, where CheckInNodeID1 can be any random node ID."), + TestStep("2a", "TH sends RegisterClient command."), + TestStep("2b", "TH reads from the DUT the RegisteredClients attribute."), + TestStep(3, "TH sends UnregisterClient command with the CheckInNodeID3."), + TestStep("4a", "TH sends UnregisterClient command with the CheckInNodeID2."), + TestStep("4b", "TH reads from the DUT the RegisteredClients attribute."), + TestStep("5a", "TH sends RegisterClient command."), + TestStep("5b", "TH reads from the DUT the RegisteredClients attribute."), + TestStep("5c", "TH sends UnregisterClient command with the CheckInNodeID4 as in Step 5a and an invalid VerificationKey5."), + TestStep("5d", "TH reads from the DUT the RegisteredClients attribute."), + TestStep("6a", "TH sends RegisterClient command."), + TestStep("6b", "TH reads from the DUT the RegisteredClients attribute."), + TestStep("6c", "TH sends UnregisterClient command with the CheckInNodeID6 as in Step 6a and a wrong VerificationKey7."), + TestStep("6d", "TH reads from the DUT the RegisteredClients attribute."), + TestStep(7, "Set the TH to Manage privilege for ICDM cluster."), + TestStep("8a", "TH sends RegisterClient command."), + TestStep("8b", "TH sends UnregisterClient command with the CheckInNodeID8 from Step 8a and an invalid VerificationKey9."), + TestStep("8c", "TH sends UnregisterClient command with the CheckInNodeID8 from Step 8a and a valid wrong VerificationKey10."), + TestStep("8d", "TH sends UnregisterClient command with the CheckInNodeID8 and VerificationKey8 from Step 8a."), + ] + return steps + + def pics_TC_ICDM_3_3(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + pics = [ + "ICDM.S", + "ICDM.S.F00" + ] + return pics + + # + # ICDM 3.3 Test Body + # + + @async_test_body + async def test_TC_ICDM_3_3(self): + + cluster = Clusters.Objects.IcdManagement + attributes = cluster.Attributes + + # Pre-Condition: Commissioning + self.step(0) + + self.step("1a") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + + for client in registeredClients: + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client.checkInNodeID)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + # Try / Case for the Test Plan post condition + try: + self.step("1b") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client1.checkInNodeID)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.NotFound, "Unexpected error returned") + pass + + self.step("2a") + try: + await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=client2.checkInNodeID, monitoredSubject=client2.subjectId, key=client2.key, clientType=client2.clientType)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("2b") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + # Validate list size + asserts.assert_equal(len(registeredClients), 1, + "The expected length of RegisteredClients is 1. List has the wrong size.") + + # Validate entry values + asserts.assert_equal( + registeredClients[0].checkInNodeID, client2.checkInNodeID, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].monitoredSubject, client2.subjectId, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].clientType, client2.clientType, "The read attribute does not match the registered value.") + + self.step(3) + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client3.checkInNodeID)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.NotFound, "Unexpected error returned") + + self.step("4a") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client2.checkInNodeID)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("4b") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + asserts.assert_equal(len(registeredClients), 0, + "The RegisteredClients list must be empty. List has the wrong size.") + + self.step("5a") + try: + await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=client4.checkInNodeID, monitoredSubject=client4.subjectId, key=client4.key, clientType=client4.clientType)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("5b") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + # Validate list size + asserts.assert_equal(len(registeredClients), 1, + "The expected length of RegisteredClients is 1. List has the wrong size.") + + # Validate entry values + asserts.assert_equal( + registeredClients[0].checkInNodeID, client4.checkInNodeID, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].monitoredSubject, client4.subjectId, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].clientType, client4.clientType, "The read attribute does not match the registered value.") + + self.step("5c") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client4.checkInNodeID, verificationKey=kInvalidKey)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("5d") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + asserts.assert_equal(len(registeredClients), 0, + "The RegisteredClients list must be empty. List has the wrong size.") + + self.step("6a") + try: + await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=client6.checkInNodeID, monitoredSubject=client6.subjectId, key=client6.key, clientType=client6.clientType)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("6b") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + # Validate list size + asserts.assert_equal(len(registeredClients), 1, + "The expected length of RegisteredClients is 1. List has the wrong size.") + + # Validate entry values + asserts.assert_equal( + registeredClients[0].checkInNodeID, client6.checkInNodeID, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].monitoredSubject, client6.subjectId, "The read attribute does not match the registered value.") + asserts.assert_equal( + registeredClients[0].clientType, client6.clientType, "The read attribute does not match the registered value.") + + self.step("6c") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client6.checkInNodeID, verificationKey=kIncorrectKey)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("6d") + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + asserts.assert_equal(len(registeredClients), 0, + "The RegisteredClients list must be empty. List has the wrong size.") + self.step(7) + ac = Clusters.AccessControl + previousAcl = await self.read_single_attribute_check_success(cluster=ac, attribute=ac.Attributes.Acl) + newAcls = [] + + # Set Admin permissions on Access Control cluster + newAclEntry = ac.Structs.AccessControlEntryStruct(privilege=ac.Enums.AccessControlEntryPrivilegeEnum.kAdminister, + authMode=ac.Enums.AccessControlEntryAuthModeEnum.kCase, + subjects=previousAcl[0].subjects, targets=[ac.Structs.AccessControlTargetStruct( + cluster=Clusters.AccessControl.id)], fabricIndex=previousAcl[0].fabricIndex + ) + newAcls.append(newAclEntry) + + # Set Manage permissions on ICD Management cluster + newAclEntry = ac.Structs.AccessControlEntryStruct(privilege=ac.Enums.AccessControlEntryPrivilegeEnum.kManage, + authMode=ac.Enums.AccessControlEntryAuthModeEnum.kCase, + subjects=previousAcl[0].subjects, targets=[ac.Structs.AccessControlTargetStruct( + cluster=Clusters.IcdManagement.id)], fabricIndex=previousAcl[0].fabricIndex + ) + newAcls.append(newAclEntry) + + try: + await self.default_controller.WriteAttribute(nodeid=self.dut_node_id, attributes=[(0, ac.Attributes.Acl(newAcls))]) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("8a") + try: + await self._send_single_icdm_command(commands.RegisterClient(checkInNodeID=client8.checkInNodeID, monitoredSubject=client8.subjectId, key=client8.key, clientType=client8.clientType)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + self.step("8b") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client8.checkInNodeID, verificationKey=kInvalidKey)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Failure, "Unexpected error returned") + + self.step("8c") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client8.checkInNodeID, verificationKey=kIncorrectKey)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Failure, "Unexpected error returned") + self.step("8d") + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client8.checkInNodeID, verificationKey=client8.key)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + # Post-Condition steps + finally: + # Reset ACLs + try: + await self.default_controller.WriteAttribute(nodeid=self.dut_node_id, attributes=[(0, ac.Attributes.Acl(previousAcl))]) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + # Clear all RegisteredClients + registeredClients = await self._read_icdm_attribute_expect_success( + attributes.RegisteredClients) + + for client in registeredClients: + try: + await self._send_single_icdm_command(commands.UnregisterClient(checkInNodeID=client.checkInNodeID)) + except InteractionModelError as e: + asserts.assert_equal( + e.status, Status.Success, "Unexpected error returned") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_OCC_3_1.py b/src/python_testing/TC_OCC_3_1.py index 4380866333f339..3fd082a62bc974 100644 --- a/src/python_testing/TC_OCC_3_1.py +++ b/src/python_testing/TC_OCC_3_1.py @@ -16,11 +16,11 @@ # # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: run1 -# test-runner-run/run1/app: ${TYPE_OF_APP} +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --endpoint 1 # === END CI TEST ARGUMENTS === # There are CI issues to be followed up for the test cases below that implements manually controlling sensor device for # the occupancy state ON/OFF change. @@ -29,19 +29,29 @@ import logging import time +from typing import Any, Optional import chip.clusters as Clusters -from chip import ChipDeviceCtrl from chip.interaction_model import Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts class TC_OCC_3_1(MatterBaseTest): - async def read_occ_attribute_expect_success(self, endpoint, attribute): + async def read_occ_attribute_expect_success(self, attribute): cluster = Clusters.Objects.OccupancySensing + endpoint = self.matter_test_config.endpoint return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + async def write_hold_time(self, hold_time: Optional[Any]) -> Status: + dev_ctrl = self.default_controller + node_id = self.dut_node_id + endpoint = self.matter_test_config.endpoint + + cluster = Clusters.OccupancySensing + write_result = await dev_ctrl.WriteAttribute(node_id, [(endpoint, cluster.Attributes.HoldTime(hold_time))]) + return write_result[0].Status + def desc_TC_OCC_3_1(self) -> str: return "[TC-OCC-3.1] Primary functionality with server as DUT" @@ -63,39 +73,37 @@ def pics_TC_OCC_3_1(self) -> list[str]: @async_test_body async def test_TC_OCC_3_1(self): - - endpoint = self.user_params.get("endpoint", 1) - node_id = self.matter_test_config.dut_node_ids[0] hold_time = 10 # 10 seconds for occupancy state hold time self.step(1) # commissioning and getting cluster attribute list - attributes = Clusters.OccupancySensing.Attributes - attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + cluster = Clusters.OccupancySensing + attributes = cluster.Attributes + attribute_list = await self.read_occ_attribute_expect_success(attribute=attributes.AttributeList) - self.step(2) - if attributes.HoldTime.attribute_id in attribute_list: - # write 10 as a HoldTime attibute - write_res = await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.HoldTime(hold_time))]) - asserts.assert_equal(write_res[0].status, Status.Success, "Write HoldTime failed") + has_hold_time = attributes.HoldTime.attribute_id in attribute_list + self.step(2) + if has_hold_time: + # write 10 as a HoldTime attribute + await self.write_single_attribute(cluster.Attributes.HoldTime(hold_time)) else: logging.info("No HoldTime attribute supports. Will test only occupancy attribute triggering functionality") self.step(3) # check if Occupancy attribute is 0 - occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + occupancy_dut = await self.read_occ_attribute_expect_success(attribute=attributes.Occupancy) # if occupancy is on, then wait until the sensor occupancy state is 0. if occupancy_dut == 1: # Don't trigger occupancy sensor to render occupancy attribute to 0 - if attributes.HoldTime.attribute_id in attribute_list: - time.sleep(hold_time + 2) # add some extra 2 seconds to ensure hold time has passed. + if has_hold_time: + time.sleep(hold_time + 2.0) # add some extra 2 seconds to ensure hold time has passed. else: # a user wait until a sensor specific time to change occupancy attribute to 0. This is the case where the sensor doesn't support HoldTime. self.wait_for_user_input( prompt_msg="Type any letter and press ENTER after the sensor occupancy is detection ready state (occupancy attribute = 0)") # check sensor occupancy state is 0 for the next test step - occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + occupancy_dut = await self.read_occ_attribute_expect_success(attribute=attributes.Occupancy) asserts.assert_equal(occupancy_dut, 0, "Occupancy attribute is still 1.") self.step(4) @@ -103,18 +111,18 @@ async def test_TC_OCC_3_1(self): self.wait_for_user_input(prompt_msg="Type any letter and press ENTER after a sensor occupancy is triggered.") # And then check if Occupancy attribute has changed. - occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + occupancy_dut = await self.read_occ_attribute_expect_success(attribute=attributes.Occupancy) asserts.assert_equal(occupancy_dut, 1, "Occupancy state is not changed to 1") self.step(5) # check if Occupancy attribute is back to 0 after HoldTime attribute period # Tester should not be triggering the sensor for this test step. - if attributes.HoldTime.attribute_id in attribute_list: + if has_hold_time: # Start a timer based on HoldTime - time.sleep(hold_time+2) # add some extra 2 seconds to ensure hold time has passed. + time.sleep(hold_time + 2.0) # add some extra 2 seconds to ensure hold time has passed. - occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + occupancy_dut = await self.read_occ_attribute_expect_success(attribute=attributes.Occupancy) asserts.assert_equal(occupancy_dut, 0, "Occupancy state is not 0 after HoldTime period") else: diff --git a/src/python_testing/TC_OCC_3_2.py b/src/python_testing/TC_OCC_3_2.py index 3624f1044c40e8..7e811362e2e2a4 100644 --- a/src/python_testing/TC_OCC_3_2.py +++ b/src/python_testing/TC_OCC_3_2.py @@ -19,72 +19,31 @@ # # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: run1 -# test-runner-run/run1/app: ${TYPE_OF_APP} +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --endpoint 1 # === END CI TEST ARGUMENTS === + # TODO: There are CI issues to be followed up for the test cases below that implements manually controlling sensor device for # the occupancy state ON/OFF change. # [TC-OCC-3.1] test procedure step 4 # [TC-OCC-3.2] test precedure step 3a, 3c import logging -import queue -import time -from typing import Any import chip.clusters as Clusters -from chip import ChipDeviceCtrl -from chip.clusters.Attribute import TypedAttributePath -from matter_testing_support import (AttributeValue, ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, async_test_body, - default_matter_test_main) +from matter_testing_support import (ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, async_test_body, + await_sequence_of_reports, default_matter_test_main) from mobly import asserts class TC_OCC_3_2(MatterBaseTest): - async def read_occ_attribute_expect_success(self, endpoint, attribute): + async def read_occ_attribute_expect_success(self, attribute): cluster = Clusters.Objects.OccupancySensing - return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) - - def _await_sequence_of_reports(self, report_queue: queue.Queue, endpoint_id: int, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float): - start_time = time.time() - elapsed = 0.0 - time_remaining = timeout_sec - - sequence_idx = 0 - actual_values = [] - - while time_remaining > 0: - expected_value = sequence[sequence_idx] - logging.info(f"Expecting value {expected_value} for attribute {attribute} on endpoint {endpoint_id}") - try: - item: AttributeValue = report_queue.get(block=True, timeout=time_remaining) - - # Track arrival of all values for the given attribute. - if item.endpoint_id == endpoint_id and item.attribute == attribute: - actual_values.append(item.value) - - if item.value == expected_value: - logging.info(f"Got expected attribute change {sequence_idx+1}/{len(sequence)} for attribute {attribute}") - sequence_idx += 1 - else: - asserts.assert_equal(item.value, expected_value, - msg="Did not get expected attribute value in correct sequence.") - - # We are done waiting when we have accumulated all results. - if sequence_idx == len(sequence): - logging.info("Got all attribute changes, done waiting.") - return - except queue.Empty: - # No error, we update timeouts and keep going - pass - - elapsed = time.time() - start_time - time_remaining = timeout_sec - elapsed - - asserts.fail(f"Did not get full sequence {sequence} in {timeout_sec:.1f} seconds. Got {actual_values} before time-out.") + endpoint_id = self.matter_test_config.endpoint + return await self.read_single_attribute_check_success(endpoint=endpoint_id, cluster=cluster, attribute=attribute) def desc_TC_OCC_3_2(self) -> str: return "[TC-OCC-3.2] Subscription Report Verification with server as DUT" @@ -124,22 +83,32 @@ def pics_TC_OCC_3_2(self) -> list[str]: @async_test_body async def test_TC_OCC_3_2(self): - - endpoint = self.user_params.get("endpoint", 1) endpoint_id = self.matter_test_config.endpoint - node_id = self.matter_test_config.dut_node_ids[0] + node_id = self.dut_node_id + dev_ctrl = self.default_controller + post_prompt_settle_delay_seconds = 10.0 cluster = Clusters.Objects.OccupancySensing - attributes = Clusters.OccupancySensing.Attributes - occupancy_sensor_type_bitmap_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorTypeBitmap) + attributes = cluster.Attributes self.step(1) - attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + + occupancy_sensor_type_bitmap_dut = await self.read_occ_attribute_expect_success(attribute=attributes.OccupancySensorTypeBitmap) + has_type_pir = ((occupancy_sensor_type_bitmap_dut & cluster.Enums.OccupancySensorTypeEnum.kPir) != 0) or \ + ((occupancy_sensor_type_bitmap_dut & cluster.Enums.OccupancySensorTypeEnum.kPIRAndUltrasonic) != 0) + has_type_ultrasonic = ((occupancy_sensor_type_bitmap_dut & cluster.Enums.OccupancySensorTypeEnum.kUltrasonic) != 0) or \ + ((occupancy_sensor_type_bitmap_dut & cluster.Enums.OccupancySensorTypeEnum.kPIRAndUltrasonic) != 0) + has_type_contact = (occupancy_sensor_type_bitmap_dut & cluster.Enums.OccupancySensorTypeEnum.kPhysicalContact) != 0 + + attribute_list = await self.read_occ_attribute_expect_success(attribute=attributes.AttributeList) + has_pir_timing_attrib = attributes.PIROccupiedToUnoccupiedDelay.attribute_id in attribute_list + has_ultrasonic_timing_attrib = attributes.UltrasonicOccupiedToUnoccupiedDelay.attribute_id in attribute_list + has_contact_timing_attrib = attributes.PhysicalContactOccupiedToUnoccupiedDelay.attribute_id in attribute_list self.step(2) # min interval = 0, and max interval = 30 seconds attrib_listener = ClusterAttributeChangeAccumulator(Clusters.Objects.OccupancySensing) - await attrib_listener.start(ChipDeviceCtrl, node_id, endpoint=endpoint_id) + await attrib_listener.start(dev_ctrl, node_id, endpoint=endpoint_id, min_interval_sec=0, max_interval_sec=30) # TODO - Will add Namepiped to assimilate the manual sensor untrigger here self.step("3a") @@ -147,7 +116,7 @@ async def test_TC_OCC_3_2(self): self.step("3b") if attributes.Occupancy.attribute_id in attribute_list: - initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + initial_dut = await self.read_occ_attribute_expect_success(attribute=attributes.Occupancy) asserts.assert_equal(initial_dut, 0, "Occupancy attribute is still detected state") # TODO - Will add Namepiped to assimilate the manual sensor trigger here @@ -156,8 +125,8 @@ async def test_TC_OCC_3_2(self): prompt_msg="Type any letter and press ENTER after the sensor occupancy is triggered and its occupancy state changed.") self.step("3d") - self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.Occupancy, sequence=[ - 0, 1], timeout_sec=post_prompt_settle_delay_seconds) + await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.Occupancy, sequence=[ + 0, 1], timeout_sec=post_prompt_settle_delay_seconds) self.step("4a") if attributes.HoldTime.attribute_id not in attribute_list: @@ -165,83 +134,73 @@ async def test_TC_OCC_3_2(self): self.skip_all_remaining_steps("4b") self.step("4b") - initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.HoldTime) + initial_dut = await self.read_occ_attribute_expect_success(attribute=attributes.HoldTime) self.step("4c") - # write a different a HoldTime attibute + # write a different a HoldTime attribute value diff_val = 12 - await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.HoldTime(diff_val))]) + await self.write_single_attribute(attributes.HoldTime(diff_val)) self.step("4d") - self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.HoldTime, sequence=[ - initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.HoldTime, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) self.step("5a") - if (occupancy_sensor_type_bitmap_dut & Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPir) == 0: - logging.info("No PIR timing attribute supports. Skip this test cases, 5b, 5c, 5d") + if not has_type_pir or not has_pir_timing_attrib: + logging.info("No PIR timing attribute support. Skip this steps 5b, 5c, 5d") self.skip_step("5b") - self.skip_test("5c") + self.skip_step("5c") self.skip_step("5d") else: self.step("5b") - if attributes.PIROccupiedToUnoccupiedDelay.attribute_id in attribute_list: - initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIROccupiedToUnoccupiedDelay) - - else: - logging.info("No PIROccupiedToUnoccupiedDelay attribute supports. Terminate this test case") + initial_dut = await self.read_occ_attribute_expect_success(attribute=attributes.PIROccupiedToUnoccupiedDelay) self.step("5c") # write the new attribute value diff_val = 11 - await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.PIROccupiedToUnoccupiedDelay(diff_val))]) + await self.write_single_attribute(attributes.PIROccupiedToUnoccupiedDelay(diff_val)) self.step("5d") - self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.PIROccupiedToUnoccupiedDelay, sequence=[ - initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.PIROccupiedToUnoccupiedDelay, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) self.step("6a") - if (occupancy_sensor_type_bitmap_dut & Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kUltrasonic) == 0: - logging.info("No Ultrasonic timing attribute supports. Skip this test cases, 6b, 6c, 6d") + if not has_type_ultrasonic or not has_ultrasonic_timing_attrib: + logging.info("No Ultrasonic timing attribute supports. Skip steps 6b, 6c, 6d") self.skip_step("6b") - self.skip_test("6c") + self.skip_step("6c") self.skip_step("6d") else: self.step("6b") - if attributes.UltrasonicOccupiedToUnoccupiedDelay.attribute_id in attribute_list: - initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.UltrasonicOccupiedToUnoccupiedDelay) - - else: - logging.info("No UltrasonicOccupiedToUnoccupiedDelay attribute supports. Skip this test case") + initial_dut = await self.read_occ_attribute_expect_success(attribute=attributes.UltrasonicOccupiedToUnoccupiedDelay) self.step("6c") # write the new attribute value diff_val = 14 - await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.UltrasonicOccupiedToUnoccupiedDelay(diff_val))]) + await self.write_single_attribute(attributes.UltrasonicOccupiedToUnoccupiedDelay(diff_val)) self.step("6d") - self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.UltrasonicOccupiedToUnoccupiedDelay, sequence=[ - initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.UltrasonicOccupiedToUnoccupiedDelay, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) self.step("7a") - if (occupancy_sensor_type_bitmap_dut & Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPhysicalContact) == 0: + if not has_type_contact or not has_contact_timing_attrib: logging.info("No Physical contact timing attribute supports. Skip this test case") - self.skip_all_remaining_steps("7b") - - self.step("7b") - if attributes.PhysicalContactOccupiedToUnoccupiedDelay.attribute_id in attribute_list: - initial_dut = await self.t_success(endpoint=endpoint, attribute=attributes.PhysicalContactOccupiedToUnoccupiedDelay) - + self.skip_step("7b") + self.skip_step("7c") + self.skip_step("7d") else: - logging.info("No PhysicalContactOccupiedToUnoccupiedDelay attribute supports. Skip this test case") + self.step("7b") + initial_dut = await self.read_occ_attribute_expect_success(attribute=attributes.PhysicalContactOccupiedToUnoccupiedDelay) - self.step("7c") - # write the new attribute value - diff_val = 9 - await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.PhysicalContactOccupiedToUnoccupiedDelay(diff_val))]) + self.step("7c") + # write the new attribute value + diff_val = 9 + await self.write_single_attribute(attributes.PhysicalContactOccupiedToUnoccupiedDelay(diff_val)) - self.step("7d") - self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.PhysicalContactOccupiedToUnoccupiedDelay, sequence=[ - initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + self.step("7d") + await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.PhysicalContactOccupiedToUnoccupiedDelay, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) if __name__ == "__main__": diff --git a/src/python_testing/TC_SWTCH.py b/src/python_testing/TC_SWTCH.py index 08bf23d91c30d4..e2ef307973003b 100644 --- a/src/python_testing/TC_SWTCH.py +++ b/src/python_testing/TC_SWTCH.py @@ -36,10 +36,10 @@ import chip.clusters as Clusters import test_plan_support from chip.clusters import ClusterObjects as ClusterObjects -from chip.clusters.Attribute import EventReadResult, TypedAttributePath +from chip.clusters.Attribute import EventReadResult from chip.tlv import uint -from matter_testing_support import (AttributeValue, ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest, - TestStep, default_matter_test_main, has_feature, per_endpoint_test) +from matter_testing_support import (ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest, TestStep, + await_sequence_of_reports, default_matter_test_main, has_feature, per_endpoint_test) from mobly import asserts logger = logging.getLogger(__name__) @@ -170,44 +170,6 @@ def _ask_for_release(self): else: time.sleep(self.keep_pressed_delay/1000) - def _await_sequence_of_reports(self, report_queue: queue.Queue, endpoint_id: int, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float): - start_time = time.time() - elapsed = 0.0 - time_remaining = timeout_sec - - sequence_idx = 0 - actual_values = [] - - while time_remaining > 0: - expected_value = sequence[sequence_idx] - logging.info(f"Expecting value {expected_value} for attribute {attribute} on endpoint {endpoint_id}") - try: - item: AttributeValue = report_queue.get(block=True, timeout=time_remaining) - - # Track arrival of all values for the given attribute. - if item.endpoint_id == endpoint_id and item.attribute == attribute: - actual_values.append(item.value) - - if item.value == expected_value: - logging.info(f"Got expected attribute change {sequence_idx+1}/{len(sequence)} for attribute {attribute}") - sequence_idx += 1 - else: - asserts.assert_equal(item.value, expected_value, - msg="Did not get expected attribute value in correct sequence.") - - # We are done waiting when we have accumulated all results. - if sequence_idx == len(sequence): - logging.info("Got all attribute changes, done waiting.") - return - except queue.Empty: - # No error, we update timeouts and keep going - pass - - elapsed = time.time() - start_time - time_remaining = timeout_sec - elapsed - - asserts.fail(f"Did not get full sequence {sequence} in {timeout_sec:.1f} seconds. Got {actual_values} before time-out.") - def _await_sequence_of_events(self, event_queue: queue.Queue, endpoint_id: int, sequence: list[ClusterObjects.ClusterEvent], timeout_sec: float): start_time = time.time() elapsed = 0.0 @@ -511,8 +473,8 @@ async def test_TC_SWTCH_2_4(self): # - TH expects report of CurrentPosition 1, followed by a report of Current Position 0. logging.info( f"Starting to wait for {post_prompt_settle_delay_seconds:.1f} seconds for CurrentPosition to go {switch_pressed_position}, then 0.") - self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.CurrentPosition, sequence=[ - switch_pressed_position, 0], timeout_sec=post_prompt_settle_delay_seconds) + await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.CurrentPosition, sequence=[ + switch_pressed_position, 0], timeout_sec=post_prompt_settle_delay_seconds) # - TH expects at least InitialPress with NewPosition = 1 logging.info(f"Starting to wait for {post_prompt_settle_delay_seconds:.1f} seconds for InitialPress event.") diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 273ffe94b5f821..8275b8f0f83326 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -27,6 +27,7 @@ import random import re import sys +import time import typing import uuid from binascii import hexlify, unhexlify @@ -331,6 +332,58 @@ def wait_for_report(self): asserts.fail("[AttributeChangeCallback] Attribute {expected_attribute} not found in returned report") +def await_sequence_of_reports(report_queue: queue.Queue, endpoint_id: int, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float): + """Given a queue.Queue hooked-up to an attribute change accumulator, await a given expected sequence of attribute reports. + + Args: + - report_queue: the queue that receives all the reports. + - endpoint_id: endpoint ID to match for reports to check. + - attribute: attribute to match for reports to check. + - sequence: list of attribute values in order that are expected. + - timeout_sec: number of seconds to wait for. + + This will fail current Mobly test with assertion failure if the data is not as expected in order. + + Returns nothing on success so the test can go on. + """ + start_time = time.time() + elapsed = 0.0 + time_remaining = timeout_sec + + sequence_idx = 0 + actual_values = [] + + while time_remaining > 0: + expected_value = sequence[sequence_idx] + logging.info(f"Expecting value {expected_value} for attribute {attribute} on endpoint {endpoint_id}") + try: + item: AttributeValue = report_queue.get(block=True, timeout=time_remaining) + + # Track arrival of all values for the given attribute. + if item.endpoint_id == endpoint_id and item.attribute == attribute: + actual_values.append(item.value) + + if item.value == expected_value: + logging.info(f"Got expected attribute change {sequence_idx+1}/{len(sequence)} for attribute {attribute}") + sequence_idx += 1 + else: + asserts.assert_equal(item.value, expected_value, + msg="Did not get expected attribute value in correct sequence.") + + # We are done waiting when we have accumulated all results. + if sequence_idx == len(sequence): + logging.info("Got all attribute changes, done waiting.") + return + except queue.Empty: + # No error, we update timeouts and keep going + pass + + elapsed = time.time() - start_time + time_remaining = timeout_sec - elapsed + + asserts.fail(f"Did not get full sequence {sequence} in {timeout_sec:.1f} seconds. Got {actual_values} before time-out.") + + @dataclass class AttributeValue: endpoint_id: int @@ -360,7 +413,7 @@ async def start(self, dev_ctrl, node_id: int, endpoint: int, fabric_filtered: bo self._subscription = await dev_ctrl.ReadAttribute( nodeid=node_id, attributes=[(endpoint, self._expected_cluster)], - reportInterval=(min_interval_sec, max_interval_sec), + reportInterval=(int(min_interval_sec), int(max_interval_sec)), fabricFiltered=fabric_filtered, keepSubscriptions=True ) @@ -977,6 +1030,25 @@ async def read_single_attribute_expect_error( return attr_ret + async def write_single_attribute(self, attribute_value: object, endpoint_id: int = None, expect_success: bool = True) -> Status: + """Write a single `attribute_value` on a given `endpoint_id` and assert on failure. + + If `endpoint_id` is None, the default DUT endpoint for the test is selected. + + If `expect_success` is True, a test assertion fails on error status codes + + Status code is returned. + """ + dev_ctrl = self.default_controller + node_id = self.dut_node_id + endpoint = self.matter_test_config.endpoint if endpoint_id is None else endpoint_id + + write_result = await dev_ctrl.WriteAttribute(node_id, [(endpoint, attribute_value)]) + if expect_success: + asserts.assert_equal(write_result[0].Status, Status.Success, + f"Expected write success for write to attribute {attribute_value} on endpoint {endpoint}") + return write_result[0].Status + async def send_single_cmd( self, cmd: Clusters.ClusterObjects.ClusterCommand, dev_ctrl: ChipDeviceCtrl = None, node_id: int = None, endpoint: int = None, diff --git a/src/python_testing/test_testing/test_TC_ICDM_2_1.py b/src/python_testing/test_testing/test_TC_ICDM_2_1.py index 5069eb6ed9d0a7..a337b91ccef41d 100755 --- a/src/python_testing/test_testing/test_TC_ICDM_2_1.py +++ b/src/python_testing/test_testing/test_TC_ICDM_2_1.py @@ -41,6 +41,7 @@ class ICDMData(): UserActiveModeTriggerHint: int UserActiveModeTriggerInstruction: string OperatingMode: c.Enums.OperatingModeEnum + MaximumCheckInBackOff: int expect_pass: bool @@ -57,145 +58,161 @@ class ICDMData(): # -------- # IdleModeDuration under minimum (< 1) ICDMData(0, 0, 0, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, False), + c.Enums.OperatingModeEnum.kSit, 64800, False), # IdleModeDuration at minimum ICDMData(0, 1, 0, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, True), + c.Enums.OperatingModeEnum.kSit, 64800, True), # IdleModeDuration at maximum ICDMData(0, 64800, 100, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, True), + c.Enums.OperatingModeEnum.kSit, 64800, True), # IdleModeDuration over maximum (>64800) ICDMData(0, 64801, 100, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, False), + c.Enums.OperatingModeEnum.kSit, 64800, False), # IdleModeDuration < ActiveModeDuration ICDMData(0, 1, 1001, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, False), + c.Enums.OperatingModeEnum.kSit, 64800, False), # -------- # Test cases to validate ActiveModeDuration # -------- # ActiveModeDuration under minimum ICDMData(0, 100, -1, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, False), + c.Enums.OperatingModeEnum.kSit, 64800, False), # ActiveModeDuration at minimum ICDMData(0, 100, 0, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, True), + c.Enums.OperatingModeEnum.kSit, 64800, True), # ActiveModeDuration at maximum - value is max IdleModeDuration value - 1 ICDMData(0, 64800, 0x3DCC4FF, 100, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, True), + c.Enums.OperatingModeEnum.kSit, 64800, True), # -------- # Test cases to validate ActiveModeThreshold # -------- # ActiveModeThreshold < minimum ICDMData(0, 1, 0, -1, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, False), + c.Enums.OperatingModeEnum.kSit, 64800, False), # ActiveModeThreshold at SIT minimum ICDMData(0, 1, 0, 0, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, True), + c.Enums.OperatingModeEnum.kSit, 64800, True), # ActiveModeThreshold under LIT minimum ICDMData(0x7, 1, 0, 4999, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # ActiveModeThreshold at LIT minimum ICDMData(0x7, 1, 0, 5000, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # ActiveModeThreshold at Maximum ICDMData(0, 1, 0, 0xFFFF, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, True), + c.Enums.OperatingModeEnum.kSit, 64800, True), # ActiveModeThreshold over Maximum ICDMData(0, 1, 0, 0x10000, [], 0, 2, 0, "", - c.Enums.OperatingModeEnum.kSit, False), + c.Enums.OperatingModeEnum.kSit, 64800, False), # -------- # Test cases to validate ClientsSupportedPerFabric # -------- # ClientsSupportedPerFabric under minimum (< 1) ICDMData(0, 1, 0, 100, [], 0, 0, 0, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # ClientsSupportedPerFabric at minimum ICDMData(0, 1, 0, 100, [], 0, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # ClientsSupportedPerFabric at maximum ICDMData(0, 1, 0, 100, [], 0, 255, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # ClientsSupportedPerFabric > maximum ICDMData(0, 1, 0, 100, [], 0, 256, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # -------- # Test cases to validate RegisteredClients # -------- # Incorrect type ICDMData(0, 1, 0, 100, 0, 0, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # Correct type ICDMData(0, 1, 0, 100, [], 0, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # -------- # Test cases to validate ICDCounter # -------- # ICDCounter under minimum (< 0) ICDMData(0, 1, 0, 100, [], -1, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # ICDCounter at minimum ICDMData(0, 1, 0, 100, [], 0, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # ICDCounter at maximum ICDMData(0, 1, 0, 100, [], 0xFFFFFFFF, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # ICDCounter over maximum ICDMData(0, 1, 0, 100, [], 0x100000000, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # -------- # Test cases to validate UserActiveModeTriggerHint # -------- # UserActiveModeTriggerHint outsite valid range ICDMData(0, 1, 0, 100, [], 0, 1, 0x1FFFF, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # UserActiveModeTriggerHint outsite valid range ICDMData(0, 1, 0, 100, [], 0, 1, -1, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # UserActiveModeTriggerHint with no hints ICDMData(0, 1, 0, 100, [], 0, 1, 0, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # UserActiveModeTriggerHint wiht two instruction depedent bits set ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction | uat.kActuateSensorSeconds, "", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # -------- # Test cases to validate UserActiveModeTriggerInstruction # -------- # UserActiveModeTriggerInstruction with wrong encoding ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, "Hello\uD83D\uDE00World", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # UserActiveModeTriggerInstruction with empty string ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # UserActiveModeTriggerInstruction with empty string ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, "", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # UserActiveModeTriggerInstruction with max string length ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, long_string, - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # UserActiveModeTriggerInstruction > max string length ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, too_long_string, - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # UserActiveModeTriggerInstruction invalid number - Trailing 0s ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "001", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # UserActiveModeTriggerInstruction invalid number - Letters ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "not a number", - c.Enums.OperatingModeEnum.kLit, False), + c.Enums.OperatingModeEnum.kLit, 64800, False), # UserActiveModeTriggerInstruction Valid number ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorLightsBlink, "", c.Enums.OperatingModeEnum.kLit, 64800, False), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorLightsBlink, "AAAAAAA", c.Enums.OperatingModeEnum.kLit, 64800, False), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorLightsBlink, "AAAAA", c.Enums.OperatingModeEnum.kLit, 64800, False), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorLightsBlink, "AAAAAK", c.Enums.OperatingModeEnum.kLit, 64800, False), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorLightsBlink, "012345", c.Enums.OperatingModeEnum.kLit, 64800, True), # -------- # Test cases to validate OpertingMode # -------- # OpertingMode with negative value ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", - -1, False), + -1, 64800, False), # OpertingMode with Accepted value ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", - c.Enums.OperatingModeEnum.kLit, True), + c.Enums.OperatingModeEnum.kLit, 64800, True), # OpertingMode with unkown value ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", - c.Enums.OperatingModeEnum.kUnknownEnumValue, False), + c.Enums.OperatingModeEnum.kUnknownEnumValue, 64800, False), + # -------- + # Test cases to validate MaximumCheckInBackOff + # -------- + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", + c.Enums.OperatingModeEnum.kUnknownEnumValue, 0, False), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", + c.Enums.OperatingModeEnum.kSit, 1, True), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", + c.Enums.OperatingModeEnum.kSit, 64800, True), + ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000", + c.Enums.OperatingModeEnum.kSit, 64801, False), ] @@ -205,13 +222,13 @@ def test_spec_to_attribute_cache(test_icdm: ICDMData) -> Attribute.AsyncReadTran resp.attributes = {0: {c: {attr.FeatureMap: test_icdm.FeatureMap, attr.IdleModeDuration: test_icdm.IdleModeDuration, attr.ActiveModeDuration: test_icdm.ActiveModeDuration, attr.ActiveModeThreshold: test_icdm.ActiveModeThreshold, attr.RegisteredClients: test_icdm.RegisteredClients, attr.ICDCounter: test_icdm.ICDCounter, attr.ClientsSupportedPerFabric: test_icdm.ClientsSupportedPerFabric, attr.UserActiveModeTriggerHint: test_icdm.UserActiveModeTriggerHint, - attr.UserActiveModeTriggerInstruction: test_icdm.UserActiveModeTriggerInstruction, attr.OperatingMode: test_icdm.OperatingMode}}} + attr.UserActiveModeTriggerInstruction: test_icdm.UserActiveModeTriggerInstruction, attr.OperatingMode: test_icdm.OperatingMode, attr.MaximumCheckInBackOff: test_icdm.MaximumCheckInBackOff}}} return resp def main(): pics = {"ICDM.S.A0000": True, "ICDM.S.A0001": True, "ICDM.S.A0002": True, "ICDM.S.A0003": True, "ICDM.S.A0004": True, - "ICDM.S.A0005": True, "ICDM.S.A0006": True, "ICDM.S.A0007": True, "ICDM.S.A0008": True, } + "ICDM.S.A0005": True, "ICDM.S.A0006": True, "ICDM.S.A0007": True, "ICDM.S.A0008": True, "ICDM.S.A0009": True, } test_runner = MockTestRunner( 'TC_ICDM_2_1', 'TC_ICDM_2_1', 'test_TC_ICDM_2_1', 0, pics)