diff --git a/examples/air-quality-sensor-app/air-quality-sensor-common/icd-lit-air-quality-sensor-app.matter b/examples/air-quality-sensor-app/air-quality-sensor-common/icd-lit-air-quality-sensor-app.matter index d44f1245d4ae83..8ba7e8c7ffb54a 100644 --- a/examples/air-quality-sensor-app/air-quality-sensor-common/icd-lit-air-quality-sensor-app.matter +++ b/examples/air-quality-sensor-app/air-quality-sensor-common/icd-lit-air-quality-sensor-app.matter @@ -2851,7 +2851,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/all-clusters-app/realtek/data_model/all-clusters-app.matter b/examples/all-clusters-app/realtek/data_model/all-clusters-app.matter index 468a7df37b4e4f..f5e99c046532c5 100644 --- a/examples/all-clusters-app/realtek/data_model/all-clusters-app.matter +++ b/examples/all-clusters-app/realtek/data_model/all-clusters-app.matter @@ -8272,7 +8272,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } server cluster RelativeHumidityMeasurement { diff --git a/examples/chef/devices/icd_rootnode_contactsensor_ed3b19ec55.matter b/examples/chef/devices/icd_rootnode_contactsensor_ed3b19ec55.matter index 81d91eb62f7096..afb651201e422a 100644 --- a/examples/chef/devices/icd_rootnode_contactsensor_ed3b19ec55.matter +++ b/examples/chef/devices/icd_rootnode_contactsensor_ed3b19ec55.matter @@ -2128,7 +2128,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/chef/devices/rootnode_extractorhood_0359bf807d.matter b/examples/chef/devices/rootnode_extractorhood_0359bf807d.matter index b1b4e4bad08b63..13d61f7e672196 100644 --- a/examples/chef/devices/rootnode_extractorhood_0359bf807d.matter +++ b/examples/chef/devices/rootnode_extractorhood_0359bf807d.matter @@ -2009,7 +2009,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/closure-app/closure-common/closure-app.matter b/examples/closure-app/closure-common/closure-app.matter index 1fc6c059bb85b7..864be38bc6195f 100644 --- a/examples/closure-app/closure-common/closure-app.matter +++ b/examples/closure-app/closure-common/closure-app.matter @@ -2424,7 +2424,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/closure-app/silabs/data_model/closure-thread-app.matter b/examples/closure-app/silabs/data_model/closure-thread-app.matter index f436ed7598d9ec..ea0fdbabadd8f1 100644 --- a/examples/closure-app/silabs/data_model/closure-thread-app.matter +++ b/examples/closure-app/silabs/data_model/closure-thread-app.matter @@ -2270,7 +2270,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/closure-app/silabs/data_model/closure-wifi-app.matter b/examples/closure-app/silabs/data_model/closure-wifi-app.matter index dc7c2ed239a964..72ba0b08828297 100644 --- a/examples/closure-app/silabs/data_model/closure-wifi-app.matter +++ b/examples/closure-app/silabs/data_model/closure-wifi-app.matter @@ -2132,7 +2132,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/contact-sensor-app/bouffalolab/data_model/contact-sensor-app.matter b/examples/contact-sensor-app/bouffalolab/data_model/contact-sensor-app.matter index 6eef5c337b70e5..8480b344fd1e26 100644 --- a/examples/contact-sensor-app/bouffalolab/data_model/contact-sensor-app.matter +++ b/examples/contact-sensor-app/bouffalolab/data_model/contact-sensor-app.matter @@ -2326,7 +2326,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter index 0b423d911f2b90..96c3dc8bf0abd3 100644 --- a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter +++ b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter @@ -2172,7 +2172,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter index 070158c2b5cd77..a1e8535ff1865d 100644 --- a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter +++ b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter @@ -2165,7 +2165,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/light-switch-app/light-switch-common/icd-lit-light-switch-app.matter b/examples/light-switch-app/light-switch-common/icd-lit-light-switch-app.matter index 4fab472a272449..c76110cf74ce29 100644 --- a/examples/light-switch-app/light-switch-common/icd-lit-light-switch-app.matter +++ b/examples/light-switch-app/light-switch-common/icd-lit-light-switch-app.matter @@ -3272,7 +3272,7 @@ endpoint 0 { callback attribute operatingMode; callback attribute maximumCheckInBackOff; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.matter b/examples/light-switch-app/light-switch-common/light-switch-app.matter index d05eb71f103ed9..975ff7645c9c80 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.matter +++ b/examples/light-switch-app/light-switch-common/light-switch-app.matter @@ -3388,7 +3388,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/light-switch-app/qpg/zap/switch.matter b/examples/light-switch-app/qpg/zap/switch.matter index 10325c3125bcc6..4b5a85c0528814 100644 --- a/examples/light-switch-app/qpg/zap/switch.matter +++ b/examples/light-switch-app/qpg/zap/switch.matter @@ -3198,7 +3198,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_11.matter b/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_11.matter index 2f9e613bd20916..be1fe0b12af652 100644 --- a/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_11.matter +++ b/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_11.matter @@ -2882,7 +2882,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_2.matter b/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_2.matter index 5b50074ec20a7b..d1ec1539e87b1e 100644 --- a/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_2.matter +++ b/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_2.matter @@ -3038,7 +3038,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_8.matter b/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_8.matter index 6097edd3853e3d..bd0540f803831d 100644 --- a/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_8.matter +++ b/examples/light-switch-app/realtek/data_model/light-switch-app-1_to_8.matter @@ -3038,7 +3038,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/light-switch-app/realtek/data_model/light-switch-app.matter b/examples/light-switch-app/realtek/data_model/light-switch-app.matter index 51dbb991f81de3..7bbc1e9dd45f3f 100644 --- a/examples/light-switch-app/realtek/data_model/light-switch-app.matter +++ b/examples/light-switch-app/realtek/data_model/light-switch-app.matter @@ -2794,7 +2794,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/lighting-app/qpg/zap/light.matter b/examples/lighting-app/qpg/zap/light.matter index d60589d257b88d..f33ba1b9ece1a9 100644 --- a/examples/lighting-app/qpg/zap/light.matter +++ b/examples/lighting-app/qpg/zap/light.matter @@ -3214,7 +3214,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter index 0d5125e4e23e60..198c73b3e01296 100644 --- a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter +++ b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter @@ -2360,7 +2360,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/lock-app/lock-common/lock-app.matter b/examples/lock-app/lock-common/lock-app.matter index 30d0a17eaa8c47..d27d6d92d4df94 100644 --- a/examples/lock-app/lock-common/lock-app.matter +++ b/examples/lock-app/lock-common/lock-app.matter @@ -3252,7 +3252,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/lock-app/nxp/zap/lock-app.matter b/examples/lock-app/nxp/zap/lock-app.matter index ea0f8e9b511a39..21649425e068f8 100644 --- a/examples/lock-app/nxp/zap/lock-app.matter +++ b/examples/lock-app/nxp/zap/lock-app.matter @@ -2826,7 +2826,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 1; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/lock-app/qpg/zap/lock.matter b/examples/lock-app/qpg/zap/lock.matter index dbfd1cff9f0a34..aad818982660df 100644 --- a/examples/lock-app/qpg/zap/lock.matter +++ b/examples/lock-app/qpg/zap/lock.matter @@ -3191,7 +3191,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/lock-app/realtek/data_model/lock-app.matter b/examples/lock-app/realtek/data_model/lock-app.matter index 83e88c2f42e1ad..e0961589b5c443 100644 --- a/examples/lock-app/realtek/data_model/lock-app.matter +++ b/examples/lock-app/realtek/data_model/lock-app.matter @@ -2799,7 +2799,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/lock-app/silabs/data_model/lock-app.matter b/examples/lock-app/silabs/data_model/lock-app.matter index 061c6e7153ce02..545f9b45bcdfdf 100644 --- a/examples/lock-app/silabs/data_model/lock-app.matter +++ b/examples/lock-app/silabs/data_model/lock-app.matter @@ -3256,7 +3256,7 @@ endpoint 0 { callback attribute activeModeDuration; callback attribute activeModeThreshold; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter b/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter index ad5951897533c6..e46d4f37b062fe 100644 --- a/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter +++ b/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter @@ -2584,7 +2584,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; handle command RegisterClient; handle command RegisterClientResponse; diff --git a/examples/thermostat/qpg/zap/thermostaticRadiatorValve.matter b/examples/thermostat/qpg/zap/thermostaticRadiatorValve.matter index f7a851b338f665..cfcce1eb50c1cf 100644 --- a/examples/thermostat/qpg/zap/thermostaticRadiatorValve.matter +++ b/examples/thermostat/qpg/zap/thermostaticRadiatorValve.matter @@ -3003,7 +3003,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/examples/window-app/common/window-app.matter b/examples/window-app/common/window-app.matter index 1d9c64b5982860..d09e20ad90da49 100644 --- a/examples/window-app/common/window-app.matter +++ b/examples/window-app/common/window-app.matter @@ -2780,7 +2780,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; callback attribute featureMap; - ram attribute clusterRevision default = 3; + callback attribute clusterRevision; } } endpoint 1 { diff --git a/src/BUILD.gn b/src/BUILD.gn index 14eb5e836c4d9a..7b9936d63fcf31 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -59,6 +59,7 @@ if (chip_build_tests) { "${chip_root}/src/app/clusters/general-commissioning-server/tests", "${chip_root}/src/app/clusters/general-diagnostics-server/tests", "${chip_root}/src/app/clusters/group-key-mgmt-server/tests", + "${chip_root}/src/app/clusters/icd-management-server/tests", "${chip_root}/src/app/clusters/network-commissioning/tests", "${chip_root}/src/app/clusters/ota-provider/tests", "${chip_root}/src/app/clusters/software-diagnostics-server/tests", diff --git a/src/app/clusters/icd-management-server/BUILD.gn b/src/app/clusters/icd-management-server/BUILD.gn index ffad5c6eb5cbe0..081f82424cc034 100644 --- a/src/app/clusters/icd-management-server/BUILD.gn +++ b/src/app/clusters/icd-management-server/BUILD.gn @@ -16,13 +16,22 @@ import("${chip_root}/src/app/icd/icd.gni") # transitional dependencies so that chip_data_model sets # the right dependencies -group("icd-management-server") { +source_set("icd-management-server") { + sources = [ + "ICDManagementCluster.cpp", + "ICDManagementCluster.h", + ] public_deps = [ "${chip_root}/src/app/icd/server:configuration-data", "${chip_root}/src/app/icd/server:icd-server-config", + "${chip_root}/src/app/icd/server:observer", + "${chip_root}/src/app/server-cluster", ] if (chip_enable_icd_server) { - public_deps += [ "${chip_root}/src/app/icd/server:notifier" ] + public_deps += [ + "${chip_root}/src/app/icd/server:notifier", + "${chip_root}/src/app/server", + ] if (chip_enable_icd_checkin) { public_deps += [ "${chip_root}/src/app/icd/server:monitoring-table" ] } diff --git a/src/app/clusters/icd-management-server/CodegenIntegration.cpp b/src/app/clusters/icd-management-server/CodegenIntegration.cpp new file mode 100644 index 00000000000000..babcb5bbfdf360 --- /dev/null +++ b/src/app/clusters/icd-management-server/CodegenIntegration.cpp @@ -0,0 +1,116 @@ +/** + * + * Copyright (c) 2025 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +namespace { +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::IcdManagement; + +static_assert((IcdManagement::StaticApplicationConfig::kFixedClusterConfig.size() == 1 && + IcdManagement::StaticApplicationConfig::kFixedClusterConfig[0].endpointNumber == kRootEndpointId) || + IcdManagement::StaticApplicationConfig::kFixedClusterConfig.size() == 0); + +#if CHIP_CONFIG_ENABLE_ICD_CIP +LazyRegisteredServerCluster gServer; +#else +LazyRegisteredServerCluster gServer; +#endif + +constexpr chip::BitMask kEnabledCommands() +{ +#if defined(ICD_MANAGEMENT_STAY_ACTIVE_REQUEST_COMMAND) + return chip::BitMask(kStayActive); +#else + return chip::BitMask(); +#endif +} + +class IntegrationDelegate : public CodegenClusterIntegration::Delegate +{ +public: + ServerClusterRegistration & CreateRegistration(EndpointId endpointId, unsigned clusterInstanceIndex, + uint32_t optionalAttributeBits, uint32_t featureMap) override + { + ICDManagementCluster::OptionalAttributeSet optionalAttributeSet(optionalAttributeBits); + constexpr BitMask enabledCommands = kEnabledCommands(); + + // Get UserActiveModeTriggerHint + BitMask userActiveModeTriggerHint(0); + if (Clusters::IcdManagement::Attributes::UserActiveModeTriggerHint::Get(endpointId, &userActiveModeTriggerHint) != + Protocols::InteractionModel::Status::Success) + { + ChipLogError(Zcl, "Failed to get UserActiveModeTriggerHint, using default (0)"); + userActiveModeTriggerHint.ClearAll(); + } + + // Get UserActiveModeTriggerInstruction + char instructionBuffer[kUserActiveModeTriggerInstructionMaxLength]; + MutableCharSpan instructionSpan(instructionBuffer); + + if (Clusters::IcdManagement::Attributes::UserActiveModeTriggerInstruction::Get(endpointId, instructionSpan) != + Protocols::InteractionModel::Status::Success) + { + ChipLogError(Zcl, "Failed to get UserActiveModeTriggerInstruction, using default (empty string)"); + instructionSpan = MutableCharSpan(); + } + + gServer.Create(endpointId, *Server::GetInstance().GetSessionKeystore(), Server::GetInstance().GetFabricTable(), + ICDConfigurationData::GetInstance(), optionalAttributeSet, enabledCommands, userActiveModeTriggerHint, + instructionSpan); + return gServer.Registration(); + } + + ServerClusterInterface * FindRegistration(unsigned clusterInstanceIndex) override { return &gServer.Cluster(); } + void ReleaseRegistration(unsigned clusterInstanceIndex) override { gServer.Destroy(); } +}; +} // namespace + +void MatterIcdManagementClusterInitCallback(EndpointId endpointId) +{ + IntegrationDelegate integrationDelegate; + CodegenClusterIntegration::RegisterServer( + { + .endpointId = endpointId, + .clusterId = IcdManagement::Id, + .fixedClusterInstanceCount = static_cast(IcdManagement::StaticApplicationConfig::kFixedClusterConfig.size()), + .maxClusterInstanceCount = 1, // only root-node functionality supported by this implementation + .fetchFeatureMap = false, + .fetchOptionalAttributes = true, + }, + integrationDelegate); +} + +void MatterIcdManagementClusterShutdownCallback(EndpointId endpointId) +{ + IntegrationDelegate integrationDelegate; + CodegenClusterIntegration::UnregisterServer( + { + .endpointId = endpointId, + .clusterId = IcdManagement::Id, + .fixedClusterInstanceCount = static_cast(IcdManagement::StaticApplicationConfig::kFixedClusterConfig.size()), + .maxClusterInstanceCount = 1, // only root-node functionality supported by this implementation + }, + integrationDelegate); +} diff --git a/src/app/clusters/icd-management-server/ICDManagementCluster.cpp b/src/app/clusters/icd-management-server/ICDManagementCluster.cpp index 51bb4f9b058d79..2ec82220933446 100644 --- a/src/app/clusters/icd-management-server/ICDManagementCluster.cpp +++ b/src/app/clusters/icd-management-server/ICDManagementCluster.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2023 Project CHIP Authors + * Copyright (c) 2023-2025 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. @@ -15,22 +15,17 @@ * limitations under the License. */ -#include "ICDManagementCluster.h" - -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #if CHIP_CONFIG_ENABLE_ICD_CIP -#include // nogncheck +#include +#include // nogncheck +#include // nogncheck +#endif +#if CHIP_CONFIG_ENABLE_ICD_SERVER +#include // nogncheck #endif -#include -#include -#include using namespace chip; using namespace chip::app; @@ -42,10 +37,7 @@ using namespace chip::Access; using chip::Protocols::InteractionModel::Status; namespace { -ICDManagementAttributeAccess gAttribute; #if CHIP_CONFIG_ENABLE_ICD_CIP -ICDManagementFabricDelegate gFabricDelegate; - /** * @brief Function checks if the client has admin permissions to the cluster in the commandPath * @@ -78,8 +70,6 @@ CHIP_ERROR CheckAdmin(CommandHandler * commandObj, const ConcreteCommandPath & c namespace chip::app::Clusters { -ICDManagementServer ICDManagementServer::instance; - #if CHIP_CONFIG_ENABLE_ICD_CIP void ICDManagementFabricDelegate::Init(PersistentStorageDelegate & storage, Crypto::SymmetricKeystore * symmetricKeystore, ICDConfigurationData & icdConfigurationData) @@ -98,141 +88,361 @@ void ICDManagementFabricDelegate::OnFabricRemoved(const FabricTable & fabricTabl } #endif // CHIP_CONFIG_ENABLE_ICD_CIP -ICDManagementAttributeAccess::ICDManagementAttributeAccess() : - AttributeAccessInterface(MakeOptional(kRootEndpointId), IcdManagement::Id) -{} +ICDManagementCluster::ICDManagementCluster(EndpointId endpointId, Crypto::SymmetricKeystore & symmetricKeystore, + FabricTable & fabricTable, ICDConfigurationData & icdConfigurationData, + OptionalAttributeSet optionalAttributeSet, + BitMask enabledCommands, + BitMask userActiveModeTriggerBitmap, + CharSpan userActiveModeTriggerInstruction) : + DefaultServerCluster({ endpointId, IcdManagement::Id }), + mSymmetricKeystore(symmetricKeystore), mFabricTable(fabricTable), mICDConfigurationData(icdConfigurationData), + mOptionalAttributeSet(optionalAttributeSet), mUserActiveModeTriggerBitmap(userActiveModeTriggerBitmap), + mEnabledCommands(enabledCommands), mUserActiveModeTriggerInstructionLength(0) +{ + static_assert(sizeof(mUserActiveModeTriggerInstruction) <= UINT8_MAX, + "mUserActiveModeTriggerInstruction size must fit in uint8_t"); -void ICDManagementAttributeAccess::Init(PersistentStorageDelegate & storage, Crypto::SymmetricKeystore * symmetricKeystore, - FabricTable & fabricTable, ICDConfigurationData & icdConfigurationData) + MutableCharSpan buffer(mUserActiveModeTriggerInstruction); + CopyCharSpanToMutableCharSpanWithTruncation(userActiveModeTriggerInstruction, buffer); + mUserActiveModeTriggerInstructionLength = static_cast(buffer.size()); +} + +CHIP_ERROR ICDManagementCluster::Startup(ServerClusterContext & context) { -#if CHIP_CONFIG_ENABLE_ICD_CIP - mStorage = &storage; - mSymmetricKeystore = symmetricKeystore; - mFabricTable = &fabricTable; -#endif // CHIP_CONFIG_ENABLE_ICD_CIP - mICDConfigurationData = &icdConfigurationData; + ReturnErrorOnFailure(DefaultServerCluster::Startup(context)); +// TODO(#32321): Remove #if after issue is resolved +// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample +// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management +// server in those examples. +#if CHIP_CONFIG_ENABLE_ICD_SERVER + Server::GetInstance().GetICDManager().RegisterObserver(this); +#endif + return CHIP_NO_ERROR; } -CHIP_ERROR ICDManagementAttributeAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +void ICDManagementCluster::Shutdown() { - VerifyOrDie(aPath.mClusterId == IcdManagement::Id); +// TODO(#32321): Remove #if after issue is resolved +// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample +// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management +// server in those examples. +#if CHIP_CONFIG_ENABLE_ICD_SERVER + Server::GetInstance().GetICDManager().ReleaseObserver(this); +#endif +} - switch (aPath.mAttributeId) +DataModel::ActionReturnStatus ICDManagementCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & aEncoder) +{ + switch (request.path.mAttributeId) { + case IcdManagement::Attributes::ClusterRevision::Id: + return aEncoder.Encode(kRevision); + + case IcdManagement::Attributes::FeatureMap::Id: + return aEncoder.Encode(mICDConfigurationData.GetFeatureMap()); + case IcdManagement::Attributes::IdleModeDuration::Id: - return ReadIdleModeDuration(aPath.mEndpointId, aEncoder); + return aEncoder.Encode(mICDConfigurationData.GetIdleModeDuration().count()); case IcdManagement::Attributes::ActiveModeDuration::Id: - return ReadActiveModeDuration(aPath.mEndpointId, aEncoder); + return aEncoder.Encode(mICDConfigurationData.GetActiveModeDuration().count()); case IcdManagement::Attributes::ActiveModeThreshold::Id: - return ReadActiveModeThreshold(aPath.mEndpointId, aEncoder); + return aEncoder.Encode(mICDConfigurationData.GetActiveModeThreshold().count()); - case IcdManagement::Attributes::FeatureMap::Id: - return ReadFeatureMap(aPath.mEndpointId, aEncoder); #if CHIP_CONFIG_ENABLE_ICD_LIT case IcdManagement::Attributes::OperatingMode::Id: - return ReadOperatingMode(aPath.mEndpointId, aEncoder); + return ReadOperatingMode(aEncoder); #endif // CHIP_CONFIG_ENABLE_ICD_LIT -#if CHIP_CONFIG_ENABLE_ICD_CIP - case IcdManagement::Attributes::RegisteredClients::Id: - return ReadRegisteredClients(aPath.mEndpointId, aEncoder); - case IcdManagement::Attributes::ICDCounter::Id: - return ReadICDCounter(aPath.mEndpointId, aEncoder); + case IcdManagement::Attributes::UserActiveModeTriggerHint::Id: + return aEncoder.Encode(mUserActiveModeTriggerBitmap); - case IcdManagement::Attributes::ClientsSupportedPerFabric::Id: - return ReadClientsSupportedPerFabric(aPath.mEndpointId, aEncoder); + case IcdManagement::Attributes::UserActiveModeTriggerInstruction::Id: + return aEncoder.Encode(CharSpan(mUserActiveModeTriggerInstruction, mUserActiveModeTriggerInstructionLength)); - case IcdManagement::Attributes::MaximumCheckInBackOff::Id: - return ReadMaximumCheckInBackOff(aPath.mEndpointId, aEncoder); -#endif // CHIP_CONFIG_ENABLE_ICD_CIP + default: + return Protocols::InteractionModel::Status::UnsupportedAttribute; } +} - return CHIP_NO_ERROR; +CHIP_ERROR ICDManagementCluster::Attributes(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) +{ + AttributeListBuilder attributeListBuilder(builder); + BitFlags featureMap = mICDConfigurationData.GetFeatureMap(); + const bool hasUAT = featureMap.Has(IcdManagement::Feature::kUserActiveModeTrigger); + const bool hasLIT = featureMap.Has(IcdManagement::Feature::kLongIdleTimeSupport); + const bool hasUATInstruction = mOptionalAttributeSet.IsSet(IcdManagement::Attributes::UserActiveModeTriggerInstruction::Id); + + const AttributeListBuilder::OptionalAttributeEntry optionalEntries[] = { + { hasUAT, IcdManagement::Attributes::UserActiveModeTriggerHint::kMetadataEntry }, + { hasUATInstruction, IcdManagement::Attributes::UserActiveModeTriggerInstruction::kMetadataEntry }, + { hasLIT, IcdManagement::Attributes::OperatingMode::kMetadataEntry }, + }; + return attributeListBuilder.Append(Span(IcdManagement::Attributes::kMandatoryMetadata), Span(optionalEntries)); } -CHIP_ERROR ICDManagementAttributeAccess::ReadIdleModeDuration(EndpointId endpoint, AttributeValueEncoder & encoder) +std::optional ICDManagementCluster::InvokeCommand(const DataModel::InvokeRequest & request, + TLV::TLVReader & input_arguments, + CommandHandler * handler) { - return encoder.Encode(mICDConfigurationData->GetIdleModeDuration().count()); + switch (request.path.mCommandId) + { + case IcdManagement::Commands::StayActiveRequest::Id: { +// TODO(#32321): Remove #if after issue is resolved +// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample +// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management +// server in those examples. +#if CHIP_CONFIG_ENABLE_ICD_SERVER + Commands::StayActiveRequest::DecodableType commandData; + ReturnErrorOnFailure(commandData.Decode(input_arguments)); + + Commands::StayActiveResponse::Type response; + response.promisedActiveDuration = Server::GetInstance().GetICDManager().StayActiveRequest(commandData.stayActiveDuration); + handler->AddResponse(request.path, response); + return std::nullopt; +#else + return Status::UnsupportedCommand; +#endif // CHIP_CONFIG_ENABLE_ICD_SERVER + } + default: + return Status::UnsupportedCommand; + } } -CHIP_ERROR ICDManagementAttributeAccess::ReadActiveModeDuration(EndpointId endpoint, AttributeValueEncoder & encoder) +CHIP_ERROR ICDManagementCluster::AcceptedCommands(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) { - return encoder.Encode(mICDConfigurationData->GetActiveModeDuration().count()); + if (mICDConfigurationData.GetFeatureMap().Has(Feature::kLongIdleTimeSupport) || + mEnabledCommands.Has(OptionalCommands::kStayActive)) + { + static constexpr DataModel::AcceptedCommandEntry kStayActiveCommand[] = { + Commands::StayActiveRequest::kMetadataEntry, + }; + ReturnErrorOnFailure(builder.ReferenceExisting(kStayActiveCommand)); + } + return CHIP_NO_ERROR; } -CHIP_ERROR ICDManagementAttributeAccess::ReadActiveModeThreshold(EndpointId endpoint, AttributeValueEncoder & encoder) +CHIP_ERROR ICDManagementCluster::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder & builder) { - return encoder.Encode(mICDConfigurationData->GetActiveModeThreshold().count()); + if (mICDConfigurationData.GetFeatureMap().Has(Feature::kLongIdleTimeSupport) || + mEnabledCommands.Has(OptionalCommands::kStayActive)) + { + static constexpr CommandId kStayActiveResponse[] = { + Commands::StayActiveResponse::Id, + }; + ReturnErrorOnFailure(builder.ReferenceExisting(kStayActiveResponse)); + } + return CHIP_NO_ERROR; } -CHIP_ERROR ICDManagementAttributeAccess::ReadFeatureMap(EndpointId endpoint, AttributeValueEncoder & encoder) +void ICDManagementCluster::OnICDModeChange() { - return encoder.Encode(mICDConfigurationData->GetFeatureMap()); + // Notify attribute change for OperatingMode attribute + NotifyAttributeChanged(IcdManagement::Attributes::OperatingMode::Id); } #if CHIP_CONFIG_ENABLE_ICD_LIT -CHIP_ERROR ICDManagementAttributeAccess::ReadOperatingMode(EndpointId endpoint, AttributeValueEncoder & encoder) +CHIP_ERROR ICDManagementCluster::ReadOperatingMode(AttributeValueEncoder & encoder) { - return mICDConfigurationData->GetICDMode() == ICDConfigurationData::ICDMode::SIT + return mICDConfigurationData.GetICDMode() == ICDConfigurationData::ICDMode::SIT ? encoder.Encode(IcdManagement::OperatingModeEnum::kSit) : encoder.Encode(IcdManagement::OperatingModeEnum::kLit); } #endif // CHIP_CONFIG_ENABLE_ICD_LIT #if CHIP_CONFIG_ENABLE_ICD_CIP -CHIP_ERROR ICDManagementAttributeAccess::ReadRegisteredClients(EndpointId endpoint, AttributeValueEncoder & encoder) +ICDManagementClusterWithCIP::ICDManagementClusterWithCIP( + EndpointId endpointId, Crypto::SymmetricKeystore & symmetricKeystore, FabricTable & fabricTable, + ICDConfigurationData & icdConfigurationData, OptionalAttributeSet optionalAttributeSet, + BitMask enabledCommands, + BitMask userActiveModeTriggerBitmap, CharSpan userActiveModeTriggerInstruction) : + ICDManagementCluster(endpointId, symmetricKeystore, fabricTable, icdConfigurationData, optionalAttributeSet, enabledCommands, + userActiveModeTriggerBitmap, userActiveModeTriggerInstruction) +{} + +CHIP_ERROR ICDManagementClusterWithCIP::Startup(ServerClusterContext & context) { - uint16_t supported_clients = mICDConfigurationData->GetClientsSupportedPerFabric(); - PersistentStorageDelegate * storage = mStorage; - Crypto::SymmetricKeystore * symmetricKeystore = mSymmetricKeystore; - const FabricTable * fabricTable = mFabricTable; + ReturnErrorOnFailure(ICDManagementCluster::Startup(context)); - return encoder.EncodeList([supported_clients, storage, symmetricKeystore, fabricTable](const auto & subEncoder) -> CHIP_ERROR { - ICDMonitoringEntry e(symmetricKeystore); + mFabricDelegate.Init(context.storage, &mSymmetricKeystore, mICDConfigurationData); + return mFabricTable.AddFabricDelegate(&mFabricDelegate); +} - for (const auto & fabricInfo : *fabricTable) - { - ICDMonitoringTable table(*storage, fabricInfo.GetFabricIndex(), supported_clients, symmetricKeystore); - for (uint16_t i = 0; i < table.Limit(); ++i) - { - CHIP_ERROR err = table.Get(i, e); - if (CHIP_ERROR_NOT_FOUND == err) - { - // No more entries in the table - break; - } - ReturnErrorOnFailure(err); +void ICDManagementClusterWithCIP::Shutdown() +{ + mFabricTable.RemoveFabricDelegate(&mFabricDelegate); - Structs::MonitoringRegistrationStruct::Type s{ .checkInNodeID = e.checkInNodeID, - .monitoredSubject = e.monitoredSubject, - .clientType = e.clientType, - .fabricIndex = e.fabricIndex }; - ReturnErrorOnFailure(subEncoder.Encode(s)); - } + ICDManagementCluster::Shutdown(); +} + +DataModel::ActionReturnStatus ICDManagementClusterWithCIP::ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & aEncoder) +{ + switch (request.path.mAttributeId) + { + case IcdManagement::Attributes::RegisteredClients::Id: + return ReadRegisteredClients(aEncoder); + case IcdManagement::Attributes::ICDCounter::Id: + return aEncoder.Encode(mICDConfigurationData.GetICDCounter().GetValue()); + case IcdManagement::Attributes::ClientsSupportedPerFabric::Id: + return aEncoder.Encode(mICDConfigurationData.GetClientsSupportedPerFabric()); + case IcdManagement::Attributes::MaximumCheckInBackOff::Id: + return aEncoder.Encode(mICDConfigurationData.GetMaximumCheckInBackoff().count()); + default: + // Delegate to base class for all other attributes + return ICDManagementCluster::ReadAttribute(request, aEncoder); + } +} + +CHIP_ERROR ICDManagementClusterWithCIP::Attributes(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) +{ + // First get the base class attributes + ReturnErrorOnFailure(ICDManagementCluster::Attributes(path, builder)); + + // Add CIP-specific attributes + BitFlags featureMap = mICDConfigurationData.GetFeatureMap(); + const bool hasCIP = featureMap.Has(Feature::kCheckInProtocolSupport); + + if (hasCIP) + { + static constexpr DataModel::AttributeEntry kCIPAttributes[] = { + Attributes::RegisteredClients::kMetadataEntry, + Attributes::ICDCounter::kMetadataEntry, + Attributes::ClientsSupportedPerFabric::kMetadataEntry, + Attributes::MaximumCheckInBackOff::kMetadataEntry, + }; + ReturnErrorOnFailure(builder.ReferenceExisting(kCIPAttributes)); + } + + return CHIP_NO_ERROR; +} + +std::optional ICDManagementClusterWithCIP::InvokeCommand(const DataModel::InvokeRequest & request, + TLV::TLVReader & input_arguments, + CommandHandler * handler) +{ + switch (request.path.mCommandId) + { + case IcdManagement::Commands::RegisterClient::Id: { + Commands::RegisterClient::DecodableType commandData; + ReturnErrorOnFailure(commandData.Decode(input_arguments, handler->GetAccessingFabricIndex())); + + uint32_t icdCounter = 0; + Status status = RegisterClient(handler, request.path, commandData, icdCounter); + if (status == Status::Success) + { + Commands::RegisterClientResponse::Type response; + response.ICDCounter = icdCounter; + handler->AddResponse(request.path, response); + } + else + { + handler->AddStatus(request.path, status); } - return CHIP_NO_ERROR; - }); + break; + } + case IcdManagement::Commands::UnregisterClient::Id: { + Commands::UnregisterClient::DecodableType commandData; + ReturnErrorOnFailure(commandData.Decode(input_arguments, handler->GetAccessingFabricIndex())); + + Status status = UnregisterClient(handler, request.path, commandData); + handler->AddStatus(request.path, status); + break; + } + default: + // Delegate to base class for all other commands + return ICDManagementCluster::InvokeCommand(request, input_arguments, handler); + } + return std::nullopt; +} + +CHIP_ERROR ICDManagementClusterWithCIP::AcceptedCommands(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) +{ + ReturnErrorOnFailure(ICDManagementCluster::AcceptedCommands(path, builder)); + + BitFlags featureMap = mICDConfigurationData.GetFeatureMap(); + const bool hasCIP = featureMap.Has(Feature::kCheckInProtocolSupport); + + if (hasCIP) + { + static constexpr DataModel::AcceptedCommandEntry kCIPCommands[] = { + Commands::RegisterClient::kMetadataEntry, + Commands::UnregisterClient::kMetadataEntry, + }; + ReturnErrorOnFailure(builder.ReferenceExisting(kCIPCommands)); + } + + return CHIP_NO_ERROR; } -CHIP_ERROR ICDManagementAttributeAccess::ReadICDCounter(EndpointId endpoint, AttributeValueEncoder & encoder) +CHIP_ERROR ICDManagementClusterWithCIP::GeneratedCommands(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) { - return encoder.Encode(mICDConfigurationData->GetICDCounter().GetValue()); + ReturnErrorOnFailure(ICDManagementCluster::GeneratedCommands(path, builder)); + + BitFlags featureMap = mICDConfigurationData.GetFeatureMap(); + const bool hasCIP = featureMap.Has(Feature::kCheckInProtocolSupport); + + if (hasCIP) + { + static constexpr CommandId kCIPResponses[] = { + Commands::RegisterClientResponse::Id, + }; + ReturnErrorOnFailure(builder.ReferenceExisting(kCIPResponses)); + } + + return CHIP_NO_ERROR; } -CHIP_ERROR ICDManagementAttributeAccess::ReadClientsSupportedPerFabric(EndpointId endpoint, AttributeValueEncoder & encoder) +CHIP_ERROR ICDManagementClusterWithCIP::ReadRegisteredClients(AttributeValueEncoder & encoder) { - return encoder.Encode(mICDConfigurationData->GetClientsSupportedPerFabric()); + uint16_t supported_clients = mICDConfigurationData.GetClientsSupportedPerFabric(); + PersistentStorageDelegate & storage = mContext->storage; + Crypto::SymmetricKeystore & symmetricKeystore = mSymmetricKeystore; + const FabricTable & fabricTable = mFabricTable; + + return encoder.EncodeList( + [supported_clients, &storage, &symmetricKeystore, &fabricTable](const auto & subEncoder) -> CHIP_ERROR { + ICDMonitoringEntry e(&symmetricKeystore); + + for (const auto & fabricInfo : fabricTable) + { + ICDMonitoringTable table(storage, fabricInfo.GetFabricIndex(), supported_clients, &symmetricKeystore); + for (uint16_t i = 0; i < table.Limit(); ++i) + { + CHIP_ERROR err = table.Get(i, e); + if (CHIP_ERROR_NOT_FOUND == err) + { + // No more entries in the table + break; + } + ReturnErrorOnFailure(err); + + Structs::MonitoringRegistrationStruct::Type s{ .checkInNodeID = e.checkInNodeID, + .monitoredSubject = e.monitoredSubject, + .clientType = e.clientType, + .fabricIndex = e.fabricIndex }; + ReturnErrorOnFailure(subEncoder.Encode(s)); + } + } + return CHIP_NO_ERROR; + }); } -CHIP_ERROR ICDManagementAttributeAccess::ReadMaximumCheckInBackOff(EndpointId endpoint, AttributeValueEncoder & encoder) +void ICDManagementClusterWithCIP::TriggerICDMTableUpdatedEvent() { - return encoder.Encode(mICDConfigurationData->GetMaximumCheckInBackoff().count()); + ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDListener::ICDManagementEvents::kTableUpdated); } -Status ICDManagementServer::RegisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::RegisterClient::DecodableType & commandData, uint32_t & icdCounter) +Status ICDManagementClusterWithCIP::RegisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::RegisterClient::DecodableType & commandData, + uint32_t & icdCounter) { FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); NodeId nodeId = commandData.checkInNodeID; @@ -249,10 +459,11 @@ Status ICDManagementServer::RegisterClient(CommandHandler * commandObj, const Co VerifyOrReturnError(CHIP_NO_ERROR == CheckAdmin(commandObj, commandPath, isClientAdmin), Status::Failure); bool isFirstEntryForFabric = false; - ICDMonitoringTable table(*mStorage, fabricIndex, mICDConfigurationData->GetClientsSupportedPerFabric(), mSymmetricKeystore); + ICDMonitoringTable table(mContext->storage, fabricIndex, mICDConfigurationData.GetClientsSupportedPerFabric(), + &mSymmetricKeystore); // Get current entry, if exists - ICDMonitoringEntry entry(mSymmetricKeystore); + ICDMonitoringEntry entry(&mSymmetricKeystore); CHIP_ERROR err = table.Find(nodeId, entry); if (CHIP_NO_ERROR == err) { @@ -306,13 +517,14 @@ Status ICDManagementServer::RegisterClient(CommandHandler * commandObj, const Co // Notify subscribers that the first entry for the fabric was successfully added TriggerICDMTableUpdatedEvent(); } - MatterReportingAttributeChangeCallback(kRootEndpointId, IcdManagement::Id, IcdManagement::Attributes::RegisteredClients::Id); - icdCounter = mICDConfigurationData->GetICDCounter().GetValue(); + MarkRegisteredClientsListChanged(); + icdCounter = mICDConfigurationData.GetICDCounter().GetValue(); + return Status::Success; } -Status ICDManagementServer::UnregisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::UnregisterClient::DecodableType & commandData) +Status ICDManagementClusterWithCIP::UnregisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::UnregisterClient::DecodableType & commandData) { FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); NodeId nodeId = commandData.checkInNodeID; @@ -322,10 +534,11 @@ Status ICDManagementServer::UnregisterClient(CommandHandler * commandObj, const // Check if client is admin VerifyOrReturnError(CHIP_NO_ERROR == CheckAdmin(commandObj, commandPath, isClientAdmin), Status::Failure); - ICDMonitoringTable table(*mStorage, fabricIndex, mICDConfigurationData->GetClientsSupportedPerFabric(), mSymmetricKeystore); + ICDMonitoringTable table(mContext->storage, fabricIndex, mICDConfigurationData.GetClientsSupportedPerFabric(), + &mSymmetricKeystore); // Get current entry, if exists - ICDMonitoringEntry entry(mSymmetricKeystore); + ICDMonitoringEntry entry(&mSymmetricKeystore); CHIP_ERROR err = table.Find(nodeId, entry); VerifyOrReturnError(CHIP_ERROR_NOT_FOUND != err, Status::NotFound); VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure); @@ -345,138 +558,14 @@ Status ICDManagementServer::UnregisterClient(CommandHandler * commandObj, const TriggerICDMTableUpdatedEvent(); } - MatterReportingAttributeChangeCallback(kRootEndpointId, IcdManagement::Id, IcdManagement::Attributes::RegisteredClients::Id); + MarkRegisteredClientsListChanged(); return Status::Success; } -void ICDManagementServer::TriggerICDMTableUpdatedEvent() +void ICDManagementClusterWithCIP::MarkRegisteredClientsListChanged() { - ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDListener::ICDManagementEvents::kTableUpdated); + NotifyAttributeChanged(IcdManagement::Attributes::RegisteredClients::Id); } - -#endif // CHIP_CONFIG_ENABLE_ICD_CIP - -void ICDManagementServer::Init(PersistentStorageDelegate & storage, Crypto::SymmetricKeystore * symmetricKeystore, - ICDConfigurationData & icdConfigurationData) -{ -#if CHIP_CONFIG_ENABLE_ICD_CIP - mStorage = &storage; - mSymmetricKeystore = symmetricKeystore; #endif // CHIP_CONFIG_ENABLE_ICD_CIP - mICDConfigurationData = &icdConfigurationData; -} -void ICDManagementServer::OnICDModeChange() -{ - // Notify attribute change for OperatingMode attribute - MatterReportingAttributeChangeCallback(kRootEndpointId, IcdManagement::Id, IcdManagement::Attributes::OperatingMode::Id); -} } // namespace chip::app::Clusters - -/********************************************************** - * Callbacks Implementation - *********************************************************/ - -#if CHIP_CONFIG_ENABLE_ICD_CIP -/** - * @brief ICD Management Cluster RegisterClient Command callback (from client) - * - */ -bool emberAfIcdManagementClusterRegisterClientCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::RegisterClient::DecodableType & commandData) -{ - uint32_t icdCounter = 0; - - Status status = ICDManagementServer::GetInstance().RegisterClient(commandObj, commandPath, commandData, icdCounter); - - if (Status::Success == status) - { - // Response - IcdManagement::Commands::RegisterClientResponse::Type response{ .ICDCounter = icdCounter }; - commandObj->AddResponse(commandPath, response); - return true; - } - - // Error - commandObj->AddStatus(commandPath, status); - return true; -} - -/** - * @brief ICD Management Cluster UregisterClient Command callback (from client) - * - */ -bool emberAfIcdManagementClusterUnregisterClientCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::UnregisterClient::DecodableType & commandData) -{ - Status status = ICDManagementServer::GetInstance().UnregisterClient(commandObj, commandPath, commandData); - - commandObj->AddStatus(commandPath, status); - return true; -} -#endif // CHIP_CONFIG_ENABLE_ICD_CIP - -/** - * @brief ICD Management Cluster StayActiveRequest Command callback (from client) - */ -bool emberAfIcdManagementClusterStayActiveRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::StayActiveRequest::DecodableType & commandData) -{ -// TODO(#32321): Remove #if after issue is resolved -// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample -// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management -// server in those examples. -#if CHIP_CONFIG_ENABLE_ICD_SERVER - IcdManagement::Commands::StayActiveResponse::Type response; - response.promisedActiveDuration = Server::GetInstance().GetICDManager().StayActiveRequest(commandData.stayActiveDuration); - commandObj->AddResponse(commandPath, response); -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER - return true; -} - -void MatterIcdManagementPluginServerInitCallback() -{ - PersistentStorageDelegate & storage = Server::GetInstance().GetPersistentStorage(); - Crypto::SymmetricKeystore * symmetricKeystore = Server::GetInstance().GetSessionKeystore(); - FabricTable & fabricTable = Server::GetInstance().GetFabricTable(); - ICDConfigurationData & icdConfigurationData = ICDConfigurationData::GetInstance().GetInstance(); - -#if CHIP_CONFIG_ENABLE_ICD_CIP - // Configure and register Fabric delegate - gFabricDelegate.Init(storage, symmetricKeystore, icdConfigurationData); - fabricTable.AddFabricDelegate(&gFabricDelegate); -#endif // CHIP_CONFIG_ENABLE_ICD_CIP - - // Configure and register Attribute Access Override - gAttribute.Init(storage, symmetricKeystore, fabricTable, icdConfigurationData); - AttributeAccessInterfaceRegistry::Instance().Register(&gAttribute); - - // Configure ICD Management - ICDManagementServer::GetInstance().Init(storage, symmetricKeystore, icdConfigurationData); - -// TODO(#32321): Remove #if after issue is resolved -// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample -// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management -// server in those examples. -#if CHIP_CONFIG_ENABLE_ICD_SERVER - Server::GetInstance().GetICDManager().RegisterObserver(&ICDManagementServer::GetInstance()); -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER -} - -void MatterIcdManagementPluginServerShutdownCallback() -{ - AttributeAccessInterfaceRegistry::Instance().Unregister(&gAttribute); - -#if CHIP_CONFIG_ENABLE_ICD_CIP - FabricTable & fabricTable = Server::GetInstance().GetFabricTable(); - fabricTable.RemoveFabricDelegate(&gFabricDelegate); -#endif // CHIP_CONFIG_ENABLE_ICD_CIP - -// TODO(#32321): Remove #if after issue is resolved -// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample -// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management -// server in those examples. -#if CHIP_CONFIG_ENABLE_ICD_SERVER - Server::GetInstance().GetICDManager().ReleaseObserver(&ICDManagementServer::GetInstance()); -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER -} diff --git a/src/app/clusters/icd-management-server/ICDManagementCluster.h b/src/app/clusters/icd-management-server/ICDManagementCluster.h index 7f042404685b2f..60b11fd769f42a 100644 --- a/src/app/clusters/icd-management-server/ICDManagementCluster.h +++ b/src/app/clusters/icd-management-server/ICDManagementCluster.h @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022-2025 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. @@ -17,87 +17,112 @@ #pragma once -#include -#include -#include #include +#include +#include #include -#include +#include +#include #include #include -#include #include #if CHIP_CONFIG_ENABLE_ICD_CIP -#include -#include // nogncheck #include +#include #include #endif // CHIP_CONFIG_ENABLE_ICD_CIP #include namespace chip { + namespace Crypto { -using SymmetricKeystore = SessionKeystore; +using SymmetricKeystore = Crypto::SessionKeystore; } // namespace Crypto -} // namespace chip -namespace chip { namespace app { namespace Clusters { -class ICDManagementServer : public chip::app::ICDStateObserver +namespace IcdManagement { +enum class OptionalCommands : uint8_t +{ + kStayActive = 0x01, +}; + +constexpr size_t kUserActiveModeTriggerInstructionMaxLength = 128; +} // namespace IcdManagement + +/** + * @brief ICD Management Cluster + * + * This class provides the core ICD Management functionality. When CIP (Check-In Protocol) + * features are needed, use ICDManagementClusterWithCIP which extends this class. + */ +class ICDManagementCluster : public DefaultServerCluster, public chip::app::ICDStateObserver { public: - ICDManagementServer() = default; + using OptionalAttributeSet = app::OptionalAttributeSet; - void Init(PersistentStorageDelegate & storage, Crypto::SymmetricKeystore * symmetricKeystore, - ICDConfigurationData & ICDConfigurationData); + // TODO: The interaction between enabledCommands and feature flags (particularly LITS) needs clarification. + // According to spec, LITS implies StayActiveRequest support. Options: + // 1. Document that kStayActive bit in enabledCommands is ignored when LITS feature is set + // 2. Add Startup() validation to fail if enabledCommands and feature flags are inconsistent, + // and simplify AcceptedCommands/GeneratedCommands to only check mEnabledCommands - static ICDManagementServer & GetInstance() { return instance; }; -#if CHIP_CONFIG_ENABLE_ICD_CIP /** - * @brief Function that executes the business logic of the RegisterClient Command + * @brief Constructor for ICDManagementCluster * - * @param[out] icdCounter If function succeeds, icdCounter will have the current value of the ICDCounter stored in the - * ICDConfigurationData If function fails, icdCounter will be unchanged - * @return Status + * @param userActiveModeTriggerInstruction The instruction string is copied into an internal buffer + * during construction. The caller does not need to maintain the lifetime of the CharSpan + * or its underlying data after the constructor returns. If the instruction exceeds + * kUserActiveModeTriggerInstructionMaxLength (128 bytes), it will be truncated. */ - Protocols::InteractionModel::Status RegisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const IcdManagement::Commands::RegisterClient::DecodableType & commandData, - uint32_t & icdCounter); + ICDManagementCluster(EndpointId endpointId, Crypto::SymmetricKeystore & symmetricKeystore, FabricTable & fabricTable, + ICDConfigurationData & icdConfigurationData, OptionalAttributeSet optionalAttributeSet, + BitMask enabledCommands, + BitMask userActiveModeTriggerBitmap, + CharSpan userActiveModeTriggerInstruction); - Protocols::InteractionModel::Status - UnregisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const IcdManagement::Commands::UnregisterClient::DecodableType & commandData); -#endif // CHIP_CONFIG_ENABLE_ICD_CIP + CHIP_ERROR Startup(ServerClusterContext & context) override; + void Shutdown() override; -private: - static ICDManagementServer instance; -#if CHIP_CONFIG_ENABLE_ICD_CIP - /** - * @brief Triggers table update events to notify subscribers that an entry was added or removed - * from the ICDMonitoringTable. - */ - void TriggerICDMTableUpdatedEvent(); -#endif // CHIP_CONFIG_ENABLE_ICD_CIP + DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & aEncoder) override; + CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder & builder) override; - ICDConfigurationData * mICDConfigurationData; + std::optional InvokeCommand(const DataModel::InvokeRequest & request, + TLV::TLVReader & input_arguments, CommandHandler * handler) override; -#if CHIP_CONFIG_ENABLE_ICD_CIP - PersistentStorageDelegate * mStorage; - Crypto::SymmetricKeystore * mSymmetricKeystore; -#endif // CHIP_CONFIG_ENABLE_ICD_CIP + CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) override; + CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder & builder) override; + +private: void OnEnterActiveMode() override{}; void OnEnterIdleMode() override{}; void OnTransitionToIdle() override{}; void OnICDModeChange() override; + +protected: +#if CHIP_CONFIG_ENABLE_ICD_LIT + CHIP_ERROR ReadOperatingMode(AttributeValueEncoder & encoder); +#endif // CHIP_CONFIG_ENABLE_ICD_LIT + + Crypto::SymmetricKeystore & mSymmetricKeystore; + FabricTable & mFabricTable; + ICDConfigurationData & mICDConfigurationData; + const OptionalAttributeSet mOptionalAttributeSet; + const BitMask mUserActiveModeTriggerBitmap; + const BitMask mEnabledCommands; + uint8_t mUserActiveModeTriggerInstructionLength; + char mUserActiveModeTriggerInstruction[IcdManagement::kUserActiveModeTriggerInstructionMaxLength]; }; #if CHIP_CONFIG_ENABLE_ICD_CIP + /** * @brief Implementation of Fabric Delegate for ICD Management cluster */ @@ -115,44 +140,71 @@ class ICDManagementFabricDelegate : public FabricTable::Delegate ICDConfigurationData * mICDConfigurationData = nullptr; }; -#endif // CHIP_CONFIG_ENABLE_ICD_CIP - /** - * @brief Implementation of attribute access for IcdManagement cluster + * @brief ICD Management Cluster with CIP (Check-In Protocol) support + * + * This subclass extends ICDManagementCluster with CIP functionality, + * including client registration/unregistration and fabric delegate management. + * The fabric delegate is automatically registered/unregistered in Startup/Shutdown. + * + * @param userActiveModeTriggerInstruction The instruction string is copied into an internal buffer + * during construction (by the base class). The caller does not need to maintain the lifetime + * of the CharSpan or its underlying data after the constructor returns. */ -class ICDManagementAttributeAccess : public AttributeAccessInterface +class ICDManagementClusterWithCIP : public ICDManagementCluster { public: - ICDManagementAttributeAccess(); + ICDManagementClusterWithCIP(EndpointId endpointId, Crypto::SymmetricKeystore & symmetricKeystore, FabricTable & fabricTable, + ICDConfigurationData & icdConfigurationData, OptionalAttributeSet optionalAttributeSet, + BitMask enabledCommands, + BitMask userActiveModeTriggerBitmap, + CharSpan userActiveModeTriggerInstruction); - void Init(PersistentStorageDelegate & storage, Crypto::SymmetricKeystore * symmetricKeystore, FabricTable & fabricTable, - ICDConfigurationData & icdConfigurationData); + CHIP_ERROR Startup(ServerClusterContext & context) override; + void Shutdown() override; + + DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, + AttributeValueEncoder & aEncoder) override; + + CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder & builder) override; - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + std::optional InvokeCommand(const DataModel::InvokeRequest & request, + TLV::TLVReader & input_arguments, CommandHandler * handler) override; + + CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, + ReadOnlyBufferBuilder & builder) override; + + CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder & builder) override; private: - CHIP_ERROR ReadIdleModeDuration(EndpointId endpoint, AttributeValueEncoder & encoder); - CHIP_ERROR ReadActiveModeDuration(EndpointId endpoint, AttributeValueEncoder & encoder); - CHIP_ERROR ReadActiveModeThreshold(EndpointId endpoint, AttributeValueEncoder & encoder); - CHIP_ERROR ReadFeatureMap(EndpointId endpoint, AttributeValueEncoder & encoder); + void MarkRegisteredClientsListChanged(); -#if CHIP_CONFIG_ENABLE_ICD_LIT - CHIP_ERROR ReadOperatingMode(EndpointId endpoint, AttributeValueEncoder & encoder); -#endif // CHIP_CONFIG_ENABLE_ICD_LIT + CHIP_ERROR ReadRegisteredClients(AttributeValueEncoder & encoder); -#if CHIP_CONFIG_ENABLE_ICD_CIP - CHIP_ERROR ReadRegisteredClients(EndpointId endpoint, AttributeValueEncoder & encoder); - CHIP_ERROR ReadICDCounter(EndpointId endpoint, AttributeValueEncoder & encoder); - CHIP_ERROR ReadClientsSupportedPerFabric(EndpointId endpoint, AttributeValueEncoder & encoder); - CHIP_ERROR ReadMaximumCheckInBackOff(EndpointId endpoint, AttributeValueEncoder & encoder); + /** + * @brief Triggers table update events to notify subscribers that an entry was added or removed + * from the ICDMonitoringTable. + */ + void TriggerICDMTableUpdatedEvent(); - PersistentStorageDelegate * mStorage = nullptr; - Crypto::SymmetricKeystore * mSymmetricKeystore = nullptr; - FabricTable * mFabricTable = nullptr; -#endif // CHIP_CONFIG_ENABLE_ICD_CIP + /** + * @brief Function that executes the business logic of the RegisterClient Command + * + * @param[out] icdCounter If function succeeds, icdCounter will have the current value of the ICDCounter stored in the + * ICDConfigurationData If function fails, icdCounter will be unchanged + * @return Status + */ + Protocols::InteractionModel::Status RegisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const IcdManagement::Commands::RegisterClient::DecodableType & commandData, + uint32_t & icdCounter); - ICDConfigurationData * mICDConfigurationData = nullptr; + Protocols::InteractionModel::Status + UnregisterClient(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const IcdManagement::Commands::UnregisterClient::DecodableType & commandData); + + ICDManagementFabricDelegate mFabricDelegate; }; +#endif // CHIP_CONFIG_ENABLE_ICD_CIP } // namespace Clusters } // namespace app diff --git a/src/app/clusters/icd-management-server/app_config_dependent_sources.cmake b/src/app/clusters/icd-management-server/app_config_dependent_sources.cmake index 24015b73c120b8..f96be52e4ac7fe 100644 --- a/src/app/clusters/icd-management-server/app_config_dependent_sources.cmake +++ b/src/app/clusters/icd-management-server/app_config_dependent_sources.cmake @@ -16,6 +16,7 @@ TARGET_SOURCES( ${APP_TARGET} PRIVATE + "${CLUSTER_DIR}/CodegenIntegration.cpp" "${CLUSTER_DIR}/ICDManagementCluster.cpp" "${CLUSTER_DIR}/ICDManagementCluster.h" ) diff --git a/src/app/clusters/icd-management-server/app_config_dependent_sources.gni b/src/app/clusters/icd-management-server/app_config_dependent_sources.gni index 288f2e21833759..2c3232e2a4b4a2 100644 --- a/src/app/clusters/icd-management-server/app_config_dependent_sources.gni +++ b/src/app/clusters/icd-management-server/app_config_dependent_sources.gni @@ -11,7 +11,4 @@ # 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. -app_config_dependent_sources = [ - "ICDManagementCluster.cpp", - "ICDManagementCluster.h", -] +app_config_dependent_sources = [ "CodegenIntegration.cpp" ] diff --git a/src/app/clusters/icd-management-server/tests/BUILD.gn b/src/app/clusters/icd-management-server/tests/BUILD.gn new file mode 100644 index 00000000000000..e8a3561b6fce55 --- /dev/null +++ b/src/app/clusters/icd-management-server/tests/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright (c) 2025 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/chip_test_suite.gni") + +chip_test_suite("tests") { + output_name = "libTestICDManagementCluster" + + test_sources = [ "TestICDManagementCluster.cpp" ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + "${chip_root}/src/app/clusters/icd-management-server", + "${chip_root}/src/app/clusters/testing", + "${chip_root}/src/app/icd/server:configuration-data", + "${chip_root}/src/app/icd/server:observer", + "${chip_root}/src/credentials", + "${chip_root}/src/crypto", + "${chip_root}/src/lib/core:string-builder-adapters", + "${chip_root}/src/lib/support:testing", + ] +} diff --git a/src/app/clusters/icd-management-server/tests/TestICDManagementCluster.cpp b/src/app/clusters/icd-management-server/tests/TestICDManagementCluster.cpp new file mode 100644 index 00000000000000..e9ef13695ab2f7 --- /dev/null +++ b/src/app/clusters/icd-management-server/tests/TestICDManagementCluster.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::IcdManagement; +using namespace chip::app::Clusters::IcdManagement::Attributes; + +using chip::app::DataModel::AcceptedCommandEntry; +using chip::app::DataModel::AttributeEntry; + +// initialize memory as ReadOnlyBufferBuilder may allocate +struct TestIcdManagementCluster : public ::testing::Test +{ + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } +}; + +TEST_F(TestIcdManagementCluster, TestAttributes) +{ + // Create test instances + chip::Crypto::DefaultSessionKeystore keystore; + FabricTable fabricTable; + ICDConfigurationData & icdConfig = ICDConfigurationData::GetInstance(); + + BitMask optionalCommands = + BitMask(IcdManagement::OptionalCommands::kStayActive); + BitMask userActiveModeTriggerHint(0); + + // Since ICDManagementClusterWithCIP is under the #if CHIP_CONFIG_ENABLE_ICD_CIP, we need to test both cases. +#if CHIP_CONFIG_ENABLE_ICD_CIP + ICDManagementClusterWithCIP cluster(kRootEndpointId, keystore, fabricTable, icdConfig, + OptionalAttributeSet(IcdManagement::Attributes::UserActiveModeTriggerInstruction::Id), + optionalCommands, userActiveModeTriggerHint, CharSpan()); +#else + ICDManagementCluster cluster(kRootEndpointId, keystore, fabricTable, icdConfig, + OptionalAttributeSet(IcdManagement::Attributes::UserActiveModeTriggerInstruction::Id), + optionalCommands, userActiveModeTriggerHint, CharSpan()); +#endif + + // Test attribute list + ReadOnlyBufferBuilder attributesBuilder; + ASSERT_EQ(cluster.Attributes(ConcreteClusterPath(kRootEndpointId, IcdManagement::Id), attributesBuilder), CHIP_NO_ERROR); + + // Calculate expected attributes based on feature map and configuration + BitFlags featureMap = icdConfig.GetFeatureMap(); + bool hasCIP = featureMap.Has(IcdManagement::Feature::kCheckInProtocolSupport); + bool hasUAT = featureMap.Has(IcdManagement::Feature::kUserActiveModeTrigger); + bool hasLIT = featureMap.Has(IcdManagement::Feature::kLongIdleTimeSupport); + bool hasUserActiveModeTriggerInstruction = + OptionalAttributeSet().IsSet(IcdManagement::Attributes::UserActiveModeTriggerInstruction::Id); + + ReadOnlyBufferBuilder expectedBuilder; + ASSERT_EQ(expectedBuilder.ReferenceExisting(DefaultServerCluster::GlobalAttributes()), CHIP_NO_ERROR); + + // Add mandatory attributes + ASSERT_EQ(expectedBuilder.AppendElements({ IcdManagement::Attributes::IdleModeDuration::kMetadataEntry, + IcdManagement::Attributes::ActiveModeDuration::kMetadataEntry, + IcdManagement::Attributes::ActiveModeThreshold::kMetadataEntry }), + CHIP_NO_ERROR); + + // Add optional attributes based on feature map + if (hasCIP) + { + ASSERT_EQ(expectedBuilder.AppendElements({ IcdManagement::Attributes::RegisteredClients::kMetadataEntry, + IcdManagement::Attributes::ICDCounter::kMetadataEntry, + IcdManagement::Attributes::ClientsSupportedPerFabric::kMetadataEntry, + IcdManagement::Attributes::MaximumCheckInBackOff::kMetadataEntry }), + CHIP_NO_ERROR); + } + + if (hasUAT) + { + ASSERT_EQ(expectedBuilder.EnsureAppendCapacity(1), CHIP_NO_ERROR); + ASSERT_EQ(expectedBuilder.Append(IcdManagement::Attributes::UserActiveModeTriggerHint::kMetadataEntry), CHIP_NO_ERROR); + } + if (hasUserActiveModeTriggerInstruction) + { + ASSERT_EQ(expectedBuilder.EnsureAppendCapacity(1), CHIP_NO_ERROR); + ASSERT_EQ(expectedBuilder.Append(IcdManagement::Attributes::UserActiveModeTriggerInstruction::kMetadataEntry), + CHIP_NO_ERROR); + } + if (hasLIT) + { + ASSERT_EQ(expectedBuilder.EnsureAppendCapacity(1), CHIP_NO_ERROR); + ASSERT_EQ(expectedBuilder.Append(IcdManagement::Attributes::OperatingMode::kMetadataEntry), CHIP_NO_ERROR); + } + + ASSERT_TRUE(Testing::EqualAttributeSets(attributesBuilder.TakeBuffer(), expectedBuilder.TakeBuffer())); + + // Test accepted commands list + ReadOnlyBufferBuilder acceptedCommandsBuilder; + ASSERT_EQ(cluster.AcceptedCommands(ConcreteClusterPath(kRootEndpointId, IcdManagement::Id), acceptedCommandsBuilder), + CHIP_NO_ERROR); + + // Calculate expected accepted commands based on feature map and configuration + bool hasStayActive = optionalCommands.Has(IcdManagement::OptionalCommands::kStayActive); + + size_t expectedAcceptedCommands = 0; + if (hasCIP) + { + expectedAcceptedCommands += 2; // RegisterClient, UnregisterClient + } + if (hasLIT || hasStayActive) + { + expectedAcceptedCommands += 1; // StayActiveRequest + } + + ASSERT_TRUE(acceptedCommandsBuilder.Size() == expectedAcceptedCommands); + + // Test generated commands list + ReadOnlyBufferBuilder generatedCommandsBuilder; + ASSERT_EQ(cluster.GeneratedCommands(ConcreteClusterPath(kRootEndpointId, IcdManagement::Id), generatedCommandsBuilder), + CHIP_NO_ERROR); + + // Calculate expected generated commands based on feature map and configuration + size_t expectedGeneratedCommands = 0; + if (hasCIP) + { + expectedGeneratedCommands += 1; // RegisterClientResponse + } + if (hasLIT || hasStayActive) + { + expectedGeneratedCommands += 1; // StayActiveResponse + } + + ASSERT_TRUE(generatedCommandsBuilder.Size() == expectedGeneratedCommands); +} + +} // namespace diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml index fa86c7c3e8e4ae..81852fd8683dab 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -53,6 +53,7 @@ CommandHandlerInterfaceOnlyClusters: - Group Key Management - Groupcast - HEPA Filter Monitoring + - ICD Management - Identify - Joint Fabric Administrator - Laundry Washer Mode @@ -156,6 +157,7 @@ CodeDrivenClusters: - General Diagnostics - Group Key Management - Groupcast + - ICD Management - Identify - Localization Configuration - OTA Software Update Provider diff --git a/src/app/util/util.cpp b/src/app/util/util.cpp index 23540036f3ef34..e4aabf05fed40c 100644 --- a/src/app/util/util.cpp +++ b/src/app/util/util.cpp @@ -144,6 +144,7 @@ void MatterCommodityTariffPluginServerInitCallback() {} void MatterElectricalGridConditionsPluginServerInitCallback() {} void MatterSoilMeasurementPluginServerInitCallback() {} void MatterLocalizationConfigurationPluginServerInitCallback() {} +void MatterIcdManagementPluginServerInitCallback() {} bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId) { diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index 2293bc5b9597f6..e03dffe83c7ad7 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -341,6 +341,7 @@ "FeatureMap" ], "ICD Management": [ + "ClusterRevision", "IdleModeDuration", "ActiveModeDuration", "ActiveModeThreshold", diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index bee58fc8af257c..c73d0980f468a0 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -340,6 +340,7 @@ "FeatureMap" ], "ICD Management": [ + "ClusterRevision", "IdleModeDuration", "ActiveModeDuration", "ActiveModeThreshold", diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp index 8139bb0153103c..e4d0650f33b3c8 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp @@ -7053,53 +7053,6 @@ Protocols::InteractionModel::Status Set(EndpointId endpoint, chip::CharSpan valu } // namespace UserActiveModeTriggerInstruction -namespace ClusterRevision { - -Protocols::InteractionModel::Status Get(EndpointId endpoint, uint16_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - Protocols::InteractionModel::Status status = - emberAfReadAttribute(endpoint, Clusters::IcdManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - *value = Traits::StorageToWorking(temp); - return status; -} - -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint16_t value, MarkAttributeDirty markDirty) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(ConcreteAttributePath(endpoint, Clusters::IcdManagement::Id, Id), - EmberAfWriteDataInput(writable, ZCL_INT16U_ATTRIBUTE_TYPE).SetMarkDirty(markDirty)); -} - -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint16_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::IcdManagement::Id, Id, writable, ZCL_INT16U_ATTRIBUTE_TYPE); -} - -} // namespace ClusterRevision - } // namespace Attributes } // namespace IcdManagement diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h index 9c8f43161e3c87..c660de42e024d6 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h @@ -1139,12 +1139,6 @@ Protocols::InteractionModel::Status Set(EndpointId endpoint, chip::CharSpan valu Protocols::InteractionModel::Status Set(EndpointId endpoint, chip::CharSpan value, MarkAttributeDirty markDirty); } // namespace UserActiveModeTriggerInstruction -namespace ClusterRevision { -Protocols::InteractionModel::Status Get(EndpointId endpoint, uint16_t * value); // int16u -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint16_t value); -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint16_t value, MarkAttributeDirty markDirty); -} // namespace ClusterRevision - } // namespace Attributes } // namespace IcdManagement diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index 99e8045d92c065..407d19f48aab7b 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -7066,24 +7066,6 @@ bool emberAfTimeSynchronizationClusterSetDefaultNTPCallback( bool emberAfBridgedDeviceBasicInformationClusterKeepActiveCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::BridgedDeviceBasicInformation::Commands::KeepActive::DecodableType & commandData); -/** - * @brief ICD Management Cluster RegisterClient Command callback (from client) - */ -bool emberAfIcdManagementClusterRegisterClientCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::IcdManagement::Commands::RegisterClient::DecodableType & commandData); -/** - * @brief ICD Management Cluster UnregisterClient Command callback (from client) - */ -bool emberAfIcdManagementClusterUnregisterClientCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::IcdManagement::Commands::UnregisterClient::DecodableType & commandData); -/** - * @brief ICD Management Cluster StayActiveRequest Command callback (from client) - */ -bool emberAfIcdManagementClusterStayActiveRequestCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::IcdManagement::Commands::StayActiveRequest::DecodableType & commandData); /** * @brief Timer Cluster SetTimer Command callback (from client) */