From 87f6277ca14d6a67a81724bebeecbe033fc10248 Mon Sep 17 00:00:00 2001 From: Thomas Lea <35579828+tleacmcsa@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:27:10 -0500 Subject: [PATCH 01/11] Managed ACL: Add AccessRestrictionList support (#34932) * Add AccessRestrictionList support * Update src/access/AccessConfig.h Co-authored-by: C Freeman * Reworked data manipulators and other cleanup * Fixed encode/decode so reading CommissioningARL and Arl attributes work * Reworked ARL storage Previously ARL related data was persisted in KVS. This has been removed and now the responsibility for managing/maintaining the related data (CommissioningARL and ARL attributes) is up to the app to set on AccessRestrictionProvider class. * Review fixes cleanup ArlEncoder interface. return error to client if arl review request fails return token to client in FabricRestrictionReviewUpdate * Fixed GetEntries vector pointer arg * Updated core restriction logic/integration * Restyled by clang-format * fixed include check for renamed AccessRestrictionProvider.h file * M-ACL updates - refactored AccessControl::Check into CheckACL and CheckARL - added placeholders for the upcoming CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL - extracted ARL exception processing to standalone class for better testing * Add plumbing for subject descriptor IsCommissioning field - Make session manager update that state on a message-per-message basis - Add tests Missing test: MRP test against a not-yet-committed fabric over CASE showing that IsCommissioning is true. * Fix crash * Use new IsCommissioning in ARL check * Updates for review comments * restyled * Review updates - fixed return type for some command failures - enhanced unit tests * restyled * Updated ARL tests per review comments * work around nuttx and jsoncpp contention * Review comments and nuttx build failure fix attempt * review updates --------- Co-authored-by: C Freeman Co-authored-by: Restyled.io Co-authored-by: tennessee.carmelveilleux@gmail.com --- examples/network-manager-app/linux/args.gni | 3 + .../network-manager-app.matter | 9 +- .../network-manager-app.zap | 66 +- examples/platform/linux/AppMain.cpp | 27 + examples/platform/linux/BUILD.gn | 8 + .../linux/ExampleAccessRestrictionProvider.h | 55 ++ examples/platform/linux/Options.cpp | 77 ++ examples/platform/linux/Options.h | 9 + scripts/tools/check_includes_config.py | 1 + src/access/AccessConfig.h | 22 + src/access/AccessControl.cpp | 68 +- src/access/AccessControl.h | 35 + src/access/AccessRestrictionProvider.cpp | 249 ++++++ src/access/AccessRestrictionProvider.h | 275 +++++++ src/access/BUILD.gn | 28 + src/access/RequestPath.h | 2 +- src/access/SubjectDescriptor.h | 4 + src/access/access.gni | 18 + src/access/tests/BUILD.gn | 5 + .../tests/TestAccessRestrictionProvider.cpp | 722 ++++++++++++++++++ src/app/EventManagement.cpp | 2 +- src/app/InteractionModelEngine.cpp | 4 +- src/app/chip_data_model.gni | 6 + .../access-control-server/ArlEncoder.cpp | 145 ++++ .../access-control-server/ArlEncoder.h | 113 +++ .../access-control-server.cpp | 216 +++++- src/app/reporting/Engine.cpp | 2 +- src/app/server/BUILD.gn | 1 + src/app/server/Server.cpp | 7 + src/app/server/Server.h | 7 + src/credentials/FabricTable.cpp | 82 +- src/credentials/FabricTable.h | 17 + src/credentials/tests/TestFabricTable.cpp | 18 + src/lib/core/CHIPConfig.h | 21 + .../tests/TestReliableMessageProtocol.cpp | 14 +- src/transport/SecureSession.cpp | 34 +- src/transport/SecureSession.h | 15 +- src/transport/Session.h | 7 + src/transport/SessionManager.cpp | 8 + src/transport/tests/TestSessionManager.cpp | 10 +- 40 files changed, 2349 insertions(+), 63 deletions(-) create mode 100644 examples/platform/linux/ExampleAccessRestrictionProvider.h create mode 100644 src/access/AccessConfig.h create mode 100644 src/access/AccessRestrictionProvider.cpp create mode 100644 src/access/AccessRestrictionProvider.h create mode 100644 src/access/access.gni create mode 100644 src/access/tests/TestAccessRestrictionProvider.cpp create mode 100644 src/app/clusters/access-control-server/ArlEncoder.cpp create mode 100644 src/app/clusters/access-control-server/ArlEncoder.h diff --git a/examples/network-manager-app/linux/args.gni b/examples/network-manager-app/linux/args.gni index 53bb53d2b1b2a1..e97ddb13e7c46e 100644 --- a/examples/network-manager-app/linux/args.gni +++ b/examples/network-manager-app/linux/args.gni @@ -22,3 +22,6 @@ chip_project_config_include_dirs = [ ] chip_config_network_layer_ble = false + +# This enables AccessRestrictionList (ARL) support used by the NIM sample app +chip_enable_access_restrictions = true 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 627838288db0d1..57118d365577f1 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 @@ -1623,16 +1623,23 @@ endpoint 0 { server cluster AccessControl { emits event AccessControlEntryChanged; emits event AccessControlExtensionChanged; + emits event AccessRestrictionEntryChanged; + emits event FabricRestrictionReviewUpdate; callback attribute acl; callback attribute extension; callback attribute subjectsPerAccessControlEntry; callback attribute targetsPerAccessControlEntry; callback attribute accessControlEntriesPerFabric; + callback attribute commissioningARL; + callback attribute arl; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute attributeList; - ram attribute featureMap default = 0; + ram attribute featureMap default = 1; callback attribute clusterRevision; + + handle command ReviewFabricRestrictions; + handle command ReviewFabricRestrictionsResponse; } server cluster BasicInformation { 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 1d27e3c346f320..240cd495a7fb4f 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 @@ -314,6 +314,24 @@ "define": "ACCESS_CONTROL_CLUSTER", "side": "server", "enabled": 1, + "commands": [ + { + "name": "ReviewFabricRestrictions", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ReviewFabricRestrictionsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], "attributes": [ { "name": "ACL", @@ -395,6 +413,38 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "CommissioningARL", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ARL", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -453,7 +503,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -490,6 +540,20 @@ "mfgCode": null, "side": "server", "included": 1 + }, + { + "name": "AccessRestrictionEntryChanged", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "FabricRestrictionReviewUpdate", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 } ] }, diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 307b3428126db2..074f078af003b4 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -103,6 +103,10 @@ #include "AppMain.h" #include "CommissionableInit.h" +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include "ExampleAccessRestrictionProvider.h" +#endif + #if CHIP_DEVICE_LAYER_TARGET_DARWIN #include #if CHIP_DEVICE_CONFIG_ENABLE_WIFI @@ -121,6 +125,7 @@ using namespace chip::DeviceLayer; using namespace chip::Inet; using namespace chip::Transport; using namespace chip::app::Clusters; +using namespace chip::Access; // Network comissioning implementation namespace { @@ -180,6 +185,10 @@ Optional sWiFiNetworkCommissionin app::Clusters::NetworkCommissioning::Instance sEthernetNetworkCommissioningInstance(kRootEndpointId, &sEthernetDriver); #endif // CHIP_APP_MAIN_HAS_ETHERNET_DRIVER +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +auto exampleAccessRestrictionProvider = std::make_unique(); +#endif + void EnableThreadNetworkCommissioning() { #if CHIP_APP_MAIN_HAS_THREAD_DRIVER @@ -593,9 +602,27 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) chip::app::RuntimeOptionsProvider::Instance().SetSimulateNoInternalTime( LinuxDeviceOptions::GetInstance().mSimulateNoInternalTime); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + initParams.accessRestrictionProvider = exampleAccessRestrictionProvider.get(); +#endif + // Init ZCL Data Model and CHIP App Server Server::GetInstance().Init(initParams); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (LinuxDeviceOptions::GetInstance().commissioningArlEntries.HasValue()) + { + exampleAccessRestrictionProvider->SetCommissioningEntries( + LinuxDeviceOptions::GetInstance().commissioningArlEntries.Value()); + } + + if (LinuxDeviceOptions::GetInstance().arlEntries.HasValue()) + { + // This example use of the ARL feature proactively installs the provided entries on fabric index 1 + exampleAccessRestrictionProvider->SetEntries(1, LinuxDeviceOptions::GetInstance().arlEntries.Value()); + } +#endif + #if CONFIG_BUILD_FOR_HOST_UNIT_TEST // Set ReadHandler Capacity for Subscriptions chip::app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions( diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 1fcee183f131b3..336534aae65043 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -19,6 +19,10 @@ import("${chip_root}/src/lib/core/core.gni") import("${chip_root}/src/lib/lib.gni") import("${chip_root}/src/tracing/tracing_args.gni") +if (current_os != "nuttx") { + import("//build_overrides/jsoncpp.gni") +} + declare_args() { chip_enable_smoke_co_trigger = false chip_enable_boolean_state_configuration_trigger = false @@ -101,6 +105,10 @@ source_set("app-main") { "${chip_root}/src/app/server", ] + if (current_os != "nuttx") { + public_deps += [ jsoncpp_root ] + } + if (chip_enable_pw_rpc) { defines += [ "PW_RPC_ENABLED" ] } diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h new file mode 100644 index 00000000000000..731a8ae5845a21 --- /dev/null +++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h @@ -0,0 +1,55 @@ +/* + * + * 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. + */ + +/* + * AccessRestriction implementation for Linux examples. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace Access { + +class ExampleAccessRestrictionProvider : public AccessRestrictionProvider +{ +public: + ExampleAccessRestrictionProvider() : AccessRestrictionProvider() {} + + ~ExampleAccessRestrictionProvider() {} + +protected: + CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector & arl) + { + // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events + Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector{}); + + chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, + .fabricIndex = fabricIndex }; + EventNumber eventNumber; + ReturnErrorOnFailure(chip::app::LogEvent(event, kRootEndpointId, eventNumber)); + + return CHIP_NO_ERROR; + } +}; + +} // namespace Access +} // namespace chip diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 9b83d126c1f495..6f8afea5bb496b 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,11 @@ using namespace chip; using namespace chip::ArgParser; +using namespace chip::Platform; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +using namespace chip::Access; +#endif namespace { LinuxDeviceOptions gDeviceOptions; @@ -82,6 +88,10 @@ enum kDeviceOption_TraceFile, kDeviceOption_TraceLog, kDeviceOption_TraceDecode, +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + kDeviceOption_CommissioningArlEntries, + kDeviceOption_ArlEntries, +#endif kOptionCSRResponseCSRIncorrectType, kOptionCSRResponseCSRNonceIncorrectType, kOptionCSRResponseCSRNonceTooLong, @@ -154,6 +164,10 @@ OptionDef sDeviceOptionDefs[] = { { "trace_log", kArgumentRequired, kDeviceOption_TraceLog }, { "trace_decode", kArgumentRequired, kDeviceOption_TraceDecode }, #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + { "commissioning-arl-entries", kArgumentRequired, kDeviceOption_CommissioningArlEntries }, + { "arl-entries", kArgumentRequired, kDeviceOption_ArlEntries }, +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS { "cert_error_csr_incorrect_type", kNoArgument, kOptionCSRResponseCSRIncorrectType }, { "cert_error_csr_existing_keypair", kNoArgument, kOptionCSRResponseCSRExistingKeyPair }, { "cert_error_csr_nonce_incorrect_type", kNoArgument, kOptionCSRResponseCSRNonceIncorrectType }, @@ -280,6 +294,14 @@ const char * sDeviceOptionHelp = " --trace_decode <1/0>\n" " A value of 1 enables traces decoding, 0 disables this (default 0).\n" #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + " --commissioning-arl-entries \n" + " Enable ACL cluster access restrictions used during commissioning with the provided JSON. Example:\n" + " \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 1105,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 0}]}]\"\n" + " --arl-entries \n" + " Enable ACL cluster access restrictions applied to fabric index 1 with the provided JSON. Example:\n" + " \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 1105,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 0}]}]\"\n" +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS " --cert_error_csr_incorrect_type\n" " Configure the CSRResponse to be built with an invalid CSR type.\n" " --cert_error_csr_existing_keypair\n" @@ -320,6 +342,39 @@ const char * sDeviceOptionHelp = #endif "\n"; +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, std::vector & entries) +{ + Json::Value root; + Json::Reader reader; + VerifyOrReturnValue(reader.parse(jsonString, root), false); + + for (Json::Value::const_iterator eIt = root.begin(); eIt != root.end(); eIt++) + { + AccessRestrictionProvider::Entry entry; + + entry.endpointNumber = static_cast((*eIt)["endpoint"].asUInt()); + entry.clusterId = static_cast((*eIt)["cluster"].asUInt()); + + Json::Value restrictions = (*eIt)["restrictions"]; + for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++) + { + AccessRestrictionProvider::Restriction restriction; + restriction.restrictionType = static_cast((*rIt)["type"].asUInt()); + if ((*rIt).isMember("id")) + { + restriction.id.SetValue((*rIt)["id"].asUInt()); + } + entry.restrictions.push_back(restriction); + } + + entries.push_back(entry); + } + + return true; +} +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + bool Base64ArgToVector(const char * arg, size_t maxSize, std::vector & outVector) { size_t maxBase64Size = BASE64_ENCODED_LEN(maxSize); @@ -529,6 +584,28 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, break; #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + // TODO(#35189): change to use a path to JSON files instead + case kDeviceOption_CommissioningArlEntries: { + std::vector entries; + retval = ParseAccessRestrictionEntriesFromJson(aValue, entries); + if (retval) + { + LinuxDeviceOptions::GetInstance().commissioningArlEntries.SetValue(std::move(entries)); + } + } + break; + case kDeviceOption_ArlEntries: { + std::vector entries; + retval = ParseAccessRestrictionEntriesFromJson(aValue, entries); + if (retval) + { + LinuxDeviceOptions::GetInstance().arlEntries.SetValue(std::move(entries)); + } + } + break; +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + case kOptionCSRResponseCSRIncorrectType: LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrIncorrectType = true; break; diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index f921bee4ced554..11a9061efcade8 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,10 @@ #include #include +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include +#endif + struct LinuxDeviceOptions { chip::PayloadContents payload; @@ -81,6 +86,10 @@ struct LinuxDeviceOptions #if CONFIG_BUILD_FOR_HOST_UNIT_TEST int32_t subscriptionCapacity = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; int32_t subscriptionResumptionRetryIntervalSec = -1; +#endif +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + chip::Optional> commissioningArlEntries; + chip::Optional> arlEntries; #endif static LinuxDeviceOptions & GetInstance(); }; diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index 2af375d7c4bdfd..b5195f4ab05eda 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -185,4 +185,5 @@ 'src/app/icd/client/DefaultICDStorageKey.h': {'vector'}, 'src/controller/CHIPDeviceController.cpp': {'string'}, 'src/qrcodetool/setup_payload_commands.cpp': {'string'}, + 'src/access/AccessRestrictionProvider.h': {'vector', 'map'}, } diff --git a/src/access/AccessConfig.h b/src/access/AccessConfig.h new file mode 100644 index 00000000000000..b9318a10d9e4d5 --- /dev/null +++ b/src/access/AccessConfig.h @@ -0,0 +1,22 @@ +/* + * + * 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 + +#if CHIP_HAVE_CONFIG_H +#include +#endif diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index fcb5a43d975f8e..8302fb0b122265 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -181,7 +181,7 @@ char GetRequestTypeStringForLogging(RequestType requestType) return 'w'; case RequestType::kCommandInvokeRequest: return 'i'; - case RequestType::kEventReadOrSubscribeRequest: + case RequestType::kEventReadRequest: return 'e'; default: return '?'; @@ -325,7 +325,11 @@ void AccessControl::RemoveEntryListener(EntryListener & listener) bool AccessControl::IsAccessRestrictionListSupported() const { - return false; // not yet supported +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + return mAccessRestrictionProvider != nullptr; +#else + return false; +#endif } CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, @@ -333,6 +337,21 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con { VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); + CHIP_ERROR result = CheckACL(subjectDescriptor, requestPath, requestPrivilege); + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (result == CHIP_NO_ERROR) + { + result = CheckARL(subjectDescriptor, requestPath, requestPrivilege); + } +#endif + + return result; +} + +CHIP_ERROR AccessControl::CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, + Privilege requestPrivilege) +{ #if CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 { constexpr size_t kMaxCatsToLog = 6; @@ -347,11 +366,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con } #endif // CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 - if (IsAccessRestrictionListSupported()) - { - VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); - } - { CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege); if (result != CHIP_ERROR_NOT_IMPLEMENTED) @@ -368,6 +382,7 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error"); } #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 + return result; } } @@ -497,6 +512,45 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con return CHIP_ERROR_ACCESS_DENIED; } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, + Privilege requestPrivilege) +{ + CHIP_ERROR result = CHIP_NO_ERROR; + + VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); + + if (!IsAccessRestrictionListSupported()) + { + // Access Restriction support is compiled in, but not configured/enabled. Nothing to restrict. + return CHIP_NO_ERROR; + } + + if (subjectDescriptor.isCommissioning) + { + result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath); + } + else + { + result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); + } + + if (result != CHIP_NO_ERROR) + { + ChipLogProgress(DataManagement, "AccessControl: %s", +#if 0 + // TODO(#35177): new error code coming when access check plumbing are fixed in callers + (result == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL) ? "denied (restricted)" : "denied (restriction error)"); +#else + (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); +#endif + return result; + } + + return result; +} +#endif + #if CHIP_ACCESS_CONTROL_DUMP_ENABLED CHIP_ERROR AccessControl::Dump(const Entry & entry) { diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h index a7c3472f5d99b4..df986864b8ff4c 100644 --- a/src/access/AccessControl.h +++ b/src/access/AccessControl.h @@ -18,6 +18,12 @@ #pragma once +#include + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include "AccessRestrictionProvider.h" +#endif + #include "Privilege.h" #include "RequestPath.h" #include "SubjectDescriptor.h" @@ -627,6 +633,16 @@ class AccessControl // Removes a listener from the listener list, if in the list. void RemoveEntryListener(EntryListener & listener); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + // Set an optional AcceessRestriction object for MNGD feature. + void SetAccessRestrictionProvider(AccessRestrictionProvider * accessRestrictionProvider) + { + mAccessRestrictionProvider = accessRestrictionProvider; + } + + AccessRestrictionProvider * GetAccessRestrictionProvider() { return mAccessRestrictionProvider; } +#endif + /** * Check whether or not Access Restriction List is supported. * @@ -638,6 +654,8 @@ class AccessControl * Check whether access (by a subject descriptor, to a request path, * requiring a privilege) should be allowed or denied. * + * If an AccessRestrictionProvider object is set, it will be checked for additional access restrictions. + * * @retval #CHIP_ERROR_ACCESS_DENIED if denied. * @retval other errors should also be treated as denied. * @retval #CHIP_NO_ERROR if allowed. @@ -656,12 +674,29 @@ class AccessControl void NotifyEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry, EntryListener::ChangeType changeType); + /** + * Check ACL for whether access (by a subject descriptor, to a request path, + * requiring a privilege) should be allowed or denied. + */ + CHIP_ERROR CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege); + + /** + * Check CommissioningARL or ARL (as appropriate) for whether access (by a + * subject descriptor, to a request path, requiring a privilege) should + * be allowed or denied. + */ + CHIP_ERROR CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege); + private: Delegate * mDelegate = nullptr; DeviceTypeResolver * mDeviceTypeResolver = nullptr; EntryListener * mEntryListener = nullptr; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + AccessRestrictionProvider * mAccessRestrictionProvider; +#endif }; /** diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp new file mode 100644 index 00000000000000..23e8082353abc8 --- /dev/null +++ b/src/access/AccessRestrictionProvider.cpp @@ -0,0 +1,249 @@ +/* + * + * 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 "AccessRestrictionProvider.h" + +#include +#include + +using namespace chip::Platform; + +namespace chip { +namespace Access { + +void AccessRestrictionProvider::AddListener(Listener & listener) +{ + if (mListeners == nullptr) + { + mListeners = &listener; + listener.mNext = nullptr; + return; + } + + for (Listener * l = mListeners; /**/; l = l->mNext) + { + if (l == &listener) + { + return; + } + + if (l->mNext == nullptr) + { + l->mNext = &listener; + listener.mNext = nullptr; + return; + } + } +} + +void AccessRestrictionProvider::RemoveListener(Listener & listener) +{ + if (mListeners == &listener) + { + mListeners = listener.mNext; + listener.mNext = nullptr; + return; + } + + for (Listener * l = mListeners; l != nullptr; l = l->mNext) + { + if (l->mNext == &listener) + { + l->mNext = listener.mNext; + listener.mNext = nullptr; + return; + } + } +} + +CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector & entries) +{ + for (auto & entry : entries) + { + if (!mExceptionChecker.AreRestrictionsAllowed(entry.endpointNumber, entry.clusterId)) + { + ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + mCommissioningEntries = entries; + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->MarkCommissioningRestrictionListChanged(); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, const std::vector & entries) +{ + std::vector updatedEntries; + + for (auto & entry : entries) + { + if (!mExceptionChecker.AreRestrictionsAllowed(entry.endpointNumber, entry.clusterId)) + { + ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + Entry updatedEntry = entry; + updatedEntry.fabricIndex = fabricIndex; + updatedEntries.push_back(updatedEntry); + } + + mFabricEntries[fabricIndex] = std::move(updatedEntries); + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->MarkRestrictionListChanged(fabricIndex); + } + + return CHIP_NO_ERROR; +} + +bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, + ClusterId cluster) +{ + if (endpoint != kRootEndpointId && + (cluster == app::Clusters::WiFiNetworkManagement::Id || cluster == app::Clusters::ThreadBorderRouterManagement::Id || + cluster == app::Clusters::ThreadNetworkDirectory::Id)) + { + return true; + } + + return false; +} + +CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) +{ + return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath); +} + +CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +{ + return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath); +} + +CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector & entries, const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) +{ + if (!mExceptionChecker.AreRestrictionsAllowed(requestPath.endpoint, requestPath.cluster)) + { + ChipLogProgress(DataManagement, "AccessRestrictionProvider: skipping checks for unrestrictable request path"); + return CHIP_NO_ERROR; + } + + ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", to_underlying(requestPath.requestType)); + + if (requestPath.requestType == RequestType::kRequestTypeUnknown) + { + ChipLogError(DataManagement, "AccessRestrictionProvider: RequestPath type is unknown"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and + // we want that request to succeed (when generating the report, this method will be called with the specific + // event id). All other requests require an entity id + if (!requestPath.entityId.has_value()) + { + if (requestPath.requestType == RequestType::kEventReadRequest) + { + return CHIP_NO_ERROR; + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + for (auto & entry : entries) + { + if (entry.endpointNumber != requestPath.endpoint || entry.clusterId != requestPath.cluster) + { + continue; + } + + for (auto & restriction : entry.restrictions) + { + // A missing id is a wildcard + bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value(); + if (!idMatch) + { + continue; + } + + switch (restriction.restrictionType) + { + case Type::kAttributeAccessForbidden: + if (requestPath.requestType == RequestType::kAttributeReadRequest || + requestPath.requestType == RequestType::kAttributeWriteRequest) + { +#if 0 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; +#else + return CHIP_ERROR_ACCESS_DENIED; +#endif + } + break; + case Type::kAttributeWriteForbidden: + if (requestPath.requestType == RequestType::kAttributeWriteRequest) + { +#if 0 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; +#else + return CHIP_ERROR_ACCESS_DENIED; +#endif + } + break; + case Type::kCommandForbidden: + if (requestPath.requestType == RequestType::kCommandInvokeRequest) + { +#if 0 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; +#else + return CHIP_ERROR_ACCESS_DENIED; +#endif + } + break; + case Type::kEventForbidden: + if (requestPath.requestType == RequestType::kEventReadRequest) + { +#if 0 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; +#else + return CHIP_ERROR_ACCESS_DENIED; +#endif + } + break; + } + } + } + + return CHIP_NO_ERROR; +} + +} // namespace Access +} // namespace chip diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h new file mode 100644 index 00000000000000..705d9c365f8f1f --- /dev/null +++ b/src/access/AccessRestrictionProvider.h @@ -0,0 +1,275 @@ +/* + * + * 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 "Privilege.h" +#include "RequestPath.h" +#include "SubjectDescriptor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Access { + +class AccessRestrictionProvider +{ +public: + static constexpr size_t kNumberOfFabrics = CHIP_CONFIG_MAX_FABRICS; + static constexpr size_t kEntriesPerFabric = CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC; + static constexpr size_t kRestrictionsPerEntry = CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY; + + /** + * Defines the type of access restriction, which is used to determine the meaning of the restriction's id. + */ + enum class Type : uint8_t + { + kAttributeAccessForbidden = 0, + kAttributeWriteForbidden = 1, + kCommandForbidden = 2, + kEventForbidden = 3 + }; + + /** + * Defines a single restriction on an attribute, command, or event. + * + * If id is not set, the restriction applies to all attributes, commands, or events of the given type (wildcard). + */ + struct Restriction + { + Type restrictionType; + Optional id; + }; + + /** + * Defines a single entry in the access restriction list, which contains a list of restrictions + * for a cluster on an endpoint. + */ + struct Entry + { + FabricIndex fabricIndex; + EndpointId endpointNumber; + ClusterId clusterId; + std::vector restrictions; + }; + + /** + * Defines the interface for a checker for access restriction exceptions. + */ + class AccessRestrictionExceptionChecker + { + public: + virtual ~AccessRestrictionExceptionChecker() = default; + + /** + * Check if any restrictions are allowed to be applied on the given endpoint and cluster + * because of constraints against their use in ARLs. + * + * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise + */ + virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0; + }; + + /** + * Define a standard implementation of the AccessRestrictionExceptionChecker interface + * which is the default implementation used by AccessResrictionProvider. + */ + class StandardAccessRestrictionExceptionChecker : public AccessRestrictionExceptionChecker + { + public: + StandardAccessRestrictionExceptionChecker() = default; + ~StandardAccessRestrictionExceptionChecker() = default; + + bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override; + }; + + /** + * Used to notify of changes in the access restriction list and active reviews. + */ + class Listener + { + public: + virtual ~Listener() = default; + + /** + * Notifies of a change in the commissioning access restriction list. + */ + virtual void MarkCommissioningRestrictionListChanged() = 0; + + /** + * Notifies of a change in the access restriction list. + * + * @param [in] fabricIndex The index of the fabric in which the list has changed. + */ + virtual void MarkRestrictionListChanged(FabricIndex fabricIndex) = 0; + + /** + * Notifies of an update to an active review with instructions and an optional redirect URL. + * + * @param [in] fabricIndex The index of the fabric in which the entry has changed. + * @param [in] token The token of the review being updated (obtained from ReviewFabricRestrictionsResponse) + * @param [in] instruction Optional instructions to be displayed to the user. + * @param [in] redirectUrl An optional URL to redirect the user to for more information. + */ + virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional instruction, + Optional redirectUrl) = 0; + + private: + Listener * mNext = nullptr; + + friend class AccessRestrictionProvider; + }; + + AccessRestrictionProvider() = default; + virtual ~AccessRestrictionProvider() = default; + + AccessRestrictionProvider(const AccessRestrictionProvider &) = delete; + AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete; + + /** + * Set the restriction entries that are to be used during commissioning when there is no accessing fabric. + * + * @param [in] entries The entries to set. + */ + CHIP_ERROR SetCommissioningEntries(const std::vector & entries); + + /** + * Set the restriction entries for a fabric. + * + * @param [in] fabricIndex The index of the fabric for which to create entries. + * @param [in] entries The entries to set for the fabric. + */ + CHIP_ERROR SetEntries(const FabricIndex, const std::vector & entries); + + /** + * Add a listener to be notified of changes in the access restriction list and active reviews. + * + * @param [in] listener The listener to add. + */ + void AddListener(Listener & listener); + + /** + * Remove a listener from being notified of changes in the access restriction list and active reviews. + * + * @param [in] listener The listener to remove. + */ + void RemoveListener(Listener & listener); + + /** + * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action + * during commissioning by using the CommissioningEntries. + * + * These restrictions are are only a part of overall access evaluation. + * + * If access is not restricted, CHIP_NO_ERROR will be returned. + * + * @retval CHIP_ERROR_ACCESS_DENIED if access is denied. + * @retval other errors should also be treated as restricted/denied. + * @retval CHIP_NO_ERROR if access is not restricted/denied. + */ + CHIP_ERROR CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath); + + /** + * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action. + * These restrictions are are only a part of overall access evaluation. + * + * If access is not restricted, CHIP_NO_ERROR will be returned. + * + * @retval CHIP_ERROR_ACCESS_DENIED if access is denied. + * @retval other errors should also be treated as restricted/denied. + * @retval CHIP_NO_ERROR if access is not restricted/denied. + */ + CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath); + + /** + * Request a review of the access restrictions for a fabric. + * + * @param [in] fabricIndex The index of the fabric requesting a review. + * @param [in] arl An optinal list of access restriction entries to review. If null, all entries will be reviewed. + * @param [out] token The unique token for the review, which can be matched to a review update event. + */ + CHIP_ERROR RequestFabricRestrictionReview(FabricIndex fabricIndex, const std::vector & arl, uint64_t & token) + { + token = mNextToken++; + return DoRequestFabricRestrictionReview(fabricIndex, token, arl); + } + + /** + * Get the commissioning restriction entries. + * + * @retval the commissioning restriction entries. + */ + const std::vector & GetCommissioningEntries() const { return mCommissioningEntries; } + + /** + * Get the restriction entries for a fabric. + * + * @param [in] fabricIndex the index of the fabric for which to get entries. + * @param [out] entries vector to hold the entries. + */ + CHIP_ERROR GetEntries(const FabricIndex fabricIndex, std::vector & entries) const + { + auto it = mFabricEntries.find(fabricIndex); + if (it == mFabricEntries.end()) + { + return CHIP_ERROR_NOT_FOUND; + } + + entries = (it->second); + + return CHIP_NO_ERROR; + } + +protected: + /** + * Initiate a review of the access restrictions for a fabric. This method should be implemented by the platform and be + * non-blocking. + * + * @param [in] fabricIndex The index of the fabric requesting a review. + * @param [in] token The unique token for the review, which can be matched to a review update event. + * @param [in] arl An optinal list of access restriction entries to review. If null, all entries will be reviewed. + * @return CHIP_NO_ERROR if the review was successfully requested, or an error code if the request failed. + */ + virtual CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, + const std::vector & arl) = 0; + +private: + /** + * Perform the access restriction check using the given entries. + */ + CHIP_ERROR DoCheck(const std::vector & entries, const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath); + + uint64_t mNextToken = 1; + Listener * mListeners = nullptr; + StandardAccessRestrictionExceptionChecker mExceptionChecker; + std::vector mCommissioningEntries; + std::map> mFabricEntries; +}; + +} // namespace Access +} // namespace chip diff --git a/src/access/BUILD.gn b/src/access/BUILD.gn index 8d2c4b504975ee..3f3aaafe8a6a9b 100644 --- a/src/access/BUILD.gn +++ b/src/access/BUILD.gn @@ -13,6 +13,25 @@ # limitations under the License. import("//build_overrides/chip.gni") +import("${chip_root}/build/chip/buildconfig_header.gni") +import("${chip_root}/src/access/access.gni") + +buildconfig_header("access_buildconfig") { + header = "AccessBuildConfig.h" + header_dir = "access" + + defines = [ + "CHIP_CONFIG_USE_ACCESS_RESTRICTIONS=${chip_enable_access_restrictions}", + ] + + visibility = [ ":access_config" ] +} + +source_set("access_config") { + sources = [ "AccessConfig.h" ] + + deps = [ ":access_buildconfig" ] +} source_set("types") { sources = [ @@ -23,6 +42,7 @@ source_set("types") { ] public_deps = [ + ":access_config", "${chip_root}/src/lib/core", "${chip_root}/src/lib/core:types", ] @@ -43,10 +63,18 @@ static_library("access") { cflags = [ "-Wconversion" ] public_deps = [ + ":access_config", ":types", "${chip_root}/src/lib/core", "${chip_root}/src/lib/core:types", "${chip_root}/src/lib/support", "${chip_root}/src/platform", ] + + if (chip_enable_access_restrictions) { + sources += [ + "AccessRestrictionProvider.cpp", + "AccessRestrictionProvider.h", + ] + } } diff --git a/src/access/RequestPath.h b/src/access/RequestPath.h index af791d73eb151d..920d5fed372eb1 100644 --- a/src/access/RequestPath.h +++ b/src/access/RequestPath.h @@ -30,7 +30,7 @@ enum class RequestType : uint8_t kAttributeReadRequest, kAttributeWriteRequest, kCommandInvokeRequest, - kEventReadOrSubscribeRequest + kEventReadRequest }; struct RequestPath diff --git a/src/access/SubjectDescriptor.h b/src/access/SubjectDescriptor.h index ec6abec0b38a30..9cde4102750d25 100644 --- a/src/access/SubjectDescriptor.h +++ b/src/access/SubjectDescriptor.h @@ -42,6 +42,10 @@ struct SubjectDescriptor // CASE Authenticated Tags (CATs) only valid if auth mode is CASE. CATValues cats; + + // Whether the subject is currently a pending commissionee. See `IsCommissioning` + // definition in Core Specification's ACL Architecture pseudocode. + bool isCommissioning = false; }; } // namespace Access diff --git a/src/access/access.gni b/src/access/access.gni new file mode 100644 index 00000000000000..bd18f1387b66b9 --- /dev/null +++ b/src/access/access.gni @@ -0,0 +1,18 @@ +# 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. + +declare_args() { + # Enable ARL features of Access Control + chip_enable_access_restrictions = false +} diff --git a/src/access/tests/BUILD.gn b/src/access/tests/BUILD.gn index d8b43e6a17a01d..64226adfacc260 100644 --- a/src/access/tests/BUILD.gn +++ b/src/access/tests/BUILD.gn @@ -15,6 +15,7 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") import("//build_overrides/pigweed.gni") +import("${chip_root}/src/access/access.gni") import("${chip_root}/build/chip/chip_test_suite.gni") @@ -29,4 +30,8 @@ chip_test_suite("tests") { "${chip_root}/src/lib/support:test_utils", "${dir_pw_unit_test}", ] + + if (chip_enable_access_restrictions) { + test_sources += [ "TestAccessRestrictionProvider.cpp" ] + } } diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp new file mode 100644 index 00000000000000..ddf58ae2488f4d --- /dev/null +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -0,0 +1,722 @@ +/* + * + * 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 "access/AccessControl.h" +#include "access/AccessRestrictionProvider.h" +#include "access/examples/ExampleAccessControlDelegate.h" + +#include + +#include +#include +#include +namespace chip { +namespace Access { + +class TestAccessRestrictionProvider : public AccessRestrictionProvider +{ + CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector & arl) + { + return CHIP_NO_ERROR; + } +}; + +AccessControl accessControl; +TestAccessRestrictionProvider accessRestrictionProvider; + +constexpr ClusterId kNetworkCommissioningCluster = app::Clusters::NetworkCommissioning::Id; +constexpr ClusterId kDescriptorCluster = app::Clusters::Descriptor::Id; +constexpr ClusterId kOnOffCluster = app::Clusters::OnOff::Id; + +// Clusters allowed to have restrictions +constexpr ClusterId kWiFiNetworkManagementCluster = app::Clusters::WiFiNetworkManagement::Id; +constexpr ClusterId kThreadBorderRouterMgmtCluster = app::Clusters::ThreadBorderRouterManagement::Id; +constexpr ClusterId kThreadNetworkDirectoryCluster = app::Clusters::ThreadNetworkDirectory::Id; + +constexpr NodeId kOperationalNodeId1 = 0x1111111111111111; +constexpr NodeId kOperationalNodeId2 = 0x2222222222222222; +constexpr NodeId kOperationalNodeId3 = 0x3333333333333333; + +bool operator==(const AccessRestrictionProvider::Restriction & lhs, const AccessRestrictionProvider::Restriction & rhs) +{ + return lhs.restrictionType == rhs.restrictionType && lhs.id == rhs.id; +} + +bool operator==(const AccessRestrictionProvider::Entry & lhs, const AccessRestrictionProvider::Entry & rhs) +{ + return lhs.fabricIndex == rhs.fabricIndex && lhs.endpointNumber == rhs.endpointNumber && lhs.clusterId == rhs.clusterId && + lhs.restrictions == rhs.restrictions; +} + +struct AclEntryData +{ + FabricIndex fabricIndex = kUndefinedFabricIndex; + Privilege privilege = Privilege::kView; + AuthMode authMode = AuthMode::kNone; + NodeId subject; +}; + +constexpr AclEntryData aclEntryData[] = { + { + .fabricIndex = 1, + .privilege = Privilege::kAdminister, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + }, + { + .fabricIndex = 2, + .privilege = Privilege::kAdminister, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId2, + }, +}; +constexpr size_t aclEntryDataCount = ArraySize(aclEntryData); + +struct CheckData +{ + SubjectDescriptor subjectDescriptor; + RequestPath requestPath; + Privilege privilege; + bool allow; +}; + +constexpr CheckData checkDataNoRestrictions[] = { + // Checks for implicit PASE + { .subjectDescriptor = { .fabricIndex = 0, .authMode = AuthMode::kPase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 0, .authMode = AuthMode::kPase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subject = kOperationalNodeId3 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + // Checks for entry 0 + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + // Checks for entry 1 + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +CHIP_ERROR LoadEntry(AccessControl::Entry & entry, const AclEntryData & entryData) +{ + ReturnErrorOnFailure(entry.SetAuthMode(entryData.authMode)); + ReturnErrorOnFailure(entry.SetFabricIndex(entryData.fabricIndex)); + ReturnErrorOnFailure(entry.SetPrivilege(entryData.privilege)); + ReturnErrorOnFailure(entry.AddSubject(nullptr, entryData.subject)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LoadAccessControl(AccessControl & ac, const AclEntryData * entryData, size_t count) +{ + AccessControl::Entry entry; + for (size_t i = 0; i < count; ++i, ++entryData) + { + ReturnErrorOnFailure(ac.PrepareEntry(entry)); + ReturnErrorOnFailure(LoadEntry(entry, *entryData)); + ReturnErrorOnFailure(ac.CreateEntry(nullptr, entry)); + } + return CHIP_NO_ERROR; +} + +void RunChecks(const CheckData * checkData, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + CHIP_ERROR expectedResult = checkData[i].allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED; + EXPECT_EQ(accessControl.Check(checkData[i].subjectDescriptor, checkData[i].requestPath, checkData[i].privilege), + expectedResult); + } +} + +class DeviceTypeResolver : public AccessControl::DeviceTypeResolver +{ +public: + bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return false; } +} testDeviceTypeResolver; + +class TestAccessRestriction : public ::testing::Test +{ +public: // protected + void SetUp() override + { + accessRestrictionProvider.SetCommissioningEntries(std::vector()); + accessRestrictionProvider.SetEntries(0, std::vector()); + accessRestrictionProvider.SetEntries(1, std::vector()); + accessRestrictionProvider.SetEntries(2, std::vector()); + } + + static void SetUpTestSuite() + { + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + AccessControl::Delegate * delegate = Examples::GetAccessControlDelegate(); + SetAccessControl(accessControl); + GetAccessControl().SetAccessRestrictionProvider(&accessRestrictionProvider); + VerifyOrDie(GetAccessControl().Init(delegate, testDeviceTypeResolver) == CHIP_NO_ERROR); + EXPECT_EQ(LoadAccessControl(accessControl, aclEntryData, aclEntryDataCount), CHIP_NO_ERROR); + } + static void TearDownTestSuite() + { + GetAccessControl().Finish(); + ResetAccessControlToDefault(); + } +}; + +// basic data check without restrictions +TEST_F(TestAccessRestriction, MetaTest) +{ + for (const auto & checkData : checkDataNoRestrictions) + { + CHIP_ERROR expectedResult = checkData.allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED; + EXPECT_EQ(accessControl.Check(checkData.subjectDescriptor, checkData.requestPath, checkData.privilege), expectedResult); + } +} + +// ensure failure when adding restrictons on endpoint 0 (any cluster, including those allowed on other endpoints) +TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointZeroTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.endpointNumber = 0; + entry.fabricIndex = 1; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + + entry.clusterId = kDescriptorCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + entries.clear(); + entry.clusterId = kNetworkCommissioningCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + entries.clear(); + entry.clusterId = kWiFiNetworkManagementCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + entries.clear(); + entry.clusterId = kThreadBorderRouterMgmtCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + entries.clear(); + entry.clusterId = kThreadNetworkDirectoryCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + // also test a cluster on endpoint 0 that isnt in the special allowed list + entries.clear(); + entry.clusterId = kOnOffCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); +} + +// ensure no failure adding restrictions on endpoint 1 for allowed clusters only: +// wifi network management, thread border router, thread network directory +TEST_F(TestAccessRestriction, ValidRestrictionsOnEndpointOneTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.endpointNumber = 1; + entry.fabricIndex = 1; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + + entry.clusterId = kWiFiNetworkManagementCluster; + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + entries.clear(); + entry.clusterId = kThreadBorderRouterMgmtCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + entries.clear(); + entry.clusterId = kThreadNetworkDirectoryCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + // also test a cluster on endpoint 1 that isnt in the special allowed list + entries.clear(); + entry.clusterId = kOnOffCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointOneTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.endpointNumber = 1; + entry.fabricIndex = 1; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry.clusterId = kOnOffCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); +} + +constexpr CheckData accessAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kWiFiNetworkManagementCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + + // test wildcarded entity id + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData)); + + // test specific entity id + entries.clear(); + entry.restrictions[0].id.SetValue(1); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData)); +} + +constexpr CheckData writeAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kWiFiNetworkManagementCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); + + // test wildcarded entity id + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData)); + + // test specific entity id + entries.clear(); + entry.restrictions[0].id.SetValue(1); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData)); +} + +constexpr CheckData commandAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, CommandRestrictionTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kWiFiNetworkManagementCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); + + // test wildcarded entity id + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData)); + + // test specific entity id + entries.clear(); + entry.restrictions[0].id.SetValue(1); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData)); +} + +constexpr CheckData eventAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, +}; + +TEST_F(TestAccessRestriction, EventRestrictionTest) +{ + std::vector entries; + AccessRestrictionProvider::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kWiFiNetworkManagementCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kEventForbidden }); + + // test wildcarded entity id + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData)); + + // test specific entity id + entries.clear(); + entry.restrictions[0].id.SetValue(1); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData)); +} + +constexpr CheckData combinedRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 2 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 3 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 4 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 3 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 4 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 5 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 2 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, CombinedRestrictionTest) +{ + // a restriction for all access to attribute 1 and 2, attributes 3 and 4 are allowed + std::vector entries1; + AccessRestrictionProvider::Entry entry1; + entry1.fabricIndex = 1; + entry1.endpointNumber = 1; + entry1.clusterId = kWiFiNetworkManagementCluster; + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); + entry1.restrictions[0].id.SetValue(1); + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry1.restrictions[1].id.SetValue(2); + entries1.push_back(entry1); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries1), CHIP_NO_ERROR); + + // a restriction for fabric 2 that forbids command 1 and 2. Check that command 1 is blocked on invoke, but attribute 2 write is + // allowed + std::vector entries2; + AccessRestrictionProvider::Entry entry2; + entry2.fabricIndex = 2; + entry2.endpointNumber = 1; + entry2.clusterId = kWiFiNetworkManagementCluster; + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); + entry2.restrictions[0].id.SetValue(1); + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); + entry2.restrictions[1].id.SetValue(2); + entries2.push_back(entry2); + EXPECT_EQ(accessRestrictionProvider.SetEntries(2, entries2), CHIP_NO_ERROR); + + RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData)); +} + +TEST_F(TestAccessRestriction, AttributeStorageSeperationTest) +{ + std::vector commissioningEntries; + AccessRestrictionProvider::Entry entry1; + entry1.fabricIndex = 1; + entry1.endpointNumber = 1; + entry1.clusterId = kWiFiNetworkManagementCluster; + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); + entry1.restrictions[0].id.SetValue(1); + commissioningEntries.push_back(entry1); + EXPECT_EQ(accessRestrictionProvider.SetCommissioningEntries(commissioningEntries), CHIP_NO_ERROR); + + std::vector entries; + AccessRestrictionProvider::Entry entry2; + entry2.fabricIndex = 2; + entry2.endpointNumber = 2; + entry2.clusterId = kThreadBorderRouterMgmtCluster; + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); + entry2.restrictions[0].id.SetValue(2); + entries.push_back(entry2); + EXPECT_EQ(accessRestrictionProvider.SetEntries(2, entries), CHIP_NO_ERROR); + + auto commissioningEntriesFetched = accessRestrictionProvider.GetCommissioningEntries(); + std::vector arlEntriesFetched; + EXPECT_EQ(accessRestrictionProvider.GetEntries(2, arlEntriesFetched), CHIP_NO_ERROR); + EXPECT_EQ(commissioningEntriesFetched[0], entry1); + EXPECT_EQ(commissioningEntriesFetched.size(), static_cast(1)); + EXPECT_EQ(arlEntriesFetched[0], entry2); + EXPECT_EQ(arlEntriesFetched.size(), static_cast(1)); + EXPECT_FALSE(commissioningEntriesFetched[0] == arlEntriesFetched[0]); +} + +constexpr CheckData listSelectionDuringCommissioningData[] = { + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = true }, + .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = false }, + .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, ListSelectiondDuringCommissioningTest) +{ + // during commissioning, read is allowed on WifiNetworkManagement and disallowed on ThreadBorderRouterMgmt + // after commissioning, read is disallowed on WifiNetworkManagement and allowed on ThreadBorderRouterMgmt + + std::vector entries; + AccessRestrictionProvider::Entry entry1; + entry1.fabricIndex = 1; + entry1.endpointNumber = 1; + entry1.clusterId = kThreadBorderRouterMgmtCluster; + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry1.restrictions[0].id.SetValue(1); + entries.push_back(entry1); + EXPECT_EQ(accessRestrictionProvider.SetCommissioningEntries(entries), CHIP_NO_ERROR); + + entries.clear(); + AccessRestrictionProvider::Entry entry2; + entry2.fabricIndex = 1; + entry2.endpointNumber = 1; + entry2.clusterId = kWiFiNetworkManagementCluster; + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry2.restrictions[0].id.SetValue(1); + entries.push_back(entry2); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + RunChecks(listSelectionDuringCommissioningData, ArraySize(listSelectionDuringCommissioningData)); +} + +} // namespace Access +} // namespace chip diff --git a/src/app/EventManagement.cpp b/src/app/EventManagement.cpp index 4c5faab3cdcda4..2419d564d0bc7a 100644 --- a/src/app/EventManagement.cpp +++ b/src/app/EventManagement.cpp @@ -556,7 +556,7 @@ CHIP_ERROR EventManagement::CheckEventContext(EventLoadOutContext * eventLoadOut Access::RequestPath requestPath{ .cluster = event.mClusterId, .endpoint = event.mEndpointId, - .requestType = Access::RequestType::kEventReadOrSubscribeRequest, + .requestType = Access::RequestType::kEventReadRequest, .entityId = event.mEventId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path); CHIP_ERROR accessControlError = diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 64d30bc6c0e42f..88c1777b6c8224 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -544,7 +544,7 @@ static bool CanAccessEvent(const Access::SubjectDescriptor & aSubjectDescriptor, { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId, - .requestType = Access::RequestType::kEventReadOrSubscribeRequest }; + .requestType = Access::RequestType::kEventReadRequest }; // leave requestPath.entityId optional value unset to indicate wildcard CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, aNeededPrivilege); return (err == CHIP_NO_ERROR); @@ -555,7 +555,7 @@ static bool CanAccessEvent(const Access::SubjectDescriptor & aSubjectDescriptor, { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId, - .requestType = Access::RequestType::kEventReadOrSubscribeRequest, + .requestType = Access::RequestType::kEventReadRequest, .entityId = aPath.mEventId }; CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, RequiredPrivilege::ForReadEvent(aPath)); return (err == CHIP_NO_ERROR); diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 01d47a47cb7184..a1d9a811a1b1e7 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -436,6 +436,12 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/PresetStructWithOwnedMembers.h", "${_app_root}/clusters/${cluster}/thermostat-delegate.h", ] + } else if (cluster == "access-control-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/ArlEncoder.cpp", + "${_app_root}/clusters/${cluster}/ArlEncoder.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/clusters/access-control-server/ArlEncoder.cpp b/src/app/clusters/access-control-server/ArlEncoder.cpp new file mode 100644 index 00000000000000..810c7345a13c7e --- /dev/null +++ b/src/app/clusters/access-control-server/ArlEncoder.cpp @@ -0,0 +1,145 @@ +/* + * + * 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 "ArlEncoder.h" + +using namespace chip; +using namespace chip::app; +using namespace chip::Access; + +using Entry = AccessRestrictionProvider::Entry; +using EntryListener = AccessRestrictionProvider::Listener; +using StagingRestrictionType = Clusters::AccessControl::AccessRestrictionTypeEnum; +using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; + +namespace { + +CHIP_ERROR StageEntryRestrictions(const std::vector & source, + StagingRestriction destination[], size_t destinationCount) +{ + size_t count = source.size(); + if (count > 0 && count <= destinationCount) + { + for (size_t i = 0; i < count; i++) + { + const auto & restriction = source[i]; + ReturnErrorOnFailure(ArlEncoder::Convert(restriction.restrictionType, destination[i].type)); + + if (restriction.id.HasValue()) + { + destination[i].id.SetNonNull(restriction.id.Value()); + } + } + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return CHIP_NO_ERROR; +} + +} // namespace + +namespace chip { +namespace app { + +CHIP_ERROR ArlEncoder::Convert(Clusters::AccessControl::AccessRestrictionTypeEnum from, + Access::AccessRestrictionProvider::Type & to) +{ + switch (from) + { + case StagingRestrictionType::kAttributeAccessForbidden: + to = AccessRestrictionProvider::Type::kAttributeAccessForbidden; + break; + case StagingRestrictionType::kAttributeWriteForbidden: + to = AccessRestrictionProvider::Type::kAttributeWriteForbidden; + break; + case StagingRestrictionType::kCommandForbidden: + to = AccessRestrictionProvider::Type::kCommandForbidden; + break; + case StagingRestrictionType::kEventForbidden: + to = AccessRestrictionProvider::Type::kEventForbidden; + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlEncoder::Convert(Access::AccessRestrictionProvider::Type from, + Clusters::AccessControl::AccessRestrictionTypeEnum & to) +{ + switch (from) + { + case AccessRestrictionProvider::Type::kAttributeAccessForbidden: + to = StagingRestrictionType::kAttributeAccessForbidden; + break; + case AccessRestrictionProvider::Type::kAttributeWriteForbidden: + to = StagingRestrictionType::kAttributeWriteForbidden; + break; + case AccessRestrictionProvider::Type::kCommandForbidden: + to = StagingRestrictionType::kCommandForbidden; + break; + case AccessRestrictionProvider::Type::kEventForbidden: + to = StagingRestrictionType::kEventForbidden; + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const +{ + ReturnErrorOnFailure(Stage()); + ReturnErrorOnFailure(mStagingEntry.Encode(writer, tag)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const +{ + ReturnErrorOnFailure(Stage()); + ReturnErrorOnFailure(mStagingEntry.EncodeForRead(writer, tag, fabric)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Stage() const +{ + mStagingEntry.endpoint = mEntry.endpointNumber; + mStagingEntry.cluster = mEntry.clusterId; + ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, + sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); + mStagingEntry.restrictions = Span(mStagingRestrictions, mEntry.restrictions.size()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlEncoder::EncodableEntry::Stage() const +{ + mStagingEntry.fabricIndex = mEntry.fabricIndex; + mStagingEntry.endpoint = mEntry.endpointNumber; + mStagingEntry.cluster = mEntry.clusterId; + ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, + sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); + mStagingEntry.restrictions = Span(mStagingRestrictions, mEntry.restrictions.size()); + + return CHIP_NO_ERROR; +} + +} // namespace app +} // namespace chip diff --git a/src/app/clusters/access-control-server/ArlEncoder.h b/src/app/clusters/access-control-server/ArlEncoder.h new file mode 100644 index 00000000000000..8050bf4d739db6 --- /dev/null +++ b/src/app/clusters/access-control-server/ArlEncoder.h @@ -0,0 +1,113 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +/** + * This class provides facilities for converting between access restriction + * entries (as used by the system module) and access restriction entries (as used + * by the generated cluster code). + */ +class ArlEncoder +{ +public: + ArlEncoder() = default; + ~ArlEncoder() = default; + + static CHIP_ERROR Convert(Clusters::AccessControl::AccessRestrictionTypeEnum from, + Access::AccessRestrictionProvider::Type & to); + + static CHIP_ERROR Convert(Access::AccessRestrictionProvider::Type from, + Clusters::AccessControl::AccessRestrictionTypeEnum & to); + + /** + * Used for encoding commissionable access restriction entries. + * + * Typically used temporarily on the stack to encode: + * - source: system level access restriction entry + * - staging: generated cluster level code + */ + class CommissioningEncodableEntry + { + using Entry = Access::AccessRestrictionProvider::Entry; + using StagingEntry = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::Type; + using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; + + public: + CommissioningEncodableEntry(const Entry & entry) : mEntry(entry) {} + + /** + * Encode the constructor-provided entry into the TLV writer. + */ + CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; + + static constexpr bool kIsFabricScoped = false; + + private: + CHIP_ERROR Stage() const; + + Entry mEntry; + mutable StagingEntry mStagingEntry; + mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; + }; + + /** + * Used for encoding access restriction entries. + * + * Typically used temporarily on the stack to encode: + * - source: system level access restriction entry + * - staging: generated cluster level code + */ + class EncodableEntry + { + using Entry = Access::AccessRestrictionProvider::Entry; + using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type; + using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; + + public: + EncodableEntry(const Entry & entry) : mEntry(entry) {} + + /** + * Encode the constructor-provided entry into the TLV writer. + */ + CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const; + + FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; } + + static constexpr bool kIsFabricScoped = true; + + private: + /** + * Constructor-provided entry is staged into a staging entry. + */ + CHIP_ERROR Stage() const; + + Entry mEntry; + mutable StagingEntry mStagingEntry; + mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; + }; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 321f7aa92a483a..295b5535df2f95 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -17,6 +17,11 @@ #include +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include "ArlEncoder.h" +#include +#endif + #include #include @@ -25,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +47,10 @@ using Entry = AccessControl::Entry; using EntryListener = AccessControl::EntryListener; using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type; +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +using ArlReviewEvent = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type; +#endif + // TODO(#13590): generated code doesn't automatically handle max length so do it manually constexpr int kExtensionDataMaxLength = 128; @@ -48,7 +58,12 @@ constexpr uint16_t kClusterRevision = 1; namespace { -class AccessControlAttribute : public AttributeAccessInterface, public EntryListener +class AccessControlAttribute : public AttributeAccessInterface, + public AccessControl::EntryListener +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + , + public AccessRestrictionProvider::Listener +#endif { public: AccessControlAttribute() : AttributeAccessInterface(Optional(0), AccessControlCluster::Id) {} @@ -64,8 +79,17 @@ class AccessControlAttribute : public AttributeAccessInterface, public EntryList CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override; public: - void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry, - ChangeType changeType) override; + void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, + const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + void MarkCommissioningRestrictionListChanged() override; + + void MarkRestrictionListChanged(FabricIndex fabricIndex) override; + + void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional instruction, + Optional redirectUrl) override; +#endif private: /// Business logic implementation of write, returns generic CHIP_ERROR. @@ -78,6 +102,10 @@ class AccessControlAttribute : public AttributeAccessInterface, public EntryList CHIP_ERROR ReadExtension(AttributeValueEncoder & aEncoder); CHIP_ERROR WriteAcl(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); CHIP_ERROR WriteExtension(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + CHIP_ERROR ReadCommissioningArl(AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadArl(AttributeValueEncoder & aEncoder); +#endif } sAttribute; CHIP_ERROR LogExtensionChangedEvent(const AccessControlCluster::Structs::AccessControlExtensionStruct::Type & item, @@ -114,8 +142,8 @@ CHIP_ERROR CheckExtensionEntryDataFormat(const ByteSpan & data) TLV::TLVReader reader; reader.Init(data); - auto containerType = chip::TLV::kTLVType_List; - err = reader.Next(containerType, chip::TLV::AnonymousTag()); + auto containerType = TLV::kTLVType_List; + err = reader.Next(containerType, TLV::AnonymousTag()); VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError)); err = reader.EnterContainer(containerType); @@ -123,7 +151,7 @@ CHIP_ERROR CheckExtensionEntryDataFormat(const ByteSpan & data) while ((err = reader.Next()) == CHIP_NO_ERROR) { - VerifyOrReturnError(chip::TLV::IsProfileTag(reader.GetTag()), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + VerifyOrReturnError(TLV::IsProfileTag(reader.GetTag()), CHIP_IM_GLOBAL_STATUS(ConstraintError)); } VerifyOrReturnError(err == CHIP_END_OF_TLV, CHIP_IM_GLOBAL_STATUS(ConstraintError)); @@ -159,6 +187,12 @@ CHIP_ERROR AccessControlAttribute::ReadImpl(const ConcreteReadAttributePath & aP ReturnErrorOnFailure(GetAccessControl().GetMaxEntriesPerFabric(value)); return aEncoder.Encode(static_cast(value)); } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + case AccessControlCluster::Attributes::CommissioningARL::Id: + return ReadCommissioningArl(aEncoder); + case AccessControlCluster::Attributes::Arl::Id: + return ReadArl(aEncoder); +#endif case AccessControlCluster::Attributes::ClusterRevision::Id: return aEncoder.Encode(kClusterRevision); } @@ -375,7 +409,7 @@ CHIP_ERROR AccessControlAttribute::WriteExtension(const ConcreteDataAttributePat } void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, - const Entry * entry, ChangeType changeType) + const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) { // NOTE: If the entry was changed internally by the system (e.g. creating // entries at startup from persistent storage, or deleting entries when a @@ -389,11 +423,11 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes CHIP_ERROR err; AclEvent event{ .changeType = ChangeTypeEnum::kChanged, .fabricIndex = subjectDescriptor->fabricIndex }; - if (changeType == ChangeType::kAdded) + if (changeType == AccessControl::EntryListener::ChangeType::kAdded) { event.changeType = ChangeTypeEnum::kAdded; } - else if (changeType == ChangeType::kRemoved) + else if (changeType == AccessControl::EntryListener::ChangeType::kRemoved) { event.changeType = ChangeTypeEnum::kRemoved; } @@ -428,6 +462,86 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes ChipLogError(DataManagement, "AccessControlCluster: event failed %" CHIP_ERROR_FORMAT, err.Format()); } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder) +{ + auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); + + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + if (accessRestrictionProvider != nullptr) + { + auto entries = accessRestrictionProvider->GetCommissioningEntries(); + + for (auto & entry : entries) + { + ArlEncoder::CommissioningEncodableEntry encodableEntry(entry); + ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + } + } + return CHIP_NO_ERROR; + }); +} + +CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) +{ + auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); + + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + if (accessRestrictionProvider != nullptr) + { + for (const auto & info : Server::GetInstance().GetFabricTable()) + { + auto fabric = info.GetFabricIndex(); + // get entries for fabric + std::vector entries; + ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries)); + for (const auto & entry : entries) + { + ArlEncoder::EncodableEntry encodableEntry(entry); + ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + } + } + } + return CHIP_NO_ERROR; + }); +} +void AccessControlAttribute::MarkCommissioningRestrictionListChanged() +{ + MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, + AccessControlCluster::Attributes::CommissioningARL::Id); +} + +void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex) +{ + MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id); +} + +void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, + Optional instruction, Optional redirectUrl) +{ + CHIP_ERROR err; + ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex }; + + if (instruction.HasValue()) + { + event.instruction.SetNonNull(instruction.Value()); + } + + if (redirectUrl.HasValue()) + { + event.redirectURL.SetNonNull(redirectUrl.Value()); + } + + EventNumber eventNumber; + SuccessOrExit(err = LogEvent(event, kRootEndpointId, eventNumber)); + + return; + +exit: + ChipLogError(DataManagement, "AccessControlCluster: review event failed: %" CHIP_ERROR_FORMAT, err.Format()); +} +#endif + CHIP_ERROR ChipErrorToImErrorMap(CHIP_ERROR err) { // Map some common errors into an underlying IM error @@ -481,4 +595,88 @@ void MatterAccessControlPluginServerInitCallback() AttributeAccessInterfaceRegistry::Instance().Register(&sAttribute); GetAccessControl().AddEntryListener(sAttribute); + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); + if (accessRestrictionProvider != nullptr) + { + accessRestrictionProvider->AddListener(sAttribute); + } +#endif +} + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( + CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Clusters::AccessControl::Commands::ReviewFabricRestrictions::DecodableType & commandData) +{ + if (commandPath.mEndpointId != kRootEndpointId) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); + return true; + } + + uint64_t token; + std::vector entries; + auto entryIter = commandData.arl.begin(); + while (entryIter.Next()) + { + AccessRestrictionProvider::Entry entry; + entry.fabricIndex = commandObj->GetAccessingFabricIndex(); + entry.endpointNumber = entryIter.GetValue().endpoint; + entry.clusterId = entryIter.GetValue().cluster; + + auto restrictionIter = entryIter.GetValue().restrictions.begin(); + while (restrictionIter.Next()) + { + AccessRestrictionProvider::Restriction restriction; + if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); + return true; + } + + if (!restrictionIter.GetValue().id.IsNull()) + { + restriction.id.SetValue(restrictionIter.GetValue().id.Value()); + } + entry.restrictions.push_back(restriction); + } + + if (restrictionIter.GetStatus() != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); + return true; + } + + entries.push_back(entry); + } + + if (entryIter.GetStatus() != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); + return true; + } + + CHIP_ERROR err = GetAccessControl().GetAccessRestrictionProvider()->RequestFabricRestrictionReview( + commandObj->GetAccessingFabricIndex(), entries, token); + + if (err == CHIP_NO_ERROR) + { + Clusters::AccessControl::Commands::ReviewFabricRestrictionsResponse::Type response; + response.token = token; + commandObj->AddResponse(commandPath, response); + } + else + { + ChipLogError(DataManagement, "AccessControlCluster: restriction review failed: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + + return true; } +#endif diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 6d79e9e7b34af0..99d038a8c35471 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -341,7 +341,7 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = current->mValue.mEventId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path); diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 401356d7b753a4..58524c3a648aab 100644 --- a/src/app/server/BUILD.gn +++ b/src/app/server/BUILD.gn @@ -13,6 +13,7 @@ # limitations under the License. import("//build_overrides/chip.gni") +import("${chip_root}/src/access/access.gni") import("${chip_root}/src/app/common_flags.gni") import("${chip_root}/src/app/icd/icd.gni") diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 22cd274ba87e39..426e3c686572c0 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -177,6 +177,13 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) SuccessOrExit(err = mAccessControl.Init(initParams.accessDelegate, sDeviceTypeResolver)); Access::SetAccessControl(mAccessControl); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (initParams.accessRestrictionProvider != nullptr) + { + mAccessControl.SetAccessRestrictionProvider(initParams.accessRestrictionProvider); + } +#endif + mAclStorage = initParams.aclStorage; SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 2f6126a4ace635..27c850563f6e98 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -163,6 +163,13 @@ struct ServerInitParams // ACL storage: MUST be injected. Used to store ACL entries in persistent storage. Must NOT // be initialized before being provided. app::AclStorage * aclStorage = nullptr; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce + // access restrictions that are managed by the device. + Access::AccessRestrictionProvider * accessRestrictionProvider = nullptr; +#endif + // Network native params can be injected depending on the // selected Endpoint implementation void * endpointNativeParams = nullptr; diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index b847addbe10773..f999cf7a4f9a9d 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -71,6 +71,44 @@ constexpr size_t IndexInfoTLVMaxSize() return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1); } +CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert, + const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, + FabricIndex * outFabricIndex) +{ + VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR err = CHIP_ERROR_INTERNAL; + + Crypto::P256Keypair injectedOpKey; + Crypto::P256SerializedKeypair injectedOpKeysSerialized; + + Crypto::P256Keypair * opKey = nullptr; + if (!opKeySpan.empty()) + { + VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT); + + memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size()); + SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size())); + SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized)); + opKey = &injectedOpKey; + } + + SuccessOrExit(err = fabricTable.AddNewPendingTrustedRootCert(rootCert)); + SuccessOrExit(err = + fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, + /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); + if (!leavePending) + { + SuccessOrExit(err = fabricTable.CommitPendingFabricData()); + } +exit: + if (err != CHIP_NO_ERROR) + { + fabricTable.RevertPendingFabricData(); + } + return err; +} + } // anonymous namespace CHIP_ERROR FabricInfo::Init(const FabricInfo::InitParams & initParams) @@ -695,34 +733,14 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabr CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) { - VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - - CHIP_ERROR err = CHIP_ERROR_INTERNAL; - - Crypto::P256Keypair injectedOpKey; - Crypto::P256SerializedKeypair injectedOpKeysSerialized; - - Crypto::P256Keypair * opKey = nullptr; - if (!opKeySpan.empty()) - { - VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT); - - memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size()); - SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size())); - SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized)); - opKey = &injectedOpKey; - } + return AddNewFabricForTestInternal(*this, /*leavePending=*/false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); +} - SuccessOrExit(err = AddNewPendingTrustedRootCert(rootCert)); - SuccessOrExit(err = AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, - /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); - SuccessOrExit(err = CommitPendingFabricData()); -exit: - if (err != CHIP_NO_ERROR) - { - RevertPendingFabricData(); - } - return err; +CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, + const ByteSpan & nocCert, const ByteSpan & opKeySpan, + FabricIndex * outFabricIndex) +{ + return AddNewFabricForTestInternal(*this, /*leavePending=*/true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); } /* @@ -1546,6 +1564,16 @@ bool FabricTable::SetPendingDataFabricIndex(FabricIndex fabricIndex) return isLegal; } +FabricIndex FabricTable::GetPendingNewFabricIndex() const +{ + if (mStateFlags.Has(StateFlags::kIsAddPending)) + { + return mFabricIndexWithPendingState; + } + + return kUndefinedFabricIndex; +} + CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional fabricIndex, MutableByteSpan & outputCsr) { // We can only manage commissionable pending fail-safe state if we have a keystore diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index aef60f6a17fbbe..af90d781283963 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -736,6 +736,18 @@ class DLL_EXPORT FabricTable */ bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const; + /** + * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex if none are pending. + * + * A newly-added fabric is pending if AddNOC has been previously called successfully but the + * fabric is not yet fully committed by CommissioningComplete. + * + * NOTE: that this never returns a value other than kUndefinedFabricIndex when UpdateNOC is pending. + * + * @return the fabric index of the pending fabric, or kUndefinedFabricIndex if no fabrics are pending. + */ + FabricIndex GetPendingNewFabricIndex() const; + /** * @brief Returns the operational keystore. This is used for * CASE and the only way the keystore should be used. @@ -968,6 +980,11 @@ class DLL_EXPORT FabricTable CHIP_ERROR AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, FabricIndex * outFabricIndex); + // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will + // get copied (directly) into the fabric table. The fabric will NOT be committed, and will remain pending. + CHIP_ERROR AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, + const ByteSpan & opKeySpan, FabricIndex * outFabricIndex); + // Same as AddNewFabricForTest, but ignore if we are colliding with same , so // that a single fabric table can have N nodes for same fabric. This usually works, but is bad form. CHIP_ERROR AddNewFabricForTestIgnoringCollisions(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, diff --git a/src/credentials/tests/TestFabricTable.cpp b/src/credentials/tests/TestFabricTable.cpp index c5b78fc13e5b4f..0d34f48bce6599 100644 --- a/src/credentials/tests/TestFabricTable.cpp +++ b/src/credentials/tests/TestFabricTable.cpp @@ -551,6 +551,7 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) FabricTable & fabricTable = fabricTableHolder.GetFabricTable(); EXPECT_EQ(fabricTable.FabricCount(), 0); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); { FabricIndex nextFabricIndex = kUndefinedFabricIndex; @@ -604,6 +605,7 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_NO_ERROR); EXPECT_TRUE(fetchedSpan.data_equal(rcac)); } + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); FabricIndex newFabricIndex = kUndefinedFabricIndex; bool keyIsExternallyOwned = true; @@ -614,6 +616,11 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) CHIP_NO_ERROR); EXPECT_EQ(newFabricIndex, 1); EXPECT_EQ(fabricTable.FabricCount(), 1); + + // After adding the pending new fabric (equivalent of AddNOC processing), the new + // fabric must be pending. + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), 1); + { // No more pending root cert; it's associated with a fabric now. MutableByteSpan fetchedSpan{ rcacBuf }; @@ -661,6 +668,9 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) EXPECT_EQ(nextFabricIndex, 2); } + // Fabric can't be pending anymore. + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); + // Validate contents const auto * fabricInfo = fabricTable.FindFabricWithIndex(1); ASSERT_NE(fabricInfo, nullptr); @@ -732,12 +742,16 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) } EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); + FabricIndex newFabricIndex = kUndefinedFabricIndex; EXPECT_EQ(fabricTable.FabricCount(), 1); EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR); EXPECT_EQ(fabricTable.FabricCount(), 2); EXPECT_EQ(newFabricIndex, 2); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), 2); + // No storage yet EXPECT_EQ(storage.GetNumKeys(), numStorageAfterFirstAdd); // Next fabric index has not been updated yet. @@ -1897,6 +1911,8 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe) uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size]; MutableByteSpan csrSpan{ csrBuf }; + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); + // Make sure to tag fabric index to pending opkey: otherwise the UpdateNOC fails EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast(1)), csrSpan), CHIP_NO_ERROR); @@ -1908,6 +1924,7 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe) EXPECT_EQ(fabricTable.FabricCount(), 1); EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}), CHIP_NO_ERROR); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); EXPECT_EQ(fabricTable.FabricCount(), 1); // No storage yet @@ -1936,6 +1953,7 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe) // Revert, should see Node ID 999 again fabricTable.RevertPendingFabricData(); EXPECT_EQ(fabricTable.FabricCount(), 1); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd); diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 7600b4eaa4c315..8936cc19c4f76d 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1206,6 +1206,27 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; "Please enable at least one of CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT or CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT" #endif +/** + * @def CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC + * + * Defines the maximum number of access restriction list entries per + * fabric in the access control code's ARL attribute. + */ +#ifndef CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC +#define CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC 10 +#endif + +/** + * @def CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY + * + * Defines the maximum number of access restrictions for each entry + * in the ARL attribute (each entry is for a specific cluster on an + * endpoint on a fabric). + */ +#ifndef CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY +#define CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY 10 +#endif + /** * @def CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE * diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp index 68b6ed2852d377..6390e4eca1920e 100644 --- a/src/messaging/tests/TestReliableMessageProtocol.cpp +++ b/src/messaging/tests/TestReliableMessageProtocol.cpp @@ -92,6 +92,12 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat System::PacketBufferHandle && buffer) override { IsOnMessageReceivedCalled = true; + + if (ec->HasSessionHandle() && ec->GetSessionHolder()->IsSecureSession()) + { + mLastSubjectDescriptor = ec->GetSessionHolder()->AsSecureSession()->GetSubjectDescriptor(); + } + if (payloadHeader.IsAckMsg()) { mReceivedPiggybackAck = true; @@ -125,6 +131,7 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat EXPECT_EQ(buffer->TotalLength(), sizeof(PAYLOAD)); EXPECT_EQ(memcmp(buffer->Start(), PAYLOAD, buffer->TotalLength()), 0); + return CHIP_NO_ERROR; } @@ -151,6 +158,8 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat } } + Access::SubjectDescriptor mLastSubjectDescriptor{}; + bool IsOnMessageReceivedCalled = false; bool mReceivedPiggybackAck = false; bool mRetainExchange = false; @@ -1830,9 +1839,12 @@ TEST_F(TestReliableMessageProtocol, CheckApplicationResponseDelayed) EXPECT_EQ(loopback.mSentMessageCount, kMaxMRPTransmits); EXPECT_EQ(loopback.mDroppedMessageCount, kMaxMRPTransmits - 1); EXPECT_EQ(rm->TestGetCountRetransTable(), 1); // We have no ack yet. - EXPECT_TRUE(mockReceiver.IsOnMessageReceivedCalled); // Other side got the message. + ASSERT_TRUE(mockReceiver.IsOnMessageReceivedCalled); // Other side got the message. EXPECT_FALSE(mockSender.IsOnMessageReceivedCalled); // We did not get a response. + // It was not a commissioning CASE session so that is lined-up properly. + EXPECT_FALSE(mockReceiver.mLastSubjectDescriptor.isCommissioning); + // Ensure there will be no more weirdness with acks and that our MRP timer is restarted properly. mockReceiver.SetDropAckResponse(false); diff --git a/src/transport/SecureSession.cpp b/src/transport/SecureSession.cpp index c96f9cdf908756..7694df2e2e3aca 100644 --- a/src/transport/SecureSession.cpp +++ b/src/transport/SecureSession.cpp @@ -160,10 +160,11 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const Access::SubjectDescriptor subjectDescriptor; if (IsOperationalNodeId(mPeerNodeId)) { - subjectDescriptor.authMode = Access::AuthMode::kCase; - subjectDescriptor.subject = mPeerNodeId; - subjectDescriptor.cats = mPeerCATs; - subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.authMode = Access::AuthMode::kCase; + subjectDescriptor.subject = mPeerNodeId; + subjectDescriptor.cats = mPeerCATs; + subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.isCommissioning = IsCommissioningSession(); } else if (IsPAKEKeyId(mPeerNodeId)) { @@ -171,9 +172,10 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const // Initiator (aka commissioner) leaves subject descriptor unfilled. if (GetCryptoContext().IsResponder()) { - subjectDescriptor.authMode = Access::AuthMode::kPase; - subjectDescriptor.subject = mPeerNodeId; - subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.authMode = Access::AuthMode::kPase; + subjectDescriptor.subject = mPeerNodeId; + subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.isCommissioning = IsCommissioningSession(); } } else @@ -183,6 +185,24 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const return subjectDescriptor; } +bool SecureSession::IsCommissioningSession() const +{ + // PASE session is always a commissioning session. + if (IsPASESession()) + { + return true; + } + + // CASE session is a commissioning session if it was marked as such. + // The SessionManager is what keeps track. + if (IsCASESession() && mIsCaseCommissioningSession) + { + return true; + } + + return false; +} + void SecureSession::Retain() { #if CHIP_CONFIG_SECURE_SESSION_REFCOUNT_LOGGING diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h index fe70d6714e4a0f..ba586a3095e3dd 100644 --- a/src/transport/SecureSession.h +++ b/src/transport/SecureSession.h @@ -156,6 +156,8 @@ class SecureSession : public Session, public ReferenceCountedStart(), msg->TotalLength())); CHIP_TRACE_MESSAGE_RECEIVED(payloadHeader, packetHeader, secureSession, peerAddress, msg->Start(), msg->TotalLength()); + + // Always recompute whether a message is for a commissioning session based on the latest knowledge of + // the fabric table. + if (secureSession->IsCASESession()) + { + secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() == + mFabricTable->GetPendingNewFabricIndex()); + } mCB->OnMessageReceived(packetHeader, payloadHeader, session.Value(), isDuplicate, std::move(msg)); } else diff --git a/src/transport/tests/TestSessionManager.cpp b/src/transport/tests/TestSessionManager.cpp index 154071a56418c5..dfe3ca4fd5c5ff 100644 --- a/src/transport/tests/TestSessionManager.cpp +++ b/src/transport/tests/TestSessionManager.cpp @@ -28,6 +28,7 @@ #define CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API // Up here in case some other header // includes SessionManager.h indirectly +#include #include #include #include @@ -112,10 +113,12 @@ class TestSessMgrCallback : public SessionMessageDelegate } ReceiveHandlerCallCount++; + lastSubjectDescriptor = session->GetSubjectDescriptor(); } int ReceiveHandlerCallCount = 0; bool LargeMessageSent = false; + Access::SubjectDescriptor lastSubjectDescriptor{}; }; class TestSessionManager : public ::testing::Test @@ -141,7 +144,7 @@ TEST_F(TestSessionManager, CheckSimpleInitTest) &fabricTableHolder.GetFabricTable(), sessionKeystore)); } -TEST_F(TestSessionManager, CheckMessageTest) +TEST_F(TestSessionManager, CheckMessageOverPaseTest) { uint16_t payload_len = sizeof(PAYLOAD); @@ -213,7 +216,10 @@ TEST_F(TestSessionManager, CheckMessageTest) EXPECT_EQ(err, CHIP_NO_ERROR); mContext.DrainAndServiceIO(); - EXPECT_EQ(callback.ReceiveHandlerCallCount, 1); + ASSERT_EQ(callback.ReceiveHandlerCallCount, 1); + + // This was a PASE session so we expect the subject descriptor to indicate it's for commissioning. + EXPECT_TRUE(callback.lastSubjectDescriptor.isCommissioning); // Let's send the max sized message and make sure it is received chip::System::PacketBufferHandle large_buffer = chip::MessagePacketBuffer::NewWithData(LARGE_PAYLOAD, kMaxAppMessageLen); From 4c753bee030227528bb9483bc66ac660abfc770c Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Tue, 27 Aug 2024 20:44:16 +0200 Subject: [PATCH 02/11] Adding TimeoutMs to KeepActive command (#35154) --- examples/placeholder/linux/apps/app1/config.matter | 1 + examples/placeholder/linux/apps/app2/config.matter | 1 + .../chip/bridged-device-basic-information.xml | 3 ++- src/controller/data_model/controller-clusters.matter | 1 + .../java/chip/devicecontroller/ChipClusters.java | 10 +++++++--- .../java/chip/devicecontroller/ClusterIDMapping.java | 2 +- .../java/chip/devicecontroller/ClusterInfoMapping.java | 5 +++++ .../clusters/BridgedDeviceBasicInformationCluster.kt | 9 ++++++++- src/controller/python/chip/clusters/CHIPClusters.py | 1 + src/controller/python/chip/clusters/Objects.py | 2 ++ .../CHIP/zap-generated/MTRCommandPayloadsObjc.h | 2 ++ .../CHIP/zap-generated/MTRCommandPayloadsObjc.mm | 8 +++++++- .../app-common/zap-generated/cluster-objects.cpp | 5 +++++ .../app-common/zap-generated/cluster-objects.h | 3 +++ .../chip-tool/zap-generated/cluster/Commands.h | 1 + .../zap-generated/cluster/Commands.h | 6 ++++++ 16 files changed, 53 insertions(+), 7 deletions(-) diff --git a/examples/placeholder/linux/apps/app1/config.matter b/examples/placeholder/linux/apps/app1/config.matter index 5cc4c47092ba77..a3a313d1c41b70 100644 --- a/examples/placeholder/linux/apps/app1/config.matter +++ b/examples/placeholder/linux/apps/app1/config.matter @@ -2704,6 +2704,7 @@ cluster BridgedDeviceBasicInformation = 57 { request struct KeepActiveRequest { int32u stayActiveDuration = 0; + int32u timeoutMs = 1; } /** The server SHALL attempt to keep the devices specified active for StayActiveDuration milliseconds when they are next active. */ diff --git a/examples/placeholder/linux/apps/app2/config.matter b/examples/placeholder/linux/apps/app2/config.matter index cee95d2d1cfa7a..e1920f474f61bc 100644 --- a/examples/placeholder/linux/apps/app2/config.matter +++ b/examples/placeholder/linux/apps/app2/config.matter @@ -2661,6 +2661,7 @@ cluster BridgedDeviceBasicInformation = 57 { request struct KeepActiveRequest { int32u stayActiveDuration = 0; + int32u timeoutMs = 1; } /** The server SHALL attempt to keep the devices specified active for StayActiveDuration milliseconds when they are next active. */ diff --git a/src/app/zap-templates/zcl/data-model/chip/bridged-device-basic-information.xml b/src/app/zap-templates/zcl/data-model/chip/bridged-device-basic-information.xml index b33ccb740f9a54..ef2629c3574701 100644 --- a/src/app/zap-templates/zcl/data-model/chip/bridged-device-basic-information.xml +++ b/src/app/zap-templates/zcl/data-model/chip/bridged-device-basic-information.xml @@ -97,7 +97,8 @@ limitations under the License. The server SHALL attempt to keep the devices specified active for StayActiveDuration milliseconds when they are next active. - + + diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index 6fa99d2558bf0f..e9615e98f9c90a 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -2587,6 +2587,7 @@ cluster BridgedDeviceBasicInformation = 57 { request struct KeepActiveRequest { int32u stayActiveDuration = 0; + int32u timeoutMs = 1; } /** The server SHALL attempt to keep the devices specified active for StayActiveDuration milliseconds when they are next active. */ diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java index cf344db593f46a..5d932a2510945b 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java @@ -15197,11 +15197,11 @@ public long initWithDevice(long devicePtr, int endpointId) { return 0L; } - public void keepActive(DefaultClusterCallback callback, Long stayActiveDuration) { - keepActive(callback, stayActiveDuration, 0); + public void keepActive(DefaultClusterCallback callback, Long stayActiveDuration, Long timeoutMs) { + keepActive(callback, stayActiveDuration, timeoutMs, 0); } - public void keepActive(DefaultClusterCallback callback, Long stayActiveDuration, int timedInvokeTimeoutMs) { + public void keepActive(DefaultClusterCallback callback, Long stayActiveDuration, Long timeoutMs, int timedInvokeTimeoutMs) { final long commandId = 128L; ArrayList elements = new ArrayList<>(); @@ -15209,6 +15209,10 @@ public void keepActive(DefaultClusterCallback callback, Long stayActiveDuration, BaseTLVType stayActiveDurationtlvValue = new UIntType(stayActiveDuration); elements.add(new StructElement(stayActiveDurationFieldID, stayActiveDurationtlvValue)); + final long timeoutMsFieldID = 1L; + BaseTLVType timeoutMstlvValue = new UIntType(timeoutMs); + elements.add(new StructElement(timeoutMsFieldID, timeoutMstlvValue)); + StructType commandArgs = new StructType(elements); invoke(new InvokeCallbackImpl(callback) { @Override diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java index 30f064babb1fa5..063fe951e3e12b 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java @@ -4543,7 +4543,7 @@ public static Command value(long id) throws NoSuchFieldError { } throw new NoSuchFieldError(); } - }public enum KeepActiveCommandField {StayActiveDuration(0),; + }public enum KeepActiveCommandField {StayActiveDuration(0),TimeoutMs(1),; private final int id; KeepActiveCommandField(int id) { this.id = id; diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java index f58ac0f6e47798..e6e78c35935abf 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java @@ -24443,12 +24443,17 @@ public Map> getCommandMap() { CommandParameterInfo bridgedDeviceBasicInformationkeepActivestayActiveDurationCommandParameterInfo = new CommandParameterInfo("stayActiveDuration", Long.class, Long.class); bridgedDeviceBasicInformationkeepActiveCommandParams.put("stayActiveDuration",bridgedDeviceBasicInformationkeepActivestayActiveDurationCommandParameterInfo); + + CommandParameterInfo bridgedDeviceBasicInformationkeepActivetimeoutMsCommandParameterInfo = new CommandParameterInfo("timeoutMs", Long.class, Long.class); + bridgedDeviceBasicInformationkeepActiveCommandParams.put("timeoutMs",bridgedDeviceBasicInformationkeepActivetimeoutMsCommandParameterInfo); InteractionInfo bridgedDeviceBasicInformationkeepActiveInteractionInfo = new InteractionInfo( (cluster, callback, commandArguments) -> { ((ChipClusters.BridgedDeviceBasicInformationCluster) cluster) .keepActive((DefaultClusterCallback) callback , (Long) commandArguments.get("stayActiveDuration") + , (Long) + commandArguments.get("timeoutMs") ); }, () -> new DelegatedDefaultClusterCallback(), diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/BridgedDeviceBasicInformationCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/BridgedDeviceBasicInformationCluster.kt index 8e56362d8da349..9ae253f277ed10 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/clusters/BridgedDeviceBasicInformationCluster.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/BridgedDeviceBasicInformationCluster.kt @@ -101,7 +101,11 @@ class BridgedDeviceBasicInformationCluster( object SubscriptionEstablished : AttributeListAttributeSubscriptionState() } - suspend fun keepActive(stayActiveDuration: UInt, timedInvokeTimeout: Duration? = null) { + suspend fun keepActive( + stayActiveDuration: UInt, + timeoutMs: UInt, + timedInvokeTimeout: Duration? = null, + ) { val commandId: UInt = 128u val tlvWriter = TlvWriter() @@ -109,6 +113,9 @@ class BridgedDeviceBasicInformationCluster( val TAG_STAY_ACTIVE_DURATION_REQ: Int = 0 tlvWriter.put(ContextSpecificTag(TAG_STAY_ACTIVE_DURATION_REQ), stayActiveDuration) + + val TAG_TIMEOUT_MS_REQ: Int = 1 + tlvWriter.put(ContextSpecificTag(TAG_TIMEOUT_MS_REQ), timeoutMs) tlvWriter.endStructure() val request: InvokeRequest = diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py index 028810afd4eb77..38a8146f25a655 100644 --- a/src/controller/python/chip/clusters/CHIPClusters.py +++ b/src/controller/python/chip/clusters/CHIPClusters.py @@ -3302,6 +3302,7 @@ class ChipClusters: "commandName": "KeepActive", "args": { "stayActiveDuration": "int", + "timeoutMs": "int", }, }, }, diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index ecd84b76523bce..3b121afb95307c 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -12168,9 +12168,11 @@ def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ ClusterObjectFieldDescriptor(Label="stayActiveDuration", Tag=0, Type=uint), + ClusterObjectFieldDescriptor(Label="timeoutMs", Tag=1, Type=uint), ]) stayActiveDuration: 'uint' = 0 + timeoutMs: 'uint' = 0 class Attributes: @dataclass diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h index a5934b16975260..95b3675d94e0ae 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h @@ -3019,6 +3019,8 @@ MTR_PROVISIONALLY_AVAILABLE @interface MTRBridgedDeviceBasicInformationClusterKeepActiveParams : NSObject @property (nonatomic, copy) NSNumber * _Nonnull stayActiveDuration MTR_PROVISIONALLY_AVAILABLE; + +@property (nonatomic, copy) NSNumber * _Nonnull timeoutMs MTR_PROVISIONALLY_AVAILABLE; /** * Controls whether the command is a timed command (using Timed Invoke). * diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm index 5ae975508e9b75..00dc34a9a37ee6 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm @@ -7934,6 +7934,8 @@ - (instancetype)init if (self = [super init]) { _stayActiveDuration = @(0); + + _timeoutMs = @(0); _timedInvokeTimeoutMs = nil; _serverSideProcessingTimeout = nil; } @@ -7945,6 +7947,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone; auto other = [[MTRBridgedDeviceBasicInformationClusterKeepActiveParams alloc] init]; other.stayActiveDuration = self.stayActiveDuration; + other.timeoutMs = self.timeoutMs; other.timedInvokeTimeoutMs = self.timedInvokeTimeoutMs; other.serverSideProcessingTimeout = self.serverSideProcessingTimeout; @@ -7953,7 +7956,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone; - (NSString *)description { - NSString * descriptionString = [NSString stringWithFormat:@"<%@: stayActiveDuration:%@; >", NSStringFromClass([self class]), _stayActiveDuration]; + NSString * descriptionString = [NSString stringWithFormat:@"<%@: stayActiveDuration:%@; timeoutMs:%@; >", NSStringFromClass([self class]), _stayActiveDuration, _timeoutMs]; return descriptionString; } @@ -7968,6 +7971,9 @@ - (CHIP_ERROR)_encodeToTLVReader:(chip::System::PacketBufferTLVReader &)reader { encodableStruct.stayActiveDuration = self.stayActiveDuration.unsignedIntValue; } + { + encodableStruct.timeoutMs = self.timeoutMs.unsignedIntValue; + } auto buffer = chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSizeWithoutReserve, 0); if (buffer.IsNull()) { diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp index 84a4c6da19a641..ebe45c86ed8406 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp @@ -8227,6 +8227,7 @@ CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const { DataModel::WrappedStructEncoder encoder{ aWriter, aTag }; encoder.Encode(to_underlying(Fields::kStayActiveDuration), stayActiveDuration); + encoder.Encode(to_underlying(Fields::kTimeoutMs), timeoutMs); return encoder.Finalize(); } @@ -8248,6 +8249,10 @@ CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader) { err = DataModel::Decode(reader, stayActiveDuration); } + else if (__context_tag == to_underlying(Fields::kTimeoutMs)) + { + err = DataModel::Decode(reader, timeoutMs); + } else { } diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h index 0fe76db4e0fd6e..35f77bf4895d3d 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h @@ -11089,6 +11089,7 @@ namespace KeepActive { enum class Fields : uint8_t { kStayActiveDuration = 0, + kTimeoutMs = 1, }; struct Type @@ -11099,6 +11100,7 @@ struct Type static constexpr ClusterId GetClusterId() { return Clusters::BridgedDeviceBasicInformation::Id; } uint32_t stayActiveDuration = static_cast(0); + uint32_t timeoutMs = static_cast(0); CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; @@ -11114,6 +11116,7 @@ struct DecodableType static constexpr ClusterId GetClusterId() { return Clusters::BridgedDeviceBasicInformation::Id; } uint32_t stayActiveDuration = static_cast(0); + uint32_t timeoutMs = static_cast(0); CHIP_ERROR Decode(TLV::TLVReader & reader); }; }; // namespace KeepActive diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h index f6f5f81644a9ef..a4f8cb7298bbf9 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h @@ -3528,6 +3528,7 @@ class BridgedDeviceBasicInformationKeepActive : public ClusterCommand ClusterCommand("keep-active", credsIssuerConfig) { AddArgument("StayActiveDuration", 0, UINT32_MAX, &mRequest.stayActiveDuration); + AddArgument("TimeoutMs", 0, UINT32_MAX, &mRequest.timeoutMs); ClusterCommand::AddArguments(); } diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h index b9105c44f3769d..9c139bebe8881a 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h @@ -40189,6 +40189,9 @@ class BridgedDeviceBasicInformationKeepActive : public ClusterCommand { { #if MTR_ENABLE_PROVISIONAL AddArgument("StayActiveDuration", 0, UINT32_MAX, &mRequest.stayActiveDuration); +#endif // MTR_ENABLE_PROVISIONAL +#if MTR_ENABLE_PROVISIONAL + AddArgument("TimeoutMs", 0, UINT32_MAX, &mRequest.timeoutMs); #endif // MTR_ENABLE_PROVISIONAL ClusterCommand::AddArguments(); } @@ -40206,6 +40209,9 @@ class BridgedDeviceBasicInformationKeepActive : public ClusterCommand { params.timedInvokeTimeoutMs = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil; #if MTR_ENABLE_PROVISIONAL params.stayActiveDuration = [NSNumber numberWithUnsignedInt:mRequest.stayActiveDuration]; +#endif // MTR_ENABLE_PROVISIONAL +#if MTR_ENABLE_PROVISIONAL + params.timeoutMs = [NSNumber numberWithUnsignedInt:mRequest.timeoutMs]; #endif // MTR_ENABLE_PROVISIONAL uint16_t repeatCount = mRepeatCount.ValueOr(1); uint16_t __block responsesNeeded = repeatCount; From 7d6da7c320bb976960c0f7237a07928d0f0c6150 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Tue, 27 Aug 2024 21:07:30 +0200 Subject: [PATCH 03/11] Remove RemovedOn attribute from ECOINFO cluster (#34988) * Remove RemovedOn attribute from ECOINFO cluster * Restyled by autopep8 * Remove artifact that were only initially used to debug script locally * Move attr IDs after RemovedOn removal * Empty-Commit --------- Co-authored-by: Restyled.io --- .../src/BridgedDeviceManager.cpp | 1 - .../ecosystem-information-server.cpp | 37 +--- .../ecosystem-information-server.h | 12 -- .../chip/ecosystem-information-cluster.xml | 8 +- .../data_model/controller-clusters.matter | 5 +- .../chip/devicecontroller/ChipClusters.java | 35 +--- .../devicecontroller/ClusterIDMapping.java | 5 +- .../devicecontroller/ClusterInfoMapping.java | 21 -- .../devicecontroller/ClusterReadMapping.java | 13 +- .../clusters/EcosystemInformationCluster.kt | 119 +---------- .../CHIPAttributeTLVValueDecoder.cpp | 23 --- .../python/chip/clusters/CHIPClusters.py | 12 +- .../python/chip/clusters/Objects.py | 26 +-- .../MTRAttributeSpecifiedCheck.mm | 3 - .../MTRAttributeTLVValueDecoder.mm | 15 -- .../CHIP/zap-generated/MTRBaseClusters.h | 6 - .../CHIP/zap-generated/MTRBaseClusters.mm | 36 ---- .../CHIP/zap-generated/MTRClusterConstants.h | 5 +- .../CHIP/zap-generated/MTRClusterNames.mm | 4 - .../CHIP/zap-generated/MTRClusters.h | 2 - .../CHIP/zap-generated/MTRClusters.mm | 5 - src/python_testing/TC_ECOINFO_2_1.py | 184 ++++++++---------- src/python_testing/TC_ECOINFO_2_2.py | 65 ++++--- .../zap-generated/attributes/Accessors.cpp | 90 --------- .../zap-generated/attributes/Accessors.h | 11 -- .../zap-generated/cluster-objects.cpp | 2 - .../zap-generated/cluster-objects.h | 13 -- .../app-common/zap-generated/ids/Attributes.h | 8 +- .../zap-generated/cluster/Commands.h | 9 +- .../cluster/logging/DataModelLogger.cpp | 5 - .../zap-generated/cluster/Commands.h | 94 +-------- 31 files changed, 147 insertions(+), 727 deletions(-) diff --git a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp index 8db44c3e8bfd9b..5caa4459bf1d1e 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp +++ b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp @@ -116,7 +116,6 @@ DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); // Declare Ecosystem Information cluster attributes DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(ecosystemInformationBasicAttrs) -DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::RemovedOn::Id, EPOCH_US, kNodeLabelSize, ATTRIBUTE_MASK_NULLABLE), DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::DeviceDirectory::Id, ARRAY, kDescriptorAttributeArraySize, 0), DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::LocationDirectory::Id, ARRAY, kDescriptorAttributeArraySize, 0), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); 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 2ce82394a57464..4af2adcb533ca3 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp @@ -287,21 +287,10 @@ CHIP_ERROR EcosystemInformationServer::AddLocationInfo(EndpointId aEndpoint, con return CHIP_NO_ERROR; } -CHIP_ERROR EcosystemInformationServer::RemoveDevice(EndpointId aEndpoint, uint64_t aEpochUs) -{ - auto it = mDevicesMap.find(aEndpoint); - VerifyOrReturnError((it != mDevicesMap.end()), CHIP_ERROR_INVALID_ARGUMENT); - auto & deviceInfo = it->second; - deviceInfo.mRemovedOn.SetValue(aEpochUs); - return CHIP_NO_ERROR; -} - CHIP_ERROR EcosystemInformationServer::ReadAttribute(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { switch (aPath.mAttributeId) { - case Attributes::RemovedOn::Id: - return EncodeRemovedOnAttribute(aPath.mEndpointId, aEncoder); case Attributes::DeviceDirectory::Id: return EncodeDeviceDirectoryAttribute(aPath.mEndpointId, aEncoder); case Attributes::LocationDirectory::Id: @@ -320,28 +309,6 @@ CHIP_ERROR EcosystemInformationServer::ReadAttribute(const ConcreteReadAttribute return CHIP_NO_ERROR; } -CHIP_ERROR EcosystemInformationServer::EncodeRemovedOnAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder) -{ - auto it = mDevicesMap.find(aEndpoint); - if (it == mDevicesMap.end()) - { - // We are always going to be given a valid endpoint. If the endpoint - // doesn't exist in our map that indicate that the cluster was not - // added on this endpoint, hence UnsupportedCluster. - return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); - } - - auto & deviceInfo = it->second; - if (!deviceInfo.mRemovedOn.HasValue()) - { - aEncoder.EncodeNull(); - return CHIP_NO_ERROR; - } - - aEncoder.Encode(deviceInfo.mRemovedOn.Value()); - return CHIP_NO_ERROR; -} - CHIP_ERROR EcosystemInformationServer::EncodeDeviceDirectoryAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder) { @@ -355,7 +322,7 @@ CHIP_ERROR EcosystemInformationServer::EncodeDeviceDirectoryAttribute(EndpointId } auto & deviceInfo = it->second; - if (deviceInfo.mDeviceDirectory.empty() || deviceInfo.mRemovedOn.HasValue()) + if (deviceInfo.mDeviceDirectory.empty()) { return aEncoder.EncodeEmptyList(); } @@ -381,7 +348,7 @@ CHIP_ERROR EcosystemInformationServer::EncodeLocationStructAttribute(EndpointId } auto & deviceInfo = it->second; - if (deviceInfo.mLocationDirectory.empty() || deviceInfo.mRemovedOn.HasValue()) + if (deviceInfo.mLocationDirectory.empty()) { return aEncoder.EncodeEmptyList(); } 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 d4f4d7dc3a6fc1..3fe162181391d4 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h @@ -186,16 +186,6 @@ class EcosystemInformationServer */ CHIP_ERROR AddLocationInfo(EndpointId aEndpoint, const std::string & aLocationId, FabricIndex aFabricIndex, std::unique_ptr aLocation); - - /** - * @brief Removes device at the provided endpoint. - * - * @param aEndpoint Endpoint of the associated device that has been removed. - * @param aEpochUs Epoch time in micro seconds assoicated with when device was removed. - * @return #CHIP_NO_ERROR on success. - * @return Other CHIP_ERROR associated with issue. - */ - CHIP_ERROR RemoveDevice(EndpointId aEndpoint, uint64_t aEpochUs); // TODO(#33223) Add removal and update counterparts to AddDeviceInfo and AddLocationInfo. CHIP_ERROR ReadAttribute(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder); @@ -214,12 +204,10 @@ class EcosystemInformationServer struct DeviceInfo { - Optional mRemovedOn = NullOptional; std::vector> mDeviceDirectory; std::map> mLocationDirectory; }; - CHIP_ERROR EncodeRemovedOnAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR EncodeDeviceDirectoryAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR EncodeLocationStructAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder); diff --git a/src/app/zap-templates/zcl/data-model/chip/ecosystem-information-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/ecosystem-information-cluster.xml index 1b553eeb54faf6..7abf54ccb7942a 100644 --- a/src/app/zap-templates/zcl/data-model/chip/ecosystem-information-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/ecosystem-information-cluster.xml @@ -44,15 +44,11 @@ limitations under the License. true - - RemovedOn - - - + DeviceDirectory - + LocationDirectory diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index e9615e98f9c90a..e21a82213b661a 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -9407,9 +9407,8 @@ provisional cluster EcosystemInformation = 1872 { fabric_idx fabricIndex = 254; } - readonly attribute access(read: manage) optional nullable epoch_us removedOn = 0; - readonly attribute access(read: manage) EcosystemDeviceStruct deviceDirectory[] = 1; - readonly attribute access(read: manage) EcosystemLocationStruct locationDirectory[] = 2; + readonly attribute access(read: manage) EcosystemDeviceStruct deviceDirectory[] = 0; + readonly attribute access(read: manage) EcosystemLocationStruct locationDirectory[] = 1; readonly attribute command_id generatedCommandList[] = 65528; readonly attribute command_id acceptedCommandList[] = 65529; readonly attribute event_id eventList[] = 65530; diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java index 5d932a2510945b..8dd9332beb6885 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java @@ -60772,9 +60772,8 @@ public void onSuccess(byte[] tlv) { public static class EcosystemInformationCluster extends BaseChipCluster { public static final long CLUSTER_ID = 1872L; - private static final long REMOVED_ON_ATTRIBUTE_ID = 0L; - private static final long DEVICE_DIRECTORY_ATTRIBUTE_ID = 1L; - private static final long LOCATION_DIRECTORY_ATTRIBUTE_ID = 2L; + private static final long DEVICE_DIRECTORY_ATTRIBUTE_ID = 0L; + private static final long LOCATION_DIRECTORY_ATTRIBUTE_ID = 1L; private static final long GENERATED_COMMAND_LIST_ATTRIBUTE_ID = 65528L; private static final long ACCEPTED_COMMAND_LIST_ATTRIBUTE_ID = 65529L; private static final long EVENT_LIST_ATTRIBUTE_ID = 65530L; @@ -60792,10 +60791,6 @@ public long initWithDevice(long devicePtr, int endpointId) { return 0L; } - public interface RemovedOnAttributeCallback extends BaseAttributeCallback { - void onSuccess(@Nullable Long value); - } - public interface DeviceDirectoryAttributeCallback extends BaseAttributeCallback { void onSuccess(List value); } @@ -60820,32 +60815,6 @@ public interface AttributeListAttributeCallback extends BaseAttributeCallback { void onSuccess(List value); } - public void readRemovedOnAttribute( - RemovedOnAttributeCallback callback) { - ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, REMOVED_ON_ATTRIBUTE_ID); - - readAttribute(new ReportCallbackImpl(callback, path) { - @Override - public void onSuccess(byte[] tlv) { - @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv); - callback.onSuccess(value); - } - }, REMOVED_ON_ATTRIBUTE_ID, true); - } - - public void subscribeRemovedOnAttribute( - RemovedOnAttributeCallback callback, int minInterval, int maxInterval) { - ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, REMOVED_ON_ATTRIBUTE_ID); - - subscribeAttribute(new ReportCallbackImpl(callback, path) { - @Override - public void onSuccess(byte[] tlv) { - @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv); - callback.onSuccess(value); - } - }, REMOVED_ON_ATTRIBUTE_ID, minInterval, maxInterval); - } - public void readDeviceDirectoryAttribute( DeviceDirectoryAttributeCallback callback) { readDeviceDirectoryAttributeWithFabricFilter(callback, true); diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java index 063fe951e3e12b..829cf623e75ba0 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java @@ -17238,9 +17238,8 @@ public long getID() { } public enum Attribute { - RemovedOn(0L), - DeviceDirectory(1L), - LocationDirectory(2L), + DeviceDirectory(0L), + LocationDirectory(1L), GeneratedCommandList(65528L), AcceptedCommandList(65529L), EventList(65530L), diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java index e6e78c35935abf..fe948f9429f516 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java @@ -20222,27 +20222,6 @@ public void onError(Exception ex) { } } - public static class DelegatedEcosystemInformationClusterRemovedOnAttributeCallback implements ChipClusters.EcosystemInformationCluster.RemovedOnAttributeCallback, DelegatedClusterCallback { - private ClusterCommandCallback callback; - @Override - public void setCallbackDelegate(ClusterCommandCallback callback) { - this.callback = callback; - } - - @Override - public void onSuccess(@Nullable Long value) { - Map responseValues = new LinkedHashMap<>(); - CommandResponseInfo commandResponseInfo = new CommandResponseInfo("value", "Long"); - responseValues.put(commandResponseInfo, value); - callback.onSuccess(responseValues); - } - - @Override - public void onError(Exception ex) { - callback.onFailure(ex); - } - } - public static class DelegatedEcosystemInformationClusterDeviceDirectoryAttributeCallback implements ChipClusters.EcosystemInformationCluster.DeviceDirectoryAttributeCallback, DelegatedClusterCallback { private ClusterCommandCallback callback; @Override diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java index c96de698113955..6baa71c6886c6e 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java @@ -18678,18 +18678,7 @@ private static Map readContentAppObserverInteractionInf return result; } private static Map readEcosystemInformationInteractionInfo() { - Map result = new LinkedHashMap<>();Map readEcosystemInformationRemovedOnCommandParams = new LinkedHashMap(); - InteractionInfo readEcosystemInformationRemovedOnAttributeInteractionInfo = new InteractionInfo( - (cluster, callback, commandArguments) -> { - ((ChipClusters.EcosystemInformationCluster) cluster).readRemovedOnAttribute( - (ChipClusters.EcosystemInformationCluster.RemovedOnAttributeCallback) callback - ); - }, - () -> new ClusterInfoMapping.DelegatedEcosystemInformationClusterRemovedOnAttributeCallback(), - readEcosystemInformationRemovedOnCommandParams - ); - result.put("readRemovedOnAttribute", readEcosystemInformationRemovedOnAttributeInteractionInfo); - Map readEcosystemInformationDeviceDirectoryCommandParams = new LinkedHashMap(); + Map result = new LinkedHashMap<>();Map readEcosystemInformationDeviceDirectoryCommandParams = new LinkedHashMap(); InteractionInfo readEcosystemInformationDeviceDirectoryAttributeInteractionInfo = new InteractionInfo( (cluster, callback, commandArguments) -> { ((ChipClusters.EcosystemInformationCluster) cluster).readDeviceDirectoryAttribute( diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/EcosystemInformationCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/EcosystemInformationCluster.kt index 5dfa4a7f8067ef..0fc2806b04c628 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/clusters/EcosystemInformationCluster.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/EcosystemInformationCluster.kt @@ -38,16 +38,6 @@ class EcosystemInformationCluster( private val controller: MatterController, private val endpointId: UShort, ) { - class RemovedOnAttribute(val value: ULong?) - - sealed class RemovedOnAttributeSubscriptionState { - data class Success(val value: ULong?) : RemovedOnAttributeSubscriptionState() - - data class Error(val exception: Exception) : RemovedOnAttributeSubscriptionState() - - object SubscriptionEstablished : RemovedOnAttributeSubscriptionState() - } - class DeviceDirectoryAttribute(val value: List) sealed class DeviceDirectoryAttributeSubscriptionState { @@ -112,109 +102,8 @@ class EcosystemInformationCluster( object SubscriptionEstablished : AttributeListAttributeSubscriptionState() } - suspend fun readRemovedOnAttribute(): RemovedOnAttribute { - val ATTRIBUTE_ID: UInt = 0u - - val attributePath = - AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) - - val readRequest = ReadRequest(eventPaths = emptyList(), attributePaths = listOf(attributePath)) - - val response = controller.read(readRequest) - - if (response.successes.isEmpty()) { - logger.log(Level.WARNING, "Read command failed") - throw IllegalStateException("Read command failed with failures: ${response.failures}") - } - - logger.log(Level.FINE, "Read command succeeded") - - val attributeData = - response.successes.filterIsInstance().firstOrNull { - it.path.attributeId == ATTRIBUTE_ID - } - - requireNotNull(attributeData) { "Removedon attribute not found in response" } - - // Decode the TLV data into the appropriate type - val tlvReader = TlvReader(attributeData.data) - val decodedValue: ULong? = - if (!tlvReader.isNull()) { - if (tlvReader.isNextTag(AnonymousTag)) { - tlvReader.getULong(AnonymousTag) - } else { - null - } - } else { - tlvReader.getNull(AnonymousTag) - null - } - - return RemovedOnAttribute(decodedValue) - } - - suspend fun subscribeRemovedOnAttribute( - minInterval: Int, - maxInterval: Int, - ): Flow { - val ATTRIBUTE_ID: UInt = 0u - val attributePaths = - listOf( - AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) - ) - - val subscribeRequest: SubscribeRequest = - SubscribeRequest( - eventPaths = emptyList(), - attributePaths = attributePaths, - minInterval = Duration.ofSeconds(minInterval.toLong()), - maxInterval = Duration.ofSeconds(maxInterval.toLong()), - ) - - return controller.subscribe(subscribeRequest).transform { subscriptionState -> - when (subscriptionState) { - is SubscriptionState.SubscriptionErrorNotification -> { - emit( - RemovedOnAttributeSubscriptionState.Error( - Exception( - "Subscription terminated with error code: ${subscriptionState.terminationCause}" - ) - ) - ) - } - is SubscriptionState.NodeStateUpdate -> { - val attributeData = - subscriptionState.updateState.successes - .filterIsInstance() - .firstOrNull { it.path.attributeId == ATTRIBUTE_ID } - - requireNotNull(attributeData) { "Removedon attribute not found in Node State update" } - - // Decode the TLV data into the appropriate type - val tlvReader = TlvReader(attributeData.data) - val decodedValue: ULong? = - if (!tlvReader.isNull()) { - if (tlvReader.isNextTag(AnonymousTag)) { - tlvReader.getULong(AnonymousTag) - } else { - null - } - } else { - tlvReader.getNull(AnonymousTag) - null - } - - decodedValue?.let { emit(RemovedOnAttributeSubscriptionState.Success(it)) } - } - SubscriptionState.SubscriptionEstablished -> { - emit(RemovedOnAttributeSubscriptionState.SubscriptionEstablished) - } - } - } - } - suspend fun readDeviceDirectoryAttribute(): DeviceDirectoryAttribute { - val ATTRIBUTE_ID: UInt = 1u + val ATTRIBUTE_ID: UInt = 0u val attributePath = AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) @@ -255,7 +144,7 @@ class EcosystemInformationCluster( minInterval: Int, maxInterval: Int, ): Flow { - val ATTRIBUTE_ID: UInt = 1u + val ATTRIBUTE_ID: UInt = 0u val attributePaths = listOf( AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) @@ -313,7 +202,7 @@ class EcosystemInformationCluster( } suspend fun readLocationDirectoryAttribute(): LocationDirectoryAttribute { - val ATTRIBUTE_ID: UInt = 2u + val ATTRIBUTE_ID: UInt = 1u val attributePath = AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) @@ -354,7 +243,7 @@ class EcosystemInformationCluster( minInterval: Int, maxInterval: Int, ): Flow { - val ATTRIBUTE_ID: UInt = 2u + val ATTRIBUTE_ID: UInt = 1u val attributePaths = listOf( AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) diff --git a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp index cacd15683ab39b..b6f8a6364105c0 100644 --- a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp +++ b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp @@ -42974,29 +42974,6 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR using namespace app::Clusters::EcosystemInformation; switch (aPath.mAttributeId) { - case Attributes::RemovedOn::Id: { - using TypeInfo = Attributes::RemovedOn::TypeInfo; - TypeInfo::DecodableType cppValue; - *aError = app::DataModel::Decode(aReader, cppValue); - if (*aError != CHIP_NO_ERROR) - { - return nullptr; - } - jobject value; - if (cppValue.IsNull()) - { - value = nullptr; - } - else - { - std::string valueClassName = "java/lang/Long"; - std::string valueCtorSignature = "(J)V"; - jlong jnivalue = static_cast(cppValue.Value()); - chip::JniReferences::GetInstance().CreateBoxedObject(valueClassName.c_str(), valueCtorSignature.c_str(), - jnivalue, value); - } - return value; - } case Attributes::DeviceDirectory::Id: { using TypeInfo = Attributes::DeviceDirectory::TypeInfo; TypeInfo::DecodableType cppValue; diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py index 38a8146f25a655..925ce90cfcf3a8 100644 --- a/src/controller/python/chip/clusters/CHIPClusters.py +++ b/src/controller/python/chip/clusters/CHIPClusters.py @@ -13320,20 +13320,14 @@ class ChipClusters: }, "attributes": { 0x00000000: { - "attributeName": "RemovedOn", - "attributeId": 0x00000000, - "type": "int", - "reportable": True, - }, - 0x00000001: { "attributeName": "DeviceDirectory", - "attributeId": 0x00000001, + "attributeId": 0x00000000, "type": "", "reportable": True, }, - 0x00000002: { + 0x00000001: { "attributeName": "LocationDirectory", - "attributeId": 0x00000002, + "attributeId": 0x00000001, "type": "", "reportable": True, }, diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index 3b121afb95307c..82f257f764052c 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -47207,9 +47207,8 @@ class EcosystemInformation(Cluster): def descriptor(cls) -> ClusterObjectDescriptor: return ClusterObjectDescriptor( Fields=[ - ClusterObjectFieldDescriptor(Label="removedOn", Tag=0x00000000, Type=typing.Union[None, Nullable, uint]), - ClusterObjectFieldDescriptor(Label="deviceDirectory", Tag=0x00000001, Type=typing.List[EcosystemInformation.Structs.EcosystemDeviceStruct]), - ClusterObjectFieldDescriptor(Label="locationDirectory", Tag=0x00000002, Type=typing.List[EcosystemInformation.Structs.EcosystemLocationStruct]), + ClusterObjectFieldDescriptor(Label="deviceDirectory", Tag=0x00000000, Type=typing.List[EcosystemInformation.Structs.EcosystemDeviceStruct]), + ClusterObjectFieldDescriptor(Label="locationDirectory", Tag=0x00000001, Type=typing.List[EcosystemInformation.Structs.EcosystemLocationStruct]), ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="eventList", Tag=0x0000FFFA, Type=typing.List[uint]), @@ -47218,7 +47217,6 @@ def descriptor(cls) -> ClusterObjectDescriptor: ClusterObjectFieldDescriptor(Label="clusterRevision", Tag=0x0000FFFD, Type=uint), ]) - removedOn: 'typing.Union[None, Nullable, uint]' = None deviceDirectory: 'typing.List[EcosystemInformation.Structs.EcosystemDeviceStruct]' = None locationDirectory: 'typing.List[EcosystemInformation.Structs.EcosystemLocationStruct]' = None generatedCommandList: 'typing.List[uint]' = None @@ -47285,22 +47283,6 @@ def descriptor(cls) -> ClusterObjectDescriptor: fabricIndex: 'uint' = 0 class Attributes: - @dataclass - class RemovedOn(ClusterAttributeDescriptor): - @ChipUtility.classproperty - def cluster_id(cls) -> int: - return 0x00000750 - - @ChipUtility.classproperty - def attribute_id(cls) -> int: - return 0x00000000 - - @ChipUtility.classproperty - def attribute_type(cls) -> ClusterObjectFieldDescriptor: - return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint]) - - value: 'typing.Union[None, Nullable, uint]' = None - @dataclass class DeviceDirectory(ClusterAttributeDescriptor): @ChipUtility.classproperty @@ -47309,7 +47291,7 @@ def cluster_id(cls) -> int: @ChipUtility.classproperty def attribute_id(cls) -> int: - return 0x00000001 + return 0x00000000 @ChipUtility.classproperty def attribute_type(cls) -> ClusterObjectFieldDescriptor: @@ -47325,7 +47307,7 @@ def cluster_id(cls) -> int: @ChipUtility.classproperty def attribute_id(cls) -> int: - return 0x00000002 + return 0x00000001 @ChipUtility.classproperty def attribute_type(cls) -> ClusterObjectFieldDescriptor: diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm index 925ef9cb90f1dc..3f8df9ea33d334 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm @@ -6060,9 +6060,6 @@ static BOOL AttributeIsSpecifiedInEcosystemInformationCluster(AttributeId aAttri { using namespace Clusters::EcosystemInformation; switch (aAttributeId) { - case Attributes::RemovedOn::Id: { - return YES; - } case Attributes::DeviceDirectory::Id: { return YES; } diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm index 4b0e0bb96389b3..f26c5e29e39bf8 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm @@ -17185,21 +17185,6 @@ static id _Nullable DecodeAttributeValueForEcosystemInformationCluster(Attribute { using namespace Clusters::EcosystemInformation; switch (aAttributeId) { - case Attributes::RemovedOn::Id: { - using TypeInfo = Attributes::RemovedOn::TypeInfo; - TypeInfo::DecodableType cppValue; - *aError = DataModel::Decode(aReader, cppValue); - if (*aError != CHIP_NO_ERROR) { - return nil; - } - NSNumber * _Nullable value; - if (cppValue.IsNull()) { - value = nil; - } else { - value = [NSNumber numberWithUnsignedLongLong:cppValue.Value()]; - } - return value; - } case Attributes::DeviceDirectory::Id: { using TypeInfo = Attributes::DeviceDirectory::TypeInfo; TypeInfo::DecodableType cppValue; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h index 4dc89b0bb26417..cf8380030b57cb 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h @@ -15114,12 +15114,6 @@ MTR_PROVISIONALLY_AVAILABLE MTR_PROVISIONALLY_AVAILABLE @interface MTRBaseClusterEcosystemInformation : MTRGenericBaseCluster -- (void)readAttributeRemovedOnWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; -- (void)subscribeAttributeRemovedOnWithParams:(MTRSubscribeParams *)params - subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished - reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE; -+ (void)readAttributeRemovedOnWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - - (void)readAttributeDeviceDirectoryWithParams:(MTRReadParams * _Nullable)params completion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - (void)subscribeAttributeDeviceDirectoryWithParams:(MTRSubscribeParams *)params subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm index 2c81423cfbb46a..453ec72508abce 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm @@ -105332,42 +105332,6 @@ + (void)readAttributeClusterRevisionWithClusterStateCache:(MTRClusterStateCacheC @implementation MTRBaseClusterEcosystemInformation -- (void)readAttributeRemovedOnWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion -{ - using TypeInfo = EcosystemInformation::Attributes::RemovedOn::TypeInfo; - [self.device _readKnownAttributeWithEndpointID:self.endpointID - clusterID:@(TypeInfo::GetClusterId()) - attributeID:@(TypeInfo::GetAttributeId()) - params:nil - queue:self.callbackQueue - completion:completion]; -} - -- (void)subscribeAttributeRemovedOnWithParams:(MTRSubscribeParams * _Nonnull)params - subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished - reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler -{ - using TypeInfo = EcosystemInformation::Attributes::RemovedOn::TypeInfo; - [self.device _subscribeToKnownAttributeWithEndpointID:self.endpointID - clusterID:@(TypeInfo::GetClusterId()) - attributeID:@(TypeInfo::GetAttributeId()) - params:params - queue:self.callbackQueue - reportHandler:reportHandler - subscriptionEstablished:subscriptionEstablished]; -} - -+ (void)readAttributeRemovedOnWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion -{ - using TypeInfo = EcosystemInformation::Attributes::RemovedOn::TypeInfo; - [clusterStateCacheContainer - _readKnownCachedAttributeWithEndpointID:static_cast([endpoint unsignedShortValue]) - clusterID:TypeInfo::GetClusterId() - attributeID:TypeInfo::GetAttributeId() - queue:queue - completion:completion]; -} - - (void)readAttributeDeviceDirectoryWithParams:(MTRReadParams * _Nullable)params completion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion { using TypeInfo = EcosystemInformation::Attributes::DeviceDirectory::TypeInfo; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h index 7d161a0b4302ed..3ceca84ced8163 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h @@ -4897,9 +4897,8 @@ typedef NS_ENUM(uint32_t, MTRAttributeIDType) { MTRAttributeIDTypeClusterContentAppObserverAttributeClusterRevisionID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeClusterRevisionID, // Cluster EcosystemInformation attributes - MTRAttributeIDTypeClusterEcosystemInformationAttributeRemovedOnID MTR_PROVISIONALLY_AVAILABLE = 0x00000000, - MTRAttributeIDTypeClusterEcosystemInformationAttributeDeviceDirectoryID MTR_PROVISIONALLY_AVAILABLE = 0x00000001, - MTRAttributeIDTypeClusterEcosystemInformationAttributeLocationDirectoryID MTR_PROVISIONALLY_AVAILABLE = 0x00000002, + MTRAttributeIDTypeClusterEcosystemInformationAttributeDeviceDirectoryID MTR_PROVISIONALLY_AVAILABLE = 0x00000000, + MTRAttributeIDTypeClusterEcosystemInformationAttributeLocationDirectoryID MTR_PROVISIONALLY_AVAILABLE = 0x00000001, MTRAttributeIDTypeClusterEcosystemInformationAttributeGeneratedCommandListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeGeneratedCommandListID, MTRAttributeIDTypeClusterEcosystemInformationAttributeAcceptedCommandListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID, MTRAttributeIDTypeClusterEcosystemInformationAttributeEventListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeEventListID, diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm index f8450897d10522..894f8399c7e3c7 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm @@ -8342,10 +8342,6 @@ switch (attributeID) { // Cluster EcosystemInformation attributes - case MTRAttributeIDTypeClusterEcosystemInformationAttributeRemovedOnID: - result = @"RemovedOn"; - break; - case MTRAttributeIDTypeClusterEcosystemInformationAttributeDeviceDirectoryID: result = @"DeviceDirectory"; break; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h index b3f63ee7132564..b4153ecf56db86 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h @@ -7012,8 +7012,6 @@ MTR_PROVISIONALLY_AVAILABLE MTR_PROVISIONALLY_AVAILABLE @interface MTRClusterEcosystemInformation : MTRGenericCluster -- (NSDictionary * _Nullable)readAttributeRemovedOnWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; - - (NSDictionary * _Nullable)readAttributeDeviceDirectoryWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; - (NSDictionary * _Nullable)readAttributeLocationDirectoryWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm index 73ff54e52e36f2..780c3a49d70521 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm @@ -20000,11 +20000,6 @@ - (void)contentAppMessageWithParams:(MTRContentAppObserverClusterContentAppMessa @implementation MTRClusterEcosystemInformation -- (NSDictionary * _Nullable)readAttributeRemovedOnWithParams:(MTRReadParams * _Nullable)params -{ - return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeEcosystemInformationID) attributeID:@(MTRAttributeIDTypeClusterEcosystemInformationAttributeRemovedOnID) params:params]; -} - - (NSDictionary * _Nullable)readAttributeDeviceDirectoryWithParams:(MTRReadParams * _Nullable)params { return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeEcosystemInformationID) attributeID:@(MTRAttributeIDTypeClusterEcosystemInformationAttributeDeviceDirectoryID) params:params]; diff --git a/src/python_testing/TC_ECOINFO_2_1.py b/src/python_testing/TC_ECOINFO_2_1.py index ea9ee43b7f0569..8397402983d163 100644 --- a/src/python_testing/TC_ECOINFO_2_1.py +++ b/src/python_testing/TC_ECOINFO_2_1.py @@ -25,96 +25,87 @@ class TC_ECOINFO_2_1(MatterBaseTest): - def _validate_device_directory(self, is_removed_on_null, device_directory): + def _validate_device_directory(self, device_directory): num_of_devices = len(device_directory) - if is_removed_on_null: - asserts.assert_less_equal(num_of_devices, 256, "Too many device entries") - for device in device_directory: - # TODO do fabric index check first - if device.deviceName is not None: - asserts.assert_true(type_matches(device.deviceName, str), "DeviceName should be a string") - asserts.assert_less_equal(len(device.deviceName), 64, "DeviceName should be <= 64") - asserts.assert_true(type_matches(device.deviceNameLastEdit, uint), "DeviceNameLastEdit should be a uint") - asserts.assert_greater(device.deviceNameLastEdit, 0, "DeviceNameLastEdit must be greater than 0") - else: - asserts.assert_true(device.deviceNameLastEdit is None, - "DeviceNameLastEdit should not be provided when there is no DeviceName") - - asserts.assert_true(type_matches(device.bridgedEndpoint, uint), "BridgedEndpoint should be a uint") - asserts.assert_greater_equal(device.bridgedEndpoint, 0, "BridgedEndpoint >= 0") - asserts.assert_less_equal(device.bridgedEndpoint, 0xffff_ffff, - "BridgedEndpoint less than or equal to Invalid Endpoint value") - - asserts.assert_true(type_matches(device.originalEndpoint, uint), "OriginalEndpoint should be a uint") - asserts.assert_greater_equal(device.originalEndpoint, 0, "OriginalEndpoint >= 0") - asserts.assert_less(device.originalEndpoint, 0xffff_ffff, - "OriginalEndpoint less than or equal to Invalid Endpoint value") - - asserts.assert_true(type_matches(device.deviceTypes, list), "DeviceTypes should be a list") - asserts.assert_greater_equal(len(device.deviceTypes), 1, "DeviceTypes list must contains at least one entry") - for device_type in device.deviceTypes: - asserts.assert_true(type_matches(device_type.deviceType, uint), "DeviceType should be a uint") - # TODO what other validation can we do here to device_type.deviceType - asserts.assert_true(type_matches(device_type.revision, uint), "device type's revision should be a uint") - asserts.assert_greater_equal(device_type.revision, 1, "device type's revision must >= 1") - - asserts.assert_true(type_matches(device.uniqueLocationIDs, list), "UniqueLocationIds should be a list") - num_of_unique_location_ids = len(device.uniqueLocationIDs) - asserts.assert_less_equal(num_of_unique_location_ids, 64, "UniqueLocationIds list should be <= 64") - for location_id in device.uniqueLocationIDs: - asserts.assert_true(type_matches(location_id, str), "UniqueLocationId should be a string") - location_id_string_length = len(location_id) - asserts.assert_greater_equal(location_id_string_length, 1, - "UniqueLocationId must contain at least one character") - asserts.assert_less_equal(location_id_string_length, 64, "UniqueLocationId should be <= 64") - - asserts.assert_true(type_matches(device.uniqueLocationIDsLastEdit, uint), - "UniqueLocationIdsLastEdit should be a uint") - if num_of_unique_location_ids: - asserts.assert_greater(device.uniqueLocationIDsLastEdit, 0, "UniqueLocationIdsLastEdit must be non-zero") - else: - asserts.assert_equal(num_of_devices, 0, "Device was removed, there should be no devices in DeviceDirectory") - - def _validate_location_directory(self, is_removed_on_null, location_directory): - num_of_locations = len(location_directory) - if is_removed_on_null: - asserts.assert_less_equal(num_of_locations, 64, "Too many location entries") - for location in location_directory: - asserts.assert_true(type_matches(location.uniqueLocationID, str), "UniqueLocationId should be a string") - location_id_string_length = len(location.uniqueLocationID) + asserts.assert_less_equal(num_of_devices, 256, "Too many device entries") + for device in device_directory: + # TODO do fabric index check first + if device.deviceName is not None: + asserts.assert_true(type_matches(device.deviceName, str), "DeviceName should be a string") + asserts.assert_less_equal(len(device.deviceName), 64, "DeviceName should be <= 64") + asserts.assert_true(type_matches(device.deviceNameLastEdit, uint), "DeviceNameLastEdit should be a uint") + asserts.assert_greater(device.deviceNameLastEdit, 0, "DeviceNameLastEdit must be greater than 0") + else: + asserts.assert_true(device.deviceNameLastEdit is None, + "DeviceNameLastEdit should not be provided when there is no DeviceName") + + asserts.assert_true(type_matches(device.bridgedEndpoint, uint), "BridgedEndpoint should be a uint") + asserts.assert_greater_equal(device.bridgedEndpoint, 0, "BridgedEndpoint >= 0") + asserts.assert_less_equal(device.bridgedEndpoint, 0xffff_ffff, + "BridgedEndpoint less than or equal to Invalid Endpoint value") + + asserts.assert_true(type_matches(device.originalEndpoint, uint), "OriginalEndpoint should be a uint") + asserts.assert_greater_equal(device.originalEndpoint, 0, "OriginalEndpoint >= 0") + asserts.assert_less(device.originalEndpoint, 0xffff_ffff, + "OriginalEndpoint less than or equal to Invalid Endpoint value") + + asserts.assert_true(type_matches(device.deviceTypes, list), "DeviceTypes should be a list") + asserts.assert_greater_equal(len(device.deviceTypes), 1, "DeviceTypes list must contains at least one entry") + for device_type in device.deviceTypes: + asserts.assert_true(type_matches(device_type.deviceType, uint), "DeviceType should be a uint") + # TODO what other validation can we do here to device_type.deviceType + asserts.assert_true(type_matches(device_type.revision, uint), "device type's revision should be a uint") + asserts.assert_greater_equal(device_type.revision, 1, "device type's revision must >= 1") + + asserts.assert_true(type_matches(device.uniqueLocationIDs, list), "UniqueLocationIds should be a list") + num_of_unique_location_ids = len(device.uniqueLocationIDs) + asserts.assert_less_equal(num_of_unique_location_ids, 64, "UniqueLocationIds list should be <= 64") + for location_id in device.uniqueLocationIDs: + asserts.assert_true(type_matches(location_id, str), "UniqueLocationId should be a string") + location_id_string_length = len(location_id) asserts.assert_greater_equal(location_id_string_length, 1, "UniqueLocationId must contain at least one character") asserts.assert_less_equal(location_id_string_length, 64, "UniqueLocationId should be <= 64") - asserts.assert_true(type_matches(location.locationDescriptor.locationName, str), - "LocationName should be a string") - asserts.assert_less_equal(len(location.locationDescriptor.locationName), 64, "LocationName should be <= 64") - - if location.locationDescriptor.floorNumber is not NullValue: - asserts.assert_true(type_matches(location.locationDescriptor.floorNumber, int), - "FloorNumber should be an int") - # TODO check in range of int16. - - if location.locationDescriptor.areaType is not NullValue: - # TODO check areaType is valid. - pass + asserts.assert_true(type_matches(device.uniqueLocationIDsLastEdit, uint), + "UniqueLocationIdsLastEdit should be a uint") + if num_of_unique_location_ids: + asserts.assert_greater(device.uniqueLocationIDsLastEdit, 0, "UniqueLocationIdsLastEdit must be non-zero") - asserts.assert_true(type_matches(location.locationDescriptorLastEdit, uint), - "UniqueLocationIdsLastEdit should be a uint") - asserts.assert_greater(location.locationDescriptorLastEdit, 0, "LocationDescriptorLastEdit must be non-zero") - - else: - asserts.assert_equal(num_of_locations, 0, "Device was removed, there should be no location in LocationDirectory") + def _validate_location_directory(self, location_directory): + num_of_locations = len(location_directory) + asserts.assert_less_equal(num_of_locations, 64, "Too many location entries") + for location in location_directory: + asserts.assert_true(type_matches(location.uniqueLocationID, str), "UniqueLocationId should be a string") + location_id_string_length = len(location.uniqueLocationID) + asserts.assert_greater_equal(location_id_string_length, 1, + "UniqueLocationId must contain at least one character") + asserts.assert_less_equal(location_id_string_length, 64, "UniqueLocationId should be <= 64") + + asserts.assert_true(type_matches(location.locationDescriptor.locationName, str), + "LocationName should be a string") + asserts.assert_less_equal(len(location.locationDescriptor.locationName), 64, "LocationName should be <= 64") + + if location.locationDescriptor.floorNumber is not NullValue: + asserts.assert_true(type_matches(location.locationDescriptor.floorNumber, int), + "FloorNumber should be an int") + # TODO check in range of int16. + + if location.locationDescriptor.areaType is not NullValue: + # TODO check areaType is valid. + pass + + asserts.assert_true(type_matches(location.locationDescriptorLastEdit, uint), + "UniqueLocationIdsLastEdit should be a uint") + asserts.assert_greater(location.locationDescriptorLastEdit, 0, "LocationDescriptorLastEdit must be non-zero") def steps_TC_ECOINFO_2_1(self) -> list[TestStep]: steps = [TestStep(1, "Identify endpoints with Ecosystem Information Cluster", is_commissioning=True), - TestStep(2, "Reading RemovedOn Attribute"), - TestStep(3, "Reading DeviceDirectory Attribute"), - TestStep(4, "Reading LocationDirectory Attribute"), - TestStep(5, "Try Writing to RemovedOn Attribute"), - TestStep(6, "Try Writing to DeviceDirectory Attribute"), - TestStep(7, "Try Writing to LocationDirectory Attribute"), - TestStep(8, "Repeating steps 2 to 7 for each endpoint identified in step 1")] + TestStep(2, "Reading DeviceDirectory Attribute"), + TestStep(3, "Reading LocationDirectory Attribute"), + TestStep(4, "Try Writing to DeviceDirectory Attribute"), + TestStep(5, "Try Writing to LocationDirectory Attribute"), + TestStep(6, "Repeating steps 2 to 5 for each endpoint identified in step 1")] return steps @async_test_body @@ -131,19 +122,6 @@ async def test_TC_ECOINFO_2_1(self): for idx, cluster_endpoint in enumerate(list_of_endpoints): if idx == 0: self.step(2) - removed_on = await self.read_single_attribute( - dev_ctrl, - dut_node_id, - endpoint=cluster_endpoint, - attribute=Clusters.EcosystemInformation.Attributes.RemovedOn) - - is_removed_on_null = removed_on is NullValue - if not is_removed_on_null: - asserts.assert_true(type_matches(removed_on, uint)) - asserts.assert_greater(removed_on, 0, "RemovedOn must be greater than 0", "RemovedOn should be a uint") - - if idx == 0: - self.step(3) device_directory = await self.read_single_attribute( dev_ctrl, dut_node_id, @@ -151,10 +129,10 @@ async def test_TC_ECOINFO_2_1(self): attribute=Clusters.EcosystemInformation.Attributes.DeviceDirectory, fabricFiltered=False) - self._validate_device_directory(is_removed_on_null, device_directory) + self._validate_device_directory(device_directory) if idx == 0: - self.step(4) + self.step(3) location_directory = await self.read_single_attribute( dev_ctrl, dut_node_id, @@ -162,28 +140,22 @@ async def test_TC_ECOINFO_2_1(self): attribute=Clusters.EcosystemInformation.Attributes.LocationDirectory, fabricFiltered=False) - self._validate_location_directory(is_removed_on_null, location_directory) + self._validate_location_directory(location_directory) if idx == 0: - self.step(5) - result = await dev_ctrl.WriteAttribute(dut_node_id, [(cluster_endpoint, Clusters.EcosystemInformation.Attributes.RemovedOn(2))]) - asserts.assert_equal(len(result), 1, "Expecting only one result from trying to write to RemovedOn Attribute") - asserts.assert_equal(result[0].Status, Status.UnsupportedWrite, "Expecting Status of UnsupportedWrite") - - if idx == 0: - self.step(6) + self.step(4) result = await dev_ctrl.WriteAttribute(dut_node_id, [(cluster_endpoint, Clusters.EcosystemInformation.Attributes.DeviceDirectory([]))]) asserts.assert_equal(len(result), 1, "Expecting only one result from trying to write to DeviceDirectory Attribute") asserts.assert_equal(result[0].Status, Status.UnsupportedWrite, "Expecting Status of UnsupportedWrite") if idx == 0: - self.step(7) + self.step(5) result = await dev_ctrl.WriteAttribute(dut_node_id, [(cluster_endpoint, Clusters.EcosystemInformation.Attributes.DeviceDirectory([]))]) asserts.assert_equal(len(result), 1, "Expecting only one result from trying to write to LocationDirectory Attribute") asserts.assert_equal(result[0].Status, Status.UnsupportedWrite, "Expecting Status of UnsupportedWrite") if idx == 0: - self.step(8) + self.step(6) if __name__ == "__main__": diff --git a/src/python_testing/TC_ECOINFO_2_2.py b/src/python_testing/TC_ECOINFO_2_2.py index c8bb7de4004d56..b004a95927ef59 100644 --- a/src/python_testing/TC_ECOINFO_2_2.py +++ b/src/python_testing/TC_ECOINFO_2_2.py @@ -16,7 +16,7 @@ # import chip.clusters as Clusters -from chip.clusters.Types import NullValue +from chip.interaction_model import Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts @@ -32,12 +32,13 @@ def steps_TC_ECOINFO_2_2(self) -> list[TestStep]: TestStep(2, "Add a bridged device"), TestStep("2a", "(Manual Step) Add a bridged device using method indicated by the manufacturer"), TestStep("2b", "Read root endpoint's PartsList, validate exactly one endpoint added"), - TestStep("2c", "On newly added endpoint detected in 2b read RemovedOn Ecosystem Information Attribute and validate"), + TestStep("2c", "On newly added endpoint detected in 2b read DeviceDirectory Ecosystem Information Attribute and validate success"), + TestStep("2d", "On newly added endpoint detected in 2b read LocationDirectory Ecosystem Information Attribute and validate success"), TestStep(3, "Remove bridged device"), TestStep("3a", "(Manual Step) Removed bridged device added in step 2a using method indicated by the manufacturer"), - TestStep("3b", "On newly added endpoint detected in 2b read RemovedOn Ecosystem Information Attribute and validate"), - TestStep("3c", "On newly added endpoint detected in 2b read DeviceDirectory Ecosystem Information Attribute and validate"), - TestStep("3d", "On newly added endpoint detected in 2b read LocationDirectory Ecosystem Information Attribute and validate")] + TestStep("3b", "Verify that PartsList equals what was read in 1a"), + TestStep("3c", "On endpoint detected in 2b, read DeviceDirectory Ecosystem Information Attribute and validate failure"), + TestStep("3d", "On endpoint detected in 2b, read LocationDirectory Ecosystem Information Attribute and validate failure")] return steps @@ -80,43 +81,55 @@ async def test_TC_ECOINFO_2_2(self): self.step("2c") newly_added_endpoint = list(unique_endpoints_set)[0] - removed_on = await self.read_single_attribute( - dev_ctrl, - dut_node_id, + await self.read_single_attribute_check_success( + dev_ctrl=dev_ctrl, + node_id=dut_node_id, endpoint=newly_added_endpoint, - attribute=Clusters.EcosystemInformation.Attributes.RemovedOn) + cluster=Clusters.EcosystemInformation, + attribute=Clusters.EcosystemInformation.Attributes.DeviceDirectory, + fabric_filtered=False) - asserts.assert_true(removed_on is NullValue, "RemovedOn is expected to be null for a newly added device") + self.step("2d") + await self.read_single_attribute_check_success( + dev_ctrl=dev_ctrl, + node_id=dut_node_id, + endpoint=newly_added_endpoint, + cluster=Clusters.EcosystemInformation, + attribute=Clusters.EcosystemInformation.Attributes.LocationDirectory, + fabric_filtered=False) self.step(3) self.step("3a") self.wait_for_user_input(prompt_msg="Removed bridged device added in step 2a using method indicated by the manufacturer") self.step("3b") - removed_on = await self.read_single_attribute( - dev_ctrl, - dut_node_id, - endpoint=newly_added_endpoint, - attribute=Clusters.EcosystemInformation.Attributes.RemovedOn) - asserts.assert_true(removed_on is not NullValue, "RemovedOn is expected to have a value") + root_part_list_step_3 = await dev_ctrl.ReadAttribute(dut_node_id, [(root_node_endpoint, Clusters.Descriptor.Attributes.PartsList)]) + set_of_endpoints_step_3 = set( + root_part_list_step_3[root_node_endpoint][Clusters.Descriptor][Clusters.Descriptor.Attributes.PartsList]) + + asserts.assert_equal(set_of_endpoints_step_3, set_of_endpoints_step_1, + "Expected set of endpoints after removal to be identical to when test started") self.step("3c") - device_directory = await self.read_single_attribute( - dev_ctrl, - dut_node_id, + newly_added_endpoint = list(unique_endpoints_set)[0] + await self.read_single_attribute_expect_error( + dev_ctrl=dev_ctrl, + node_id=dut_node_id, + error=Status.UnsupportedEndpoint, endpoint=newly_added_endpoint, + cluster=Clusters.EcosystemInformation, attribute=Clusters.EcosystemInformation.Attributes.DeviceDirectory, - fabricFiltered=False) - asserts.assert_equal(len(device_directory), 0, "Expected device directory to be empty") + fabric_filtered=False) self.step("3d") - location_directory = await self.read_single_attribute( - dev_ctrl, - dut_node_id, + await self.read_single_attribute_expect_error( + dev_ctrl=dev_ctrl, + node_id=dut_node_id, + error=Status.UnsupportedEndpoint, endpoint=newly_added_endpoint, + cluster=Clusters.EcosystemInformation, attribute=Clusters.EcosystemInformation.Attributes.LocationDirectory, - fabricFiltered=False) - asserts.assert_equal(len(location_directory), 0, "Expected location directory to be empty") + fabric_filtered=False) if __name__ == "__main__": 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 a8fc013fa0ce19..ebd5339fd079a7 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 @@ -37425,96 +37425,6 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint16_t valu namespace EcosystemInformation { namespace Attributes { -namespace RemovedOn { - -Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - Protocols::InteractionModel::Status status = - emberAfReadAttribute(endpoint, Clusters::EcosystemInformation::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} - -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value, MarkAttributeDirty markDirty) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EcosystemInformation::Id, Id, writable, ZCL_EPOCH_US_ATTRIBUTE_TYPE, - markDirty); -} - -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EcosystemInformation::Id, Id, writable, ZCL_EPOCH_US_ATTRIBUTE_TYPE); -} - -Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint, MarkAttributeDirty markDirty) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EcosystemInformation::Id, Id, writable, ZCL_EPOCH_US_ATTRIBUTE_TYPE, - markDirty); -} - -Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EcosystemInformation::Id, Id, writable, ZCL_EPOCH_US_ATTRIBUTE_TYPE); -} - -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value, - MarkAttributeDirty markDirty) -{ - if (value.IsNull()) - { - return SetNull(endpoint, markDirty); - } - - return Set(endpoint, value.Value(), markDirty); -} - -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace RemovedOn - namespace FeatureMap { Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, uint32_t * value) 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 cab9691ebbf4ed..5537692d6908d4 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 @@ -5812,17 +5812,6 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint16_t valu namespace EcosystemInformation { namespace Attributes { -namespace RemovedOn { -Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, DataModel::Nullable & value); // epoch_us -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value); -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value, MarkAttributeDirty markDirty); -Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint); -Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint, MarkAttributeDirty markDirty); -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value, - MarkAttributeDirty markDirty); -} // namespace RemovedOn - namespace FeatureMap { Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32 Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint32_t value); diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp index ebe45c86ed8406..c3947539864e2c 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp @@ -28890,8 +28890,6 @@ CHIP_ERROR TypeInfo::DecodableType::Decode(TLV::TLVReader & reader, const Concre { switch (path.mAttributeId) { - case Attributes::RemovedOn::TypeInfo::GetAttributeId(): - return DataModel::Decode(reader, removedOn); case Attributes::DeviceDirectory::TypeInfo::GetAttributeId(): return DataModel::Decode(reader, deviceDirectory); case Attributes::LocationDirectory::TypeInfo::GetAttributeId(): diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h index 35f77bf4895d3d..3309b8ee41878b 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h @@ -41861,18 +41861,6 @@ using DecodableType = Type; namespace Attributes { -namespace RemovedOn { -struct TypeInfo -{ - using Type = chip::app::DataModel::Nullable; - using DecodableType = chip::app::DataModel::Nullable; - using DecodableArgType = const chip::app::DataModel::Nullable &; - - static constexpr ClusterId GetClusterId() { return Clusters::EcosystemInformation::Id; } - static constexpr AttributeId GetAttributeId() { return Attributes::RemovedOn::Id; } - static constexpr bool MustUseTimedWrite() { return false; } -}; -} // namespace RemovedOn namespace DeviceDirectory { struct TypeInfo { @@ -41947,7 +41935,6 @@ struct TypeInfo CHIP_ERROR Decode(TLV::TLVReader & reader, const ConcreteAttributePath & path); - Attributes::RemovedOn::TypeInfo::DecodableType removedOn; Attributes::DeviceDirectory::TypeInfo::DecodableType deviceDirectory; Attributes::LocationDirectory::TypeInfo::DecodableType locationDirectory; Attributes::GeneratedCommandList::TypeInfo::DecodableType generatedCommandList; diff --git a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h index 85d56a98880f99..6cb26b63d9912c 100644 --- a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h +++ b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h @@ -7490,16 +7490,12 @@ static constexpr AttributeId Id = Globals::Attributes::ClusterRevision::Id; namespace EcosystemInformation { namespace Attributes { -namespace RemovedOn { -static constexpr AttributeId Id = 0x00000000; -} // namespace RemovedOn - namespace DeviceDirectory { -static constexpr AttributeId Id = 0x00000001; +static constexpr AttributeId Id = 0x00000000; } // namespace DeviceDirectory namespace LocationDirectory { -static constexpr AttributeId Id = 0x00000002; +static constexpr AttributeId Id = 0x00000001; } // namespace LocationDirectory namespace GeneratedCommandList { diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h index a4f8cb7298bbf9..e145de431ed5b1 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h @@ -13802,9 +13802,8 @@ class ContentAppObserverContentAppMessage : public ClusterCommand | Commands: | | |------------------------------------------------------------------------------| | Attributes: | | -| * RemovedOn | 0x0000 | -| * DeviceDirectory | 0x0001 | -| * LocationDirectory | 0x0002 | +| * DeviceDirectory | 0x0000 | +| * LocationDirectory | 0x0001 | | * GeneratedCommandList | 0xFFF8 | | * AcceptedCommandList | 0xFFF9 | | * EventList | 0xFFFA | @@ -26807,7 +26806,6 @@ void registerClusterEcosystemInformation(Commands & commands, CredentialIssuerCo // Attributes // make_unique(Id, credsIssuerConfig), // - make_unique(Id, "removed-on", Attributes::RemovedOn::Id, credsIssuerConfig), // make_unique(Id, "device-directory", Attributes::DeviceDirectory::Id, credsIssuerConfig), // make_unique(Id, "location-directory", Attributes::LocationDirectory::Id, credsIssuerConfig), // make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // @@ -26817,8 +26815,6 @@ void registerClusterEcosystemInformation(Commands & commands, CredentialIssuerCo make_unique(Id, "feature-map", Attributes::FeatureMap::Id, credsIssuerConfig), // make_unique(Id, "cluster-revision", Attributes::ClusterRevision::Id, credsIssuerConfig), // make_unique>(Id, credsIssuerConfig), // - make_unique>>( - Id, "removed-on", 0, UINT64_MAX, Attributes::RemovedOn::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // make_unique>>( Id, "device-directory", Attributes::DeviceDirectory::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // @@ -26839,7 +26835,6 @@ void registerClusterEcosystemInformation(Commands & commands, CredentialIssuerCo make_unique>(Id, "cluster-revision", 0, UINT16_MAX, Attributes::ClusterRevision::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // make_unique(Id, credsIssuerConfig), // - make_unique(Id, "removed-on", Attributes::RemovedOn::Id, credsIssuerConfig), // make_unique(Id, "device-directory", Attributes::DeviceDirectory::Id, credsIssuerConfig), // make_unique(Id, "location-directory", Attributes::LocationDirectory::Id, credsIssuerConfig), // make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp index 6d64b9451276ed..0efe192f7b59aa 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp +++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp @@ -18169,11 +18169,6 @@ CHIP_ERROR DataModelLogger::LogAttribute(const chip::app::ConcreteDataAttributeP case EcosystemInformation::Id: { switch (path.mAttributeId) { - case EcosystemInformation::Attributes::RemovedOn::Id: { - chip::app::DataModel::Nullable value; - ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); - return DataModelLogger::LogValue("RemovedOn", 1, value); - } case EcosystemInformation::Attributes::DeviceDirectory::Id: { chip::app::DataModel::DecodableList< chip::app::Clusters::EcosystemInformation::Structs::EcosystemDeviceStruct::DecodableType> diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h index 9c139bebe8881a..e8aa8848588f3c 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h @@ -163941,9 +163941,8 @@ class SubscribeAttributeContentAppObserverClusterRevision : public SubscribeAttr | Commands: | | |------------------------------------------------------------------------------| | Attributes: | | -| * RemovedOn | 0x0000 | -| * DeviceDirectory | 0x0001 | -| * LocationDirectory | 0x0002 | +| * DeviceDirectory | 0x0000 | +| * LocationDirectory | 0x0001 | | * GeneratedCommandList | 0xFFF8 | | * AcceptedCommandList | 0xFFF9 | | * EventList | 0xFFFA | @@ -163956,91 +163955,6 @@ class SubscribeAttributeContentAppObserverClusterRevision : public SubscribeAttr #if MTR_ENABLE_PROVISIONAL -/* - * Attribute RemovedOn - */ -class ReadEcosystemInformationRemovedOn : public ReadAttribute { -public: - ReadEcosystemInformationRemovedOn() - : ReadAttribute("removed-on") - { - } - - ~ReadEcosystemInformationRemovedOn() - { - } - - CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override - { - constexpr chip::ClusterId clusterId = chip::app::Clusters::EcosystemInformation::Id; - constexpr chip::AttributeId attributeId = chip::app::Clusters::EcosystemInformation::Attributes::RemovedOn::Id; - - ChipLogProgress(chipTool, "Sending cluster (0x%08" PRIX32 ") ReadAttribute (0x%08" PRIX32 ") on endpoint %u", endpointId, clusterId, attributeId); - - dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); - __auto_type * cluster = [[MTRBaseClusterEcosystemInformation alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; - [cluster readAttributeRemovedOnWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable error) { - NSLog(@"EcosystemInformation.RemovedOn response %@", [value description]); - if (error == nil) { - RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value); - } else { - LogNSError("EcosystemInformation RemovedOn read Error", error); - RemoteDataModelLogger::LogAttributeErrorAsJSON(@(endpointId), @(clusterId), @(attributeId), error); - } - SetCommandExitStatus(error); - }]; - return CHIP_NO_ERROR; - } -}; - -class SubscribeAttributeEcosystemInformationRemovedOn : public SubscribeAttribute { -public: - SubscribeAttributeEcosystemInformationRemovedOn() - : SubscribeAttribute("removed-on") - { - } - - ~SubscribeAttributeEcosystemInformationRemovedOn() - { - } - - CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override - { - constexpr chip::ClusterId clusterId = chip::app::Clusters::EcosystemInformation::Id; - constexpr chip::CommandId attributeId = chip::app::Clusters::EcosystemInformation::Attributes::RemovedOn::Id; - - ChipLogProgress(chipTool, "Sending cluster (0x%08" PRIX32 ") ReportAttribute (0x%08" PRIX32 ") on endpoint %u", clusterId, attributeId, endpointId); - dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); - __auto_type * cluster = [[MTRBaseClusterEcosystemInformation alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(mMinInterval) maxInterval:@(mMaxInterval)]; - if (mKeepSubscriptions.HasValue()) { - params.replaceExistingSubscriptions = !mKeepSubscriptions.Value(); - } - if (mFabricFiltered.HasValue()) { - params.filterByFabric = mFabricFiltered.Value(); - } - if (mAutoResubscribe.HasValue()) { - params.resubscribeAutomatically = mAutoResubscribe.Value(); - } - [cluster subscribeAttributeRemovedOnWithParams:params - subscriptionEstablished:^() { mSubscriptionEstablished = YES; } - reportHandler:^(NSNumber * _Nullable value, NSError * _Nullable error) { - NSLog(@"EcosystemInformation.RemovedOn response %@", [value description]); - if (error == nil) { - RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value); - } else { - RemoteDataModelLogger::LogAttributeErrorAsJSON(@(endpointId), @(clusterId), @(attributeId), error); - } - SetCommandExitStatus(error); - }]; - - return CHIP_NO_ERROR; - } -}; - -#endif // MTR_ENABLE_PROVISIONAL -#if MTR_ENABLE_PROVISIONAL - /* * Attribute DeviceDirectory */ @@ -198490,10 +198404,6 @@ void registerClusterEcosystemInformation(Commands & commands) make_unique(Id), // make_unique(Id), // make_unique(Id), // -#if MTR_ENABLE_PROVISIONAL - make_unique(), // - make_unique(), // -#endif // MTR_ENABLE_PROVISIONAL #if MTR_ENABLE_PROVISIONAL make_unique(), // make_unique(), // From 6d65c0a50dec70a77cc578a4a5b32c1f3a8a4b7e Mon Sep 17 00:00:00 2001 From: yinyihu-silabs <83772323+yinyihu-silabs@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:59:19 -0400 Subject: [PATCH 04/11] fix #35210: tc-icdm-2.1, 3.1, 3.4 (#35227) * fix 35210: tc-icdm-2.1, 3.1, 3.4 * missing changes. --- src/python_testing/TC_ICDM_2_1.py | 6 ++++-- src/python_testing/TC_ICDM_3_1.py | 7 ++++++- src/python_testing/TC_ICDM_3_4.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/python_testing/TC_ICDM_2_1.py b/src/python_testing/TC_ICDM_2_1.py index 63b27c64d6d8fa..25b7897abaaf47 100644 --- a/src/python_testing/TC_ICDM_2_1.py +++ b/src/python_testing/TC_ICDM_2_1.py @@ -96,7 +96,8 @@ def desc_TC_ICDM_2_1(self) -> str: def steps_TC_ICDM_2_1(self) -> list[TestStep]: steps = [ - TestStep(1, "Commissioning, already done", is_commissioning=True), + TestStep("1a", "Commissioning, already done", is_commissioning=True), + TestStep("1b", "CTH reads from the DUT the FeatureMap attribute."), TestStep(2, "TH reads from the DUT the ActiveModeThreshold attribute."), TestStep(3, "TH reads from the DUT the ActiveModeDuration attribute."), TestStep(4, "TH reads from the DUT the IdleModeDuration attribute."), @@ -131,8 +132,9 @@ async def test_TC_ICDM_2_1(self): attributes = cluster.Attributes # Commissioning - self.step(1) + self.step("1a") # Read feature map + self.step("1b") featureMap = await self._read_icdm_attribute_expect_success( attributes.FeatureMap) diff --git a/src/python_testing/TC_ICDM_3_1.py b/src/python_testing/TC_ICDM_3_1.py index f20e2816723a28..f6f094698ca25c 100644 --- a/src/python_testing/TC_ICDM_3_1.py +++ b/src/python_testing/TC_ICDM_3_1.py @@ -85,7 +85,7 @@ def steps_TC_ICDM_3_1(self) -> list[TestStep]: 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(10, "Repeat Step 8-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."), ] @@ -240,6 +240,11 @@ async def test_TC_ICDM_3_1(self): e.status, Status.Success, "Unexpected error returned") pass + registeredClients = await self._read_icdm_attribute_expect_success(attributes.RegisteredClients) + for remainingClient in registeredClients: + asserts.assert_not_equal(remainingClient.checkInNodeID, client["checkInNodeID"], + "CheckInNodeID was unregistered. It should not be present in the attribute list.") + self.step(11) registeredClients = await self._read_icdm_attribute_expect_success( attributes.RegisteredClients) diff --git a/src/python_testing/TC_ICDM_3_4.py b/src/python_testing/TC_ICDM_3_4.py index b062da0845e4ea..b8d0693acb7d75 100644 --- a/src/python_testing/TC_ICDM_3_4.py +++ b/src/python_testing/TC_ICDM_3_4.py @@ -63,7 +63,7 @@ def steps_TC_ICDM_3_4(self) -> list[TestStep]: steps = [ TestStep(0, "Commissioning, already done", is_commissioning=True), TestStep(1, "TH reads from the DUT the ICDCounter attribute."), - TestStep("2a", "Reboot DUT."), + TestStep("2a", "Power cycle DUT."), TestStep("2b", "TH waits for {PIXIT.WAITTIME.REBOOT}"), TestStep(3, "Verify that the DUT response contains value of ICDCounter and stores in IcdCounter2. \ IcdCounter2 is greater or equal to IcdCounter1. \ From c50b740bb6665c981a7fedf6d797224186df0b3e Mon Sep 17 00:00:00 2001 From: Hasty Granbery Date: Tue, 27 Aug 2024 13:30:57 -0700 Subject: [PATCH 05/11] [HVAC] Set thermostat delegate in thermostat app (#35230) --- examples/thermostat/linux/main.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/thermostat/linux/main.cpp b/examples/thermostat/linux/main.cpp index b9f82696e8ce79..3983d2b37dfb90 100644 --- a/examples/thermostat/linux/main.cpp +++ b/examples/thermostat/linux/main.cpp @@ -21,6 +21,9 @@ #include #include #include +#include + +#include "thermostat-delegate-impl.h" using namespace chip; using namespace chip::app; @@ -78,3 +81,12 @@ int main(int argc, char * argv[]) ChipLinuxAppMainLoop(); return 0; } + +using namespace chip::app::Clusters::Thermostat; +void emberAfThermostatClusterInitCallback(EndpointId endpoint) +{ + // Register the delegate for the Thermostat + auto & delegate = ThermostatDelegate::GetInstance(); + + SetDefaultDelegate(endpoint, &delegate); +} From 3e4f3fe050cfd2f6a5f2768d610f798321726639 Mon Sep 17 00:00:00 2001 From: Hasty Granbery Date: Tue, 27 Aug 2024 13:41:58 -0700 Subject: [PATCH 06/11] [HVAC] Handle null BuiltIn field on Preset write according to spec (#35161) * Add support for Presets attributes and commands to the Thermostat cluster Clean up the Thermostat cluster and remove the TemperatureSetpointHoldPolicy attribute and SetTemperatureSetpointHoldPolicy command * Restyled by whitespace * Restyled by clang-format * Restyled by gn. * Fix build error for Linux configure build of all-clusters-app * Fix Darwin CI issues Editorial fixes * Restyled by clang-format * More fixes * Restyled by clang-format * BUILD.gn fixes for CI * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Address review comments. * Restyled by clang-format * Regenerate Thermostat XML from spec * Move atomic enum to global-enums.xml, actually # Conflicts: # src/app/zap-templates/zcl/data-model/chip/global-structs.xml * Regenerate XML and convert thermostat-server to atomic writes * Pull in ACCapacityFormat typo un-fix * Update Test_TC_TSTAT_1_1 to know about AtomicResponse command. * Restyled patch * Fix weird merge with upstream * Fix emberAfIsTypeSigned not understanding temperature type * Merge fixes from atomic write branch * Relocate thermostat-manager sample code to all-clusters-common * Fix g++ build error on linux * Fix C formatter for long int, cast whole expression * Sync cast fix with master * Add thermostat-common dependency to thermostat app under linux * Remove MatterPostAttributeChangeCallback from thermostat-manager, as it conflicts with other implementations * Convert Atomic enums and structs to global * Restyled patch * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Regen with alchemy 0.6.1 * Updates based on comments * Add TC_MCORE_FS_1_3.py test implementation (#34650) * Fix most TC-SWTCH-2.4 remaining issues (#34677) - Move 2.4 in a better place in the file - Add test steps properly - Allow default button press position override Issue #34656 Testing done: - Test still passes on DUT with automation * Initial test script for Fabric Sync TC_MCORE_FS_1_2 (#34675) * Initial test script for Fabric Sync TC_MCORE_FS_1_2 * Apply suggestions from code review Co-authored-by: C Freeman * Address Review Comments * Address review comments * Fix default timeout after other timeouts changed * Restyled by autopep8 * Fix linter error --------- Co-authored-by: C Freeman Co-authored-by: Restyled.io * Test automation for FabricSync ICD BridgedDeviceBasicInfoCluster (#34628) * WIP Bridged ICD, commissioning to both fabrics * wip testing sending KeepActive * wip most steps implemented * using SIGSTOP and SIGCONT to control ICD server pausing * Update src/python_testing/TC_BRBINFO_4_1.py Co-authored-by: Terence Hampson * comments addressed * more comments addressed * lint pass * Update src/python_testing/TC_BRBINFO_4_1.py Co-authored-by: C Freeman * comments addressed, incl TH_SERVER configurable * added setupQRCode and setupManualCode as options for DUT commissioning * Restyled by autopep8 * Restyled by isort * Update src/python_testing/TC_BRBINFO_4_1.py Co-authored-by: Terence Hampson * Update src/python_testing/TC_BRBINFO_4_1.py Co-authored-by: Terence Hampson * Update src/python_testing/TC_BRBINFO_4_1.py Co-authored-by: Terence Hampson * comments addressed * Restyled by autopep8 --------- Co-authored-by: Terence Hampson Co-authored-by: C Freeman Co-authored-by: Restyled.io * ServiceArea test scripts (#34548) * initial commit * fix bugs * fix issues reported by the linter * fix bug in checking for unique areaDesc * add TC 1.5 * Update src/python_testing/TC_SEAR_1_2.py Co-authored-by: William * Update src/python_testing/TC_SEAR_1_2.py Co-authored-by: William * address code review comments * fix issue introduced by the previous commit * address code review feedback * Update src/python_testing/TC_SEAR_1_2.py Co-authored-by: Kiel Oleson * address code review feedback * remove PICS checked by the TC_SEAR_1.6 * more code review updates * Restyled by autopep8 --------- Co-authored-by: William Co-authored-by: Kiel Oleson Co-authored-by: Restyled.io * Remove manual tests for Thermostat presets (#34679) * Dump details about leaked ExchangeContexts before aborting (#34617) * Dump details about leaked ExchangeContexts before aborting This is implemented via a VerifyOrDieWithObject() variant of the existing VerifyOrDie() macro that calls a DumpToLog() method on the provided object if it exists (otherwise this is simply a no-op). If CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE is not enabled, VerifyOrDieWithObject() simply behaves like a plain VerifyOrDie(). DumpToLog() implementations can use ChipLogFormatRtti to log type information about an object (usually a delegate); if RTTI is disabled this simply outputs whether the object was null or not. * Address review comments * Make gcc happy and improve documentation * Remove unused include * Fix compile error without CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE * Avoid unused parameter warning * [TI] CC13x4_26x4 build fixes (#34682) * lwip pbuf, map file, and hex creation when OTA is disabled * added cc13x4 family define around the non OTA hex creation * whitespace fix * reversed custom factoy data flash with cc13x4 check * more whitespace fixes * [ICD] Add missing polling function to NoWifi connectivity manager (#34684) * Add missing polling function to NoWifi connectivity manager * Update GenericConnectivityManagerImpl_NoWiFi.h Co-authored-by: Boris Zbarsky --------- Co-authored-by: Boris Zbarsky * [OPSTATE] Add Q test script for CountdownTime (#34632) * Add Q test * Added test to test set * Remove unused var * Restyled by autopep8 * Restyled by isort * Fix name * Use pics over other method * Removed unused stuff * Added pipe commands * Fix reset * Get example to report appropriate changes. * WiP * Added some comments * Changes to make things work * Removed dev msgs * Missed some * Removed dev msgs * Straggler * Restyled by clang-format * Restyled by autopep8 * Restyled by isort * Commented unused var * Update examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp * Fix bug --------- Co-authored-by: Restyled.io * YAML update to BRBINFO, ProductId (#34513) * Bridged Device Information Cluster, Attribute ProductID test reflects marking as O, not X * Update src/app/tests/suites/certification/Test_TC_BRBINFO_2_1.yaml Co-authored-by: Terence Hampson * corrected pics * corrected pics * WIP Bridged ICD, commissioning to both fabrics * wip testing sending KeepActive * update to bridged-device-basic-information.xml and zap generated files * removed unrelated file --------- Co-authored-by: Terence Hampson Co-authored-by: Andrei Litvin * Fix simplified Linux tv-casting-app gn build error. (#34692) * adding parallel execution to restyle-diff (#34663) * adding parallel execution to restyle-diff * using xargs to call restyle-paths * fixing Copyright year * restyle the restyler * Add some bits to exercise global structs/enums to Unit Testing cluster. (#34540) * Adds things to the Unit Testing cluster XML. * This requires those things to be enabled in all-clusters-app, all-clusters-minimal-app, and one of the chef contact sensors to pass CI. * That requires an implementation in test-cluster-server * At which point might as well add a YAML test to exercise it all. * [Silabs] Port platform specific Multi-Chip OTA work (#34440) * Pull request #1836: Cherry multi ota Merge in WMN_TOOLS/matter from cherry-multi-ota to silabs_slc_1.3 Squashed commit of the following: commit 4320bb46571658bc44fb82345348265def394991 Author: Michael Rupp Date: Fri May 10 14:26:07 2024 -0400 remove some unwanted diffs in provision files commit be160931dc600de7e7ead378b70d6a43c3945e46 Author: Michael Rupp Date: Fri May 10 14:24:25 2024 -0400 revert changes to generator.project.mak commit 14b6605887166e6d5284a61feb2bf407d850bdcf Author: Michael Rupp Date: Fri May 10 13:06:12 2024 -0400 revert NVM key changes and script changes ... and 8 more commits * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Restyled by autopep8 * remove unused libs caught by linter * update doctree with new readmes * rerun CI, cirque failing for unknown reasons * fix include guards in provision examples * Restyled by clang-format --------- Co-authored-by: Restyled.io * Add python tests for Thermostat presets feature (#34693) * Add python tests for Thermostat presets feature * Restyled by autopep8 * Restyled by isort * Update the PICS code for presets attribute --------- Co-authored-by: Restyled.io * removing unneccessary git fetch (#34698) * Restyle patch * Regen to fix ordering of global structs * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Return correct AtomicResponse when committing or rolling back * Patch tests for atomic write of presets * Fix tests to work with the new setup. Specific changes: * Enable SetActivePresetRequest command in all-clusters-app. * Fix assignment of a PresetStructWithOwnedMembers to another PresetStructWithOwnedMembers to actually work correctly. * Move constraint checks that happen on write from commit to write. * Fix sending of atomic responses to not have use-stack-after-return. * Fix PICS for the tests involved. * Fix PICS values for atomic requests * Remove PresetsSchedulesEditable and QueuedPreset from various places * Restyled patch * Restyled patch, again * Remove PICS value for PresetsSchedulesEditable * clang-tidy fixes * clang-tidy fixes * Clear associated atomic writes when fabric is removed * Add tests for fabric removal and lockout of clients outside of atomic write * Python linter * Restyled patch * Clear timer when fabric is removed * Check for open atomic write before resetting * Revert auto delegate declaration on lines where there's no collision * Allow Thermostat delegate to provide timeout for atomic requests * Relocate thermostat example code to thermostat-common * Remove thermostat-manager code, replace with thermostat delegate * Sync atomic write error order with spec * Restyle patch * Drop memset of atomic write sessions * Add PreCommit stage to allow rollback of multiple attributes when only one fails * Separate OnTimerExpired method, vs ResetWrite * Method documentation * Apply suggestions from code review Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> * Remove unused InWrite check * Drop imcode alias * Switch AtomicWriteState to enum class * DRY up atomic write manager * Apply suggestions from code review Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> * Drop duplicate doc comments * Rename GetAtomicWriteScopedNodeId to GetAtomicWriteOriginatorScopedNodeId * Updates based on comments * Add MatterReportingAttributeChangeCallback calls for updated attributes * Relocate thermostat example code to thermostat-common, and remove thermostat-manager * Merge atomic write code back into thermostat-server * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Fix build after suggestions * Actually track attribute IDs associated with atomic write * Only commit presets if all attribute precommits were successful * Fix scope on err * Add documentation to methods * Remove duplicate preset check. * Move various functions into anonymous namespaces, or Thermostat namespace * Drop impossible non-atomic attribute status after rollback * Allow null BuiltIn field when saving Presets * Namespace workaround for compilers on other platforms * Fix bad merge * Fix readability issue * Force built-in to false on new presets --------- Co-authored-by: Nivedita Sarkar Co-authored-by: Restyled.io Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> Co-authored-by: Boris Zbarsky Co-authored-by: Terence Hampson Co-authored-by: Tennessee Carmel-Veilleux Co-authored-by: Chris Letnick Co-authored-by: C Freeman Co-authored-by: Douglas Rocha Ferraz Co-authored-by: Petru Lauric <81822411+plauric@users.noreply.github.com> Co-authored-by: William Co-authored-by: Kiel Oleson Co-authored-by: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> Co-authored-by: Anu Biradar <104591549+abiradarti@users.noreply.github.com> Co-authored-by: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> Co-authored-by: Rob Bultman Co-authored-by: Andrei Litvin Co-authored-by: Shao Ling Tan <161761051+shaoltan-amazon@users.noreply.github.com> Co-authored-by: Amine Alami <43780877+Alami-Amine@users.noreply.github.com> Co-authored-by: Michael Rupp <95718139+mykrupp@users.noreply.github.com> --- .../include/thermostat-delegate-impl.h | 2 +- .../src/thermostat-delegate-impl.cpp | 6 +-- .../thermostat-server/thermostat-delegate.h | 2 +- .../thermostat-server-presets.cpp | 52 +++++++++++++------ src/python_testing/TC_TSTAT_4_2.py | 12 ++++- 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h index 9edf13f839df44..b57ee2492122e7 100644 --- a/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h +++ b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h @@ -58,7 +58,7 @@ class ThermostatDelegate : public Delegate void InitializePendingPresets() override; - CHIP_ERROR AppendToPendingPresetList(const Structs::PresetStruct::Type & preset) override; + CHIP_ERROR AppendToPendingPresetList(const PresetStructWithOwnedMembers & preset) override; CHIP_ERROR GetPendingPresetAtIndex(size_t index, PresetStructWithOwnedMembers & preset) override; diff --git a/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp index 8c411cd5a9176e..7991e48323a62f 100644 --- a/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp +++ b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp @@ -177,17 +177,17 @@ void ThermostatDelegate::InitializePendingPresets() } } -CHIP_ERROR ThermostatDelegate::AppendToPendingPresetList(const PresetStruct::Type & preset) +CHIP_ERROR ThermostatDelegate::AppendToPendingPresetList(const PresetStructWithOwnedMembers & preset) { if (mNextFreeIndexInPendingPresetsList < ArraySize(mPendingPresets)) { mPendingPresets[mNextFreeIndexInPendingPresetsList] = preset; - if (preset.presetHandle.IsNull()) + if (preset.GetPresetHandle().IsNull()) { // TODO: #34556 Since we support only one preset of each type, using the octet string containing the preset scenario // suffices as the unique preset handle. Need to fix this to actually provide unique handles once multiple presets of // each type are supported. - const uint8_t handle[] = { static_cast(preset.presetScenario) }; + const uint8_t handle[] = { static_cast(preset.GetPresetScenario()) }; mPendingPresets[mNextFreeIndexInPendingPresetsList].SetPresetHandle(DataModel::MakeNullable(ByteSpan(handle))); } mNextFreeIndexInPendingPresetsList++; diff --git a/src/app/clusters/thermostat-server/thermostat-delegate.h b/src/app/clusters/thermostat-server/thermostat-delegate.h index ccb690a34fba60..5f11ab59889430 100644 --- a/src/app/clusters/thermostat-server/thermostat-delegate.h +++ b/src/app/clusters/thermostat-server/thermostat-delegate.h @@ -103,7 +103,7 @@ class Delegate * @return CHIP_NO_ERROR if the preset was appended to the list successfully. * @return CHIP_ERROR if there was an error adding the preset to the list. */ - virtual CHIP_ERROR AppendToPendingPresetList(const Structs::PresetStruct::Type & preset) = 0; + virtual CHIP_ERROR AppendToPendingPresetList(const PresetStructWithOwnedMembers & preset) = 0; /** * @brief Get the Preset at a given index in the pending presets list. diff --git a/src/app/clusters/thermostat-server/thermostat-server-presets.cpp b/src/app/clusters/thermostat-server/thermostat-server-presets.cpp index a56c94fa49834d..9413513fd0cedd 100644 --- a/src/app/clusters/thermostat-server/thermostat-server-presets.cpp +++ b/src/app/clusters/thermostat-server/thermostat-server-presets.cpp @@ -38,16 +38,16 @@ namespace { * @return true If the preset is valid i.e the PresetHandle (if not null) fits within size constraints and the presetScenario enum * value is valid. Otherwise, return false. */ -bool IsValidPresetEntry(const PresetStruct::Type & preset) +bool IsValidPresetEntry(const PresetStructWithOwnedMembers & preset) { // Check that the preset handle is not too long. - if (!preset.presetHandle.IsNull() && preset.presetHandle.Value().size() > kPresetHandleSize) + if (!preset.GetPresetHandle().IsNull() && preset.GetPresetHandle().Value().size() > kPresetHandleSize) { return false; } // Ensure we have a valid PresetScenario. - return (preset.presetScenario != PresetScenarioEnum::kUnknownEnumValue); + return (preset.GetPresetScenario() != PresetScenarioEnum::kUnknownEnumValue); } /** @@ -123,7 +123,7 @@ bool MatchingPendingPresetExists(Delegate * delegate, const PresetStructWithOwne * * @return true if a matching entry was found in the presets attribute list, false otherwise. */ -bool GetMatchingPresetInPresets(Delegate * delegate, const PresetStruct::Type & presetToMatch, +bool GetMatchingPresetInPresets(Delegate * delegate, const DataModel::Nullable & presetHandle, PresetStructWithOwnedMembers & matchingPreset) { VerifyOrReturnValue(delegate != nullptr, false); @@ -143,7 +143,7 @@ bool GetMatchingPresetInPresets(Delegate * delegate, const PresetStruct::Type & } // Note: presets coming from our delegate always have a handle. - if (presetToMatch.presetHandle.Value().data_equal(matchingPreset.GetPresetHandle().Value())) + if (presetHandle.Value().data_equal(matchingPreset.GetPresetHandle().Value())) { return true; } @@ -351,53 +351,71 @@ Status ThermostatAttrAccess::SetActivePreset(EndpointId endpoint, DataModel::Nul return Status::Success; } -CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Thermostat::Delegate * delegate, const PresetStruct::Type & preset) +CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Thermostat::Delegate * delegate, const PresetStruct::Type & newPreset) { + PresetStructWithOwnedMembers preset = newPreset; if (!IsValidPresetEntry(preset)) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } - if (preset.presetHandle.IsNull()) + if (preset.GetPresetHandle().IsNull()) { if (IsBuiltIn(preset)) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } + // Force to be false, if passed as null + preset.SetBuiltIn(false); } else { - auto & presetHandle = preset.presetHandle.Value(); - // Per spec we need to check that: // (a) There is an existing non-pending preset with this handle. PresetStructWithOwnedMembers matchingPreset; - if (!GetMatchingPresetInPresets(delegate, preset, matchingPreset)) + if (!GetMatchingPresetInPresets(delegate, preset.GetPresetHandle().Value(), matchingPreset)) { return CHIP_IM_GLOBAL_STATUS(NotFound); } // (b) There is no existing pending preset with this handle. - if (CountPresetsInPendingListWithPresetHandle(delegate, presetHandle) > 0) + if (CountPresetsInPendingListWithPresetHandle(delegate, preset.GetPresetHandle().Value()) > 0) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } + const auto & presetBuiltIn = preset.GetBuiltIn(); + const auto & matchingPresetBuiltIn = matchingPreset.GetBuiltIn(); // (c)/(d) The built-in fields do not have a mismatch. - // TODO: What's the story with nullability on the BuiltIn field? - if (!preset.builtIn.IsNull() && !matchingPreset.GetBuiltIn().IsNull() && - preset.builtIn.Value() != matchingPreset.GetBuiltIn().Value()) + if (presetBuiltIn.IsNull()) { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); + if (matchingPresetBuiltIn.IsNull()) + { + // This really shouldn't happen; internal presets should alway have built-in set + return CHIP_IM_GLOBAL_STATUS(InvalidInState); + } + preset.SetBuiltIn(matchingPresetBuiltIn.Value()); + } + else + { + if (matchingPresetBuiltIn.IsNull()) + { + // This really shouldn't happen; internal presets should alway have built-in set + return CHIP_IM_GLOBAL_STATUS(InvalidInState); + } + if (presetBuiltIn.Value() != matchingPresetBuiltIn.Value()) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } } } - if (!PresetScenarioExistsInPresetTypes(delegate, preset.presetScenario)) + if (!PresetScenarioExistsInPresetTypes(delegate, preset.GetPresetScenario())) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } - if (preset.name.HasValue() && !PresetTypeSupportsNames(delegate, preset.presetScenario)) + if (preset.GetName().HasValue() && !PresetTypeSupportsNames(delegate, preset.GetPresetScenario())) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } diff --git a/src/python_testing/TC_TSTAT_4_2.py b/src/python_testing/TC_TSTAT_4_2.py index 563d6f3f2eddfc..b133b1be2c8f22 100644 --- a/src/python_testing/TC_TSTAT_4_2.py +++ b/src/python_testing/TC_TSTAT_4_2.py @@ -246,8 +246,12 @@ async def test_TC_TSTAT_4_2(self): if self.pics_guard(self.check_pics("TSTAT.S.F08") and self.check_pics("TSTAT.S.A0050") and self.check_pics("TSTAT.S.Cfe.Rsp")): await self.send_atomic_request_begin_command() + # Set the new preset to a null built-in value; will be replaced with false on reading + test_presets = copy.deepcopy(new_presets) + test_presets[2].builtIn = NullValue + # Write to the presets attribute after calling AtomicRequest command - status = await self.write_presets(endpoint=endpoint, presets=new_presets) + status = await self.write_presets(endpoint=endpoint, presets=test_presets) status_ok = (status == Status.Success) asserts.assert_true(status_ok, "Presets write did not return Success as expected") @@ -268,8 +272,12 @@ async def test_TC_TSTAT_4_2(self): # Send the AtomicRequest begin command await self.send_atomic_request_begin_command() + # Set the existing preset to a null built-in value; will be replaced with true on reading + test_presets = copy.deepcopy(new_presets) + test_presets[0].builtIn = NullValue + # Write to the presets attribute after calling AtomicRequest command - await self.write_presets(endpoint=endpoint, presets=new_presets) + await self.write_presets(endpoint=endpoint, presets=test_presets) # Send the AtomicRequest commit command await self.send_atomic_request_commit_command() From d7053239c0d8730a015b10f092b522b92bbcce4d Mon Sep 17 00:00:00 2001 From: Junior Martinez <67972863+jmartinez-silabs@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:42:59 -0400 Subject: [PATCH 07/11] run a regen (#35233) --- .../chip-tool/zap-generated/cluster/logging/EntryToText.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/EntryToText.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/EntryToText.cpp index c97ff2590fa60b..3909bbec0d8746 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/logging/EntryToText.cpp +++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/EntryToText.cpp @@ -4485,8 +4485,6 @@ char const * AttributeIdToText(chip::ClusterId cluster, chip::AttributeId id) case chip::app::Clusters::EcosystemInformation::Id: { switch (id) { - case chip::app::Clusters::EcosystemInformation::Attributes::RemovedOn::Id: - return "RemovedOn"; case chip::app::Clusters::EcosystemInformation::Attributes::DeviceDirectory::Id: return "DeviceDirectory"; case chip::app::Clusters::EcosystemInformation::Attributes::LocationDirectory::Id: From 4d4fcb365c6cb27d1e706b117d1c0fb517e7f88f Mon Sep 17 00:00:00 2001 From: Tennessee Carmel-Veilleux Date: Tue, 27 Aug 2024 16:53:51 -0400 Subject: [PATCH 08/11] Update ColorControl bounds based on spec fixes (#35156) * Update color defaults in samples * Update color control server logic to avoid div-by-zero * Update TC-CC-2.1 with new bounds on ColorControl * Restyled by clang-format * More fixes to CI * Set default startup mireds to null * Fix some comments * Try to fix CI --------- Co-authored-by: Restyled.io --- .../all-clusters-app.matter | 6 ++--- .../all-clusters-common/all-clusters-app.zap | 6 ++--- ...de_colortemperaturelight_hbUnzYVeyn.matter | 6 ++--- ...tnode_colortemperaturelight_hbUnzYVeyn.zap | 6 ++--- ...tnode_extendedcolorlight_8lcaaYJVAa.matter | 6 ++--- ...rootnode_extendedcolorlight_8lcaaYJVAa.zap | 6 ++--- .../test_files/sample_zap_file.zap | 6 ++--- .../data_model/lighting-app-ethernet.matter | 6 ++--- .../data_model/lighting-app-ethernet.zap | 6 ++--- .../data_model/lighting-app-thread.matter | 6 ++--- .../data_model/lighting-app-thread.zap | 6 ++--- .../data_model/lighting-app-wifi.matter | 6 ++--- .../data_model/lighting-app-wifi.zap | 6 ++--- .../lighting-common/lighting-app.matter | 6 ++--- .../lighting-common/lighting-app.zap | 6 ++--- examples/lighting-app/qpg/zap/light.matter | 6 ++--- examples/lighting-app/qpg/zap/light.zap | 6 ++--- .../data_model/lighting-thread-app.matter | 6 ++--- .../silabs/data_model/lighting-thread-app.zap | 6 ++--- .../data_model/lighting-wifi-app.matter | 6 ++--- .../silabs/data_model/lighting-wifi-app.zap | 6 ++--- .../placeholder/linux/apps/app1/config.matter | 6 ++--- .../placeholder/linux/apps/app1/config.zap | 6 ++--- .../placeholder/linux/apps/app2/config.matter | 6 ++--- .../placeholder/linux/apps/app2/config.zap | 6 ++--- .../zap/tests/inputs/all-clusters-app.zap | 6 ++--- .../app-templates/endpoint_config.h | 22 ++++++++-------- .../color-control-server.cpp | 26 ++++++++++++++++--- .../color-control-server.h | 6 +++-- .../suites/certification/Test_TC_CC_2_1.yaml | 9 +++---- .../suites/certification/Test_TC_CC_6_5.yaml | 12 ++++----- .../data-model/chip/color-control-cluster.xml | 4 +-- 32 files changed, 126 insertions(+), 109 deletions(-) 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 dbaaf04237a9b4..6926da18d78a61 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 @@ -9168,10 +9168,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - persist attribute startUpColorTemperatureMireds; + persist attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0x1F; ram attribute clusterRevision default = 7; diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 9a736eea56fa30..cecc10f848c75c 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -18200,7 +18200,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -18216,7 +18216,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -18248,7 +18248,7 @@ "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.matter b/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.matter index d8758b3043edaa..0a591011e1aa1c 100644 --- a/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.matter +++ b/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.matter @@ -2356,10 +2356,10 @@ endpoint 1 { ram attribute enhancedCurrentHue default = 0x0000; ram attribute enhancedColorMode default = 0x02; ram attribute colorCapabilities default = 0x0000; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute attributeList; diff --git a/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.zap b/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.zap index d9ba9c9926b329..c53b6ae30a0c5f 100644 --- a/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.zap +++ b/examples/chef/devices/rootnode_colortemperaturelight_hbUnzYVeyn.zap @@ -3797,7 +3797,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3813,7 +3813,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3845,7 +3845,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.matter b/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.matter index 0ef1c969727a6d..53984bbd0a934d 100644 --- a/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.matter +++ b/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.matter @@ -2468,10 +2468,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x0000; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds default = 0x0000; - ram attribute startUpColorTemperatureMireds default = 0; + ram attribute startUpColorTemperatureMireds default = 0x00FA; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute attributeList; diff --git a/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.zap b/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.zap index f0fc3c6c264c20..9debf6b9ce30eb 100644 --- a/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.zap +++ b/examples/chef/devices/rootnode_extendedcolorlight_8lcaaYJVAa.zap @@ -3901,7 +3901,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3917,7 +3917,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3949,7 +3949,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 1, "maxInterval": 65534, 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 2e72d5b9db48b2..7794e2b510e669 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 @@ -5484,7 +5484,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5500,7 +5500,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5532,7 +5532,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.matter b/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.matter index 30f4d63c6a4e1e..9536120f9defb1 100644 --- a/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.matter +++ b/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.matter @@ -2535,10 +2535,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0x1F; ram attribute clusterRevision default = 7; diff --git a/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.zap b/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.zap index 87d6c2f4c2f2a8..933adab3f33f95 100644 --- a/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.zap +++ b/examples/lighting-app/bouffalolab/data_model/lighting-app-ethernet.zap @@ -4220,7 +4220,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4236,7 +4236,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4268,7 +4268,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.matter b/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.matter index 2abf5c7251711d..5a26a332b8e14d 100644 --- a/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.matter +++ b/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.matter @@ -2721,10 +2721,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0x1F; ram attribute clusterRevision default = 7; diff --git a/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.zap b/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.zap index 95e21f91d9870a..bac1dcc7b9b50c 100644 --- a/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.zap +++ b/examples/lighting-app/bouffalolab/data_model/lighting-app-thread.zap @@ -5196,7 +5196,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5212,7 +5212,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5244,7 +5244,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.matter b/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.matter index b9dfeee6321680..08172f4c3a4c42 100644 --- a/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.matter +++ b/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.matter @@ -2584,10 +2584,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0x1F; ram attribute clusterRevision default = 7; diff --git a/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.zap b/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.zap index 35a6c1aa4c9d4a..39f1cc21cb92dd 100644 --- a/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.zap +++ b/examples/lighting-app/bouffalolab/data_model/lighting-app-wifi.zap @@ -4403,7 +4403,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4419,7 +4419,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4451,7 +4451,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/lighting-common/lighting-app.matter b/examples/lighting-app/lighting-common/lighting-app.matter index 700b615879ceef..578fa85b4d3c1d 100644 --- a/examples/lighting-app/lighting-common/lighting-app.matter +++ b/examples/lighting-app/lighting-common/lighting-app.matter @@ -3173,10 +3173,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - persist attribute startUpColorTemperatureMireds; + persist attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0x1F; ram attribute clusterRevision default = 7; diff --git a/examples/lighting-app/lighting-common/lighting-app.zap b/examples/lighting-app/lighting-common/lighting-app.zap index e73563ebe35ec8..acdafaa92ef758 100644 --- a/examples/lighting-app/lighting-common/lighting-app.zap +++ b/examples/lighting-app/lighting-common/lighting-app.zap @@ -5733,7 +5733,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5749,7 +5749,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5781,7 +5781,7 @@ "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/qpg/zap/light.matter b/examples/lighting-app/qpg/zap/light.matter index 35e00056736443..8ce75010f66281 100644 --- a/examples/lighting-app/qpg/zap/light.matter +++ b/examples/lighting-app/qpg/zap/light.matter @@ -2698,10 +2698,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute attributeList; diff --git a/examples/lighting-app/qpg/zap/light.zap b/examples/lighting-app/qpg/zap/light.zap index 6390c7f37267d8..35b8f3003423a6 100644 --- a/examples/lighting-app/qpg/zap/light.zap +++ b/examples/lighting-app/qpg/zap/light.zap @@ -5881,7 +5881,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5897,7 +5897,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5929,7 +5929,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/silabs/data_model/lighting-thread-app.matter b/examples/lighting-app/silabs/data_model/lighting-thread-app.matter index f4c2928a35778e..d77bd6403bd573 100644 --- a/examples/lighting-app/silabs/data_model/lighting-thread-app.matter +++ b/examples/lighting-app/silabs/data_model/lighting-thread-app.matter @@ -2748,10 +2748,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - persist attribute startUpColorTemperatureMireds; + persist attribute startUpColorTemperatureMireds default = 0x00FA; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; diff --git a/examples/lighting-app/silabs/data_model/lighting-thread-app.zap b/examples/lighting-app/silabs/data_model/lighting-thread-app.zap index a3fd5e516aad09..42b5eb330426a5 100644 --- a/examples/lighting-app/silabs/data_model/lighting-thread-app.zap +++ b/examples/lighting-app/silabs/data_model/lighting-thread-app.zap @@ -5172,7 +5172,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5188,7 +5188,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -5220,7 +5220,7 @@ "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/lighting-app/silabs/data_model/lighting-wifi-app.matter b/examples/lighting-app/silabs/data_model/lighting-wifi-app.matter index 59919ee6b48737..978f277cf1f91a 100644 --- a/examples/lighting-app/silabs/data_model/lighting-wifi-app.matter +++ b/examples/lighting-app/silabs/data_model/lighting-wifi-app.matter @@ -3041,10 +3041,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x0000; ram attribute colorCapabilities default = 0x1F; - ram attribute colorTempPhysicalMinMireds default = 0x0000; - ram attribute colorTempPhysicalMaxMireds default = 0xFEFF; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - persist attribute startUpColorTemperatureMireds; + persist attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0x1F; ram attribute clusterRevision default = 7; diff --git a/examples/lighting-app/silabs/data_model/lighting-wifi-app.zap b/examples/lighting-app/silabs/data_model/lighting-wifi-app.zap index 6d438d10e14001..92e45461475418 100644 --- a/examples/lighting-app/silabs/data_model/lighting-wifi-app.zap +++ b/examples/lighting-app/silabs/data_model/lighting-wifi-app.zap @@ -4947,7 +4947,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4963,7 +4963,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4995,7 +4995,7 @@ "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/placeholder/linux/apps/app1/config.matter b/examples/placeholder/linux/apps/app1/config.matter index a3a313d1c41b70..a92f0a495885f3 100644 --- a/examples/placeholder/linux/apps/app1/config.matter +++ b/examples/placeholder/linux/apps/app1/config.matter @@ -9575,10 +9575,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x00; ram attribute colorCapabilities default = 0x00; - ram attribute colorTempPhysicalMinMireds default = 0x00; - ram attribute colorTempPhysicalMaxMireds default = 0xfeff; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0; ram attribute clusterRevision default = 7; diff --git a/examples/placeholder/linux/apps/app1/config.zap b/examples/placeholder/linux/apps/app1/config.zap index 0dff614f290388..c6bf896bc9e398 100644 --- a/examples/placeholder/linux/apps/app1/config.zap +++ b/examples/placeholder/linux/apps/app1/config.zap @@ -14786,7 +14786,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14802,7 +14802,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xfeff", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14834,7 +14834,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/placeholder/linux/apps/app2/config.matter b/examples/placeholder/linux/apps/app2/config.matter index e1920f474f61bc..facd9c94e4547e 100644 --- a/examples/placeholder/linux/apps/app2/config.matter +++ b/examples/placeholder/linux/apps/app2/config.matter @@ -9514,10 +9514,10 @@ endpoint 1 { ram attribute colorLoopStartEnhancedHue default = 0x2300; ram attribute colorLoopStoredEnhancedHue default = 0x00; ram attribute colorCapabilities default = 0x00; - ram attribute colorTempPhysicalMinMireds default = 0x00; - ram attribute colorTempPhysicalMaxMireds default = 0xfeff; + ram attribute colorTempPhysicalMinMireds default = 0x009A; + ram attribute colorTempPhysicalMaxMireds default = 0x01C6; ram attribute coupleColorTempToLevelMinMireds; - ram attribute startUpColorTemperatureMireds; + ram attribute startUpColorTemperatureMireds default = 0x00FA; ram attribute featureMap default = 0; ram attribute clusterRevision default = 7; diff --git a/examples/placeholder/linux/apps/app2/config.zap b/examples/placeholder/linux/apps/app2/config.zap index a3b75ae5203e2f..22e1cb640b777e 100644 --- a/examples/placeholder/linux/apps/app2/config.zap +++ b/examples/placeholder/linux/apps/app2/config.zap @@ -14546,7 +14546,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14562,7 +14562,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xfeff", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14594,7 +14594,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/scripts/tools/zap/tests/inputs/all-clusters-app.zap b/scripts/tools/zap/tests/inputs/all-clusters-app.zap index 1cbd309ffa123a..ccc41909f62992 100644 --- a/scripts/tools/zap/tests/inputs/all-clusters-app.zap +++ b/scripts/tools/zap/tests/inputs/all-clusters-app.zap @@ -17711,7 +17711,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0000", + "defaultValue": "0x009A", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -17727,7 +17727,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFEFF", + "defaultValue": "0x01C6", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -17759,7 +17759,7 @@ "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x00FA", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h index 3146dd902ffb68..9eff91f3832e75 100644 --- a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h +++ b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h @@ -322,15 +322,15 @@ { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0x1 }, /* ScheduleProgrammingVisibility */ \ \ /* Endpoint: 1, Cluster: Color Control (server) */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* WhitePointX */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* WhitePointY */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointRX */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointRY */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointGX */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointGY */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointBX */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointBY */ \ - { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* StartUpColorTemperatureMireds */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* WhitePointX */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* WhitePointY */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointRX */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointRY */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointGX */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointGY */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointBX */ \ + { (uint16_t) 0x0, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* ColorPointBY */ \ + { (uint16_t) 0xFA, (uint16_t) 0x0, (uint16_t) 0xFEFF }, /* StartUpColorTemperatureMireds */ \ \ /* Endpoint: 1, Cluster: Ballast Configuration (server) */ \ { (uint16_t) 0x1, (uint16_t) 0x1, (uint16_t) 0xFE }, /* MinLevel */ \ @@ -1495,8 +1495,8 @@ { ZAP_SIMPLE_DEFAULT(0x2300), 0x00004005, 2, ZAP_TYPE(INT16U), 0 }, /* ColorLoopStartEnhancedHue */ \ { ZAP_SIMPLE_DEFAULT(0x0000), 0x00004006, 2, ZAP_TYPE(INT16U), 0 }, /* ColorLoopStoredEnhancedHue */ \ { ZAP_SIMPLE_DEFAULT(0x1F), 0x0000400A, 2, ZAP_TYPE(BITMAP16), 0 }, /* ColorCapabilities */ \ - { ZAP_SIMPLE_DEFAULT(0x0000), 0x0000400B, 2, ZAP_TYPE(INT16U), 0 }, /* ColorTempPhysicalMinMireds */ \ - { ZAP_SIMPLE_DEFAULT(0xFEFF), 0x0000400C, 2, ZAP_TYPE(INT16U), 0 }, /* ColorTempPhysicalMaxMireds */ \ + { ZAP_SIMPLE_DEFAULT(0x009A), 0x0000400B, 2, ZAP_TYPE(INT16U), 0 }, /* ColorTempPhysicalMinMireds */ \ + { ZAP_SIMPLE_DEFAULT(0x01C6), 0x0000400C, 2, ZAP_TYPE(INT16U), 0 }, /* ColorTempPhysicalMaxMireds */ \ { ZAP_EMPTY_DEFAULT(), 0x0000400D, 2, ZAP_TYPE(INT16U), 0 }, /* CoupleColorTempToLevelMinMireds */ \ { ZAP_MIN_MAX_DEFAULTS_INDEX(36), 0x00004010, 2, ZAP_TYPE(INT16U), \ ZAP_ATTRIBUTE_MASK(MIN_MAX) | ZAP_ATTRIBUTE_MASK(TOKENIZE) | ZAP_ATTRIBUTE_MASK(WRITABLE) | \ diff --git a/src/app/clusters/color-control-server/color-control-server.cpp b/src/app/clusters/color-control-server/color-control-server.cpp index 406d06f4b1e794..05553525258d23 100644 --- a/src/app/clusters/color-control-server/color-control-server.cpp +++ b/src/app/clusters/color-control-server/color-control-server.cpp @@ -15,6 +15,8 @@ * limitations under the License. */ +#include + #include "color-control-server.h" #include #include @@ -2586,6 +2588,9 @@ Status ColorControlServer::moveToColorTemp(EndpointId aEndpoint, uint16_t colorT uint16_t temperatureMin = MIN_TEMPERATURE_VALUE; Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &temperatureMin); + // Avoid potential divide-by-zero in future Kelvin conversions. + temperatureMin = std::max(static_cast(1u), temperatureMin); + uint16_t temperatureMax = MAX_TEMPERATURE_VALUE; Attributes::ColorTempPhysicalMaxMireds::Get(endpoint, &temperatureMax); @@ -2640,6 +2645,9 @@ uint16_t ColorControlServer::getTemperatureCoupleToLevelMin(EndpointId endpoint) { // Not less than the physical min. Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &colorTemperatureCoupleToLevelMin); + + // Avoid potential divide-by-zero in future Kelvin conversions. + colorTemperatureCoupleToLevelMin = std::max(static_cast(1u), colorTemperatureCoupleToLevelMin); } return colorTemperatureCoupleToLevelMin; @@ -2670,11 +2678,11 @@ void ColorControlServer::startUpColorTempCommand(EndpointId endpoint) // EnhancedColorMode attributes SHALL be set to 0x02 (color temperature). The values of // the StartUpColorTemperatureMireds attribute are listed in the table below. // Value Action on power up - // 0x0000-0xffef Set the ColorTemperatureMireds attribute to this value. + // 0x0001-0xffef Set the ColorTemperatureMireds attribute to this value. // null Set the ColorTemperatureMireds attribute to its previous value. // Initialize startUpColorTempMireds to "maintain previous value" value null - app::DataModel::Nullable startUpColorTemp; + app::DataModel::Nullable startUpColorTemp = DataModel::NullNullable; Status status = Attributes::StartUpColorTemperatureMireds::Get(endpoint, startUpColorTemp); if (status == Status::Success && !startUpColorTemp.IsNull()) @@ -2686,6 +2694,8 @@ void ColorControlServer::startUpColorTempCommand(EndpointId endpoint) { uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &tempPhysicalMin); + // Avoid potential divide-by-zero in future Kelvin conversions. + tempPhysicalMin = std::max(static_cast(1u), tempPhysicalMin); uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; Attributes::ColorTempPhysicalMaxMireds::Get(endpoint, &tempPhysicalMax); @@ -2807,6 +2817,9 @@ bool ColorControlServer::moveColorTempCommand(app::CommandHandler * commandObj, Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &tempPhysicalMin); Attributes::ColorTempPhysicalMaxMireds::Get(endpoint, &tempPhysicalMax); + // Avoid potential divide-by-zero in future Kelvin conversions. + tempPhysicalMin = std::max(static_cast(1u), tempPhysicalMin); + // New command. Need to stop any active transitions. stopAllColorTransitions(endpoint); @@ -2817,7 +2830,7 @@ bool ColorControlServer::moveColorTempCommand(app::CommandHandler * commandObj, } // Per spec, colorTemperatureMinimumMireds field is limited to ColorTempPhysicalMinMireds and - // when colorTemperatureMinimumMireds field is 0, ColorTempPhysicalMinMireds shall be used (always >= to 0) + // when colorTemperatureMinimumMireds field is 0, ColorTempPhysicalMinMireds shall be used (always > 0) if (colorTemperatureMinimum < tempPhysicalMin) { colorTemperatureMinimum = tempPhysicalMin; @@ -2933,8 +2946,11 @@ bool ColorControlServer::stepColorTempCommand(app::CommandHandler * commandObj, Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &tempPhysicalMin); Attributes::ColorTempPhysicalMaxMireds::Get(endpoint, &tempPhysicalMax); + // Avoid potential divide-by-zero in future Kelvin conversions. + tempPhysicalMin = std::max(static_cast(1u), tempPhysicalMin); + // Per spec, colorTemperatureMinimumMireds field is limited to ColorTempPhysicalMinMireds and - // when colorTemperatureMinimumMireds field is 0, ColorTempPhysicalMinMireds shall be used (always >= to 0) + // when colorTemperatureMinimumMireds field is 0, ColorTempPhysicalMinMireds shall be used (always > 0) if (colorTemperatureMinimum < tempPhysicalMin) { colorTemperatureMinimum = tempPhysicalMin; @@ -2953,6 +2969,8 @@ bool ColorControlServer::stepColorTempCommand(app::CommandHandler * commandObj, // now, kick off the state machine. colorTempTransitionState->initialValue = 0; Attributes::ColorTemperatureMireds::Get(endpoint, &colorTempTransitionState->initialValue); + colorTempTransitionState->initialValue = std::max(static_cast(1u), colorTempTransitionState->initialValue); + colorTempTransitionState->currentValue = colorTempTransitionState->initialValue; if (stepMode == HueStepMode::kUp) diff --git a/src/app/clusters/color-control-server/color-control-server.h b/src/app/clusters/color-control-server/color-control-server.h index 1ed9e33c403538..88343e7cb0f4af 100644 --- a/src/app/clusters/color-control-server/color-control-server.h +++ b/src/app/clusters/color-control-server/color-control-server.h @@ -43,8 +43,10 @@ static constexpr uint16_t TRANSITION_STEPS_PER_1S = static constexpr uint16_t MIN_CIE_XY_VALUE = 0; static constexpr uint16_t MAX_CIE_XY_VALUE = 0xfeff; // this value comes directly from the ZCL specification table 5.3 -static constexpr uint16_t MIN_TEMPERATURE_VALUE = 0; -static constexpr uint16_t MAX_TEMPERATURE_VALUE = 0xfeff; +// Logically relevant color temperatures are between 1000K and 9000K at the very most (and this is still +// not frequent). Our implementation can default to those reasonable maxima to avoid issues related to range. +static constexpr uint16_t MIN_TEMPERATURE_VALUE = 111u; // 111 mireds == 9000K +static constexpr uint16_t MAX_TEMPERATURE_VALUE = 1000u; // 1000 mireds == 1000K static constexpr uint8_t MIN_HUE_VALUE = 0; static constexpr uint8_t MAX_HUE_VALUE = 254; diff --git a/src/app/tests/suites/certification/Test_TC_CC_2_1.yaml b/src/app/tests/suites/certification/Test_TC_CC_2_1.yaml index 294cb9a9cc81ee..b24a3d094ec161 100644 --- a/src/app/tests/suites/certification/Test_TC_CC_2_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_CC_2_1.yaml @@ -115,7 +115,7 @@ tests: response: constraints: type: int16u - minValue: 0 + minValue: 1 maxValue: 65279 - label: "Step 10: TH reads from the DUT the (0x0008) ColorMode attribute" @@ -250,7 +250,7 @@ tests: response: constraints: type: int16u - minValue: 0 + minValue: 1 maxValue: 65279 - label: @@ -262,7 +262,7 @@ tests: response: constraints: type: int16u - minValue: 0 + minValue: 1 maxValue: 65279 - label: @@ -286,8 +286,7 @@ tests: response: constraints: type: int16u - minValue: 0 - maxValue: 65279 + python: (value is None) or ((value >= 1) and (value <= 65279)) #Defined Primaries Information Attribute Set - label: diff --git a/src/app/tests/suites/certification/Test_TC_CC_6_5.yaml b/src/app/tests/suites/certification/Test_TC_CC_6_5.yaml index 3bab44386e2181..38f33654e10177 100755 --- a/src/app/tests/suites/certification/Test_TC_CC_6_5.yaml +++ b/src/app/tests/suites/certification/Test_TC_CC_6_5.yaml @@ -55,7 +55,7 @@ tests: response: constraints: type: int16u - minValue: 0 + minValue: 1 maxValue: 65279 - label: "Step 0d: TH reads ColorTempPhysicalMinMireds attribute from DUT." @@ -65,7 +65,7 @@ tests: response: constraints: type: int16u - minValue: 0 + minValue: 1 maxValue: 65279 - label: "Step 0e: TH reads ColorTempPhysicalMaxMireds attribute from DUT." @@ -75,7 +75,7 @@ tests: response: constraints: type: int16u - minValue: 0 + minValue: 1 maxValue: 65279 - label: @@ -87,8 +87,7 @@ tests: response: constraints: type: int16u - minValue: 0 - maxValue: 65279 + python: (value is None) or ((value >= 1) and (value <= 65279)) - label: "Step 2a: TH writes to StartUpColorTemperatureMireds attribute with @@ -141,8 +140,7 @@ tests: saveAs: StartUpColorTemperatureMiredsValue constraints: type: int16u - minValue: 0 - maxValue: 65279 + python: (value is None) or ((value >= 1) and (value <= 65279)) - label: "Step 2b: Verify that the DUT response contains diff --git a/src/app/zap-templates/zcl/data-model/chip/color-control-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/color-control-cluster.xml index f72140e712a703..3623ce1ccf925c 100644 --- a/src/app/zap-templates/zcl/data-model/chip/color-control-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/color-control-cluster.xml @@ -141,7 +141,7 @@ limitations under the License. CompensationText - ColorTemperatureMireds + ColorTemperatureMireds ColorMode @@ -384,7 +384,7 @@ limitations under the License. ColorLoopStartEnhancedHue ColorLoopStoredEnhancedHue ColorCapabilities - ColorTempPhysicalMinMireds + ColorTempPhysicalMinMireds ColorTempPhysicalMaxMireds From 611347e2e34d74a585e1b93a8044399b48893a7b Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Tue, 27 Aug 2024 23:42:33 +0200 Subject: [PATCH 09/11] Add plumbing in FS example for TimeoutMs parameter in KeepActive (#35174) --------- Co-authored-by: Restyled.io Co-authored-by: Yufeng Wang --- .../pigweed/protos/fabric_admin_service.proto | 1 + examples/fabric-admin/rpc/RpcServer.cpp | 27 ++++++++++++------- .../fabric-bridge-app/linux/RpcClient.cpp | 3 ++- .../linux/include/RpcClient.h | 2 +- examples/fabric-bridge-app/linux/main.cpp | 23 ++++++++++------ 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/examples/common/pigweed/protos/fabric_admin_service.proto b/examples/common/pigweed/protos/fabric_admin_service.proto index 3df3e2a7aa5f5f..4d7b2075a2ed44 100644 --- a/examples/common/pigweed/protos/fabric_admin_service.proto +++ b/examples/common/pigweed/protos/fabric_admin_service.proto @@ -27,6 +27,7 @@ message DeviceCommissioningInfo { message KeepActiveParameters { uint64 node_id = 1; uint32 stay_active_duration_ms = 2; + uint32 timeout_ms = 3; } // Define the response message to convey the status of the operation diff --git a/examples/fabric-admin/rpc/RpcServer.cpp b/examples/fabric-admin/rpc/RpcServer.cpp index fca25c25a1301a..94bca111d9c421 100644 --- a/examples/fabric-admin/rpc/RpcServer.cpp +++ b/examples/fabric-admin/rpc/RpcServer.cpp @@ -151,23 +151,31 @@ class FabricAdmin final : public rpc::FabricAdmin, public IcdManager::Delegate // TODO(#33221): We should really be using ScopedNode, but that requires larger fix in communication between // fabric-admin and fabric-bridge. For now we make the assumption that there is only one fabric used by // fabric-admin. - KeepActiveWorkData * data = chip::Platform::New(this, request.node_id, request.stay_active_duration_ms); + KeepActiveWorkData * data = + Platform::New(this, request.node_id, request.stay_active_duration_ms, request.timeout_ms); VerifyOrReturnValue(data, pw::Status::Internal()); chip::DeviceLayer::PlatformMgr().ScheduleWork(KeepActiveWork, reinterpret_cast(data)); return pw::OkStatus(); } - void ScheduleSendingKeepActiveOnCheckIn(chip::NodeId nodeId, uint32_t stayActiveDurationMs) + void ScheduleSendingKeepActiveOnCheckIn(NodeId nodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs) { // Needs for accessing mPendingCheckIn assertChipStackLockedByCurrentThread(); - auto timeNow = System::SystemClock().GetMonotonicTimestamp(); - // Spec says we should expire the request 60 mins after we get it - System::Clock::Timestamp expiryTimestamp = timeNow + System::Clock::Seconds64(60 * 60); + auto timeNow = System::SystemClock().GetMonotonicTimestamp(); + System::Clock::Timestamp expiryTimestamp = timeNow + System::Clock::Milliseconds64(timeoutMs); KeepActiveDataForCheckIn checkInData = { .mStayActiveDurationMs = stayActiveDurationMs, .mRequestExpiryTimestamp = expiryTimestamp }; - mPendingCheckIn[nodeId] = checkInData; + + auto it = mPendingCheckIn.find(nodeId); + if (it != mPendingCheckIn.end()) + { + checkInData.mStayActiveDurationMs = std::max(checkInData.mStayActiveDurationMs, it->second.mStayActiveDurationMs); + checkInData.mRequestExpiryTimestamp = std::max(checkInData.mRequestExpiryTimestamp, it->second.mRequestExpiryTimestamp); + } + + mPendingCheckIn[nodeId] = checkInData; } private: @@ -179,19 +187,20 @@ class FabricAdmin final : public rpc::FabricAdmin, public IcdManager::Delegate struct KeepActiveWorkData { - KeepActiveWorkData(FabricAdmin * fabricAdmin, chip::NodeId nodeId, uint32_t stayActiveDurationMs) : - mFabricAdmin(fabricAdmin), mNodeId(nodeId), mStayActiveDurationMs(stayActiveDurationMs) + KeepActiveWorkData(FabricAdmin * fabricAdmin, NodeId nodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs) : + mFabricAdmin(fabricAdmin), mNodeId(nodeId), mStayActiveDurationMs(stayActiveDurationMs), mTimeoutMs(timeoutMs) {} FabricAdmin * mFabricAdmin; chip::NodeId mNodeId; uint32_t mStayActiveDurationMs; + uint32_t mTimeoutMs; }; static void KeepActiveWork(intptr_t arg) { KeepActiveWorkData * data = reinterpret_cast(arg); - data->mFabricAdmin->ScheduleSendingKeepActiveOnCheckIn(data->mNodeId, data->mStayActiveDurationMs); + data->mFabricAdmin->ScheduleSendingKeepActiveOnCheckIn(data->mNodeId, data->mStayActiveDurationMs, data->mTimeoutMs); chip::Platform::Delete(data); } diff --git a/examples/fabric-bridge-app/linux/RpcClient.cpp b/examples/fabric-bridge-app/linux/RpcClient.cpp index 817479d8482af1..633b652b74180b 100644 --- a/examples/fabric-bridge-app/linux/RpcClient.cpp +++ b/examples/fabric-bridge-app/linux/RpcClient.cpp @@ -189,11 +189,12 @@ CommissionNode(chip::Controller::CommissioningWindowPasscodeParams params, Vendo return WaitForResponse(call); } -CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs) +CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs) { chip_rpc_KeepActiveParameters params; params.node_id = nodeId; params.stay_active_duration_ms = stayActiveDurationMs; + params.timeout_ms = timeoutMs; // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler // function and the call will complete. diff --git a/examples/fabric-bridge-app/linux/include/RpcClient.h b/examples/fabric-bridge-app/linux/include/RpcClient.h index c7628a8c3ba73c..f183b2ed38aa2b 100644 --- a/examples/fabric-bridge-app/linux/include/RpcClient.h +++ b/examples/fabric-bridge-app/linux/include/RpcClient.h @@ -76,4 +76,4 @@ OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams para CHIP_ERROR CommissionNode(chip::Controller::CommissioningWindowPasscodeParams params, chip::VendorId vendorId, uint16_t productId); -CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs); +CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs); diff --git a/examples/fabric-bridge-app/linux/main.cpp b/examples/fabric-bridge-app/linux/main.cpp index eeb290210caa6b..08c70782de16ab 100644 --- a/examples/fabric-bridge-app/linux/main.cpp +++ b/examples/fabric-bridge-app/linux/main.cpp @@ -199,27 +199,34 @@ void BridgedDeviceInformationCommandHandler::InvokeCommand(HandlerContext & hand EndpointId endpointId = handlerContext.mRequestPath.mEndpointId; ChipLogProgress(NotSpecified, "Received command to KeepActive on Endpoint: %d", endpointId); - BridgedDevice * device = BridgeDeviceMgr().GetDevice(endpointId); - handlerContext.SetCommandHandled(); - if (device == nullptr || !device->IsIcd()) + BridgedDeviceBasicInformation::Commands::KeepActive::DecodableType commandData; + if (DataModel::Decode(handlerContext.mPayload, commandData) != CHIP_NO_ERROR) { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::Failure); + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::InvalidCommand); return; } - BridgedDeviceBasicInformation::Commands::KeepActive::DecodableType commandData; - if (DataModel::Decode(handlerContext.mPayload, commandData) != CHIP_NO_ERROR) + const uint32_t kMinTimeoutMs = 30 * 1000; + const uint32_t kMaxTimeoutMs = 60 * 60 * 1000; + if (commandData.timeoutMs < kMinTimeoutMs || commandData.timeoutMs > kMaxTimeoutMs) { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::InvalidCommand); + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::ConstraintError); + return; + } + + BridgedDevice * device = BridgeDeviceMgr().GetDevice(endpointId); + if (device == nullptr || !device->IsIcd()) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::Failure); return; } Status status = Status::Failure; #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE - if (KeepActive(device->GetNodeId(), commandData.stayActiveDuration) == CHIP_NO_ERROR) + if (KeepActive(device->GetNodeId(), commandData.stayActiveDuration, commandData.timeoutMs) == CHIP_NO_ERROR) { ChipLogProgress(NotSpecified, "KeepActive successfully processed"); status = Status::Success; From 82ef12be071b52104aa602bc934bd570bb4ceb3f Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Tue, 27 Aug 2024 23:53:28 +0200 Subject: [PATCH 10/11] Updated BRBINFO-4-1 test after timeoutMs added (#35160) --------- Co-authored-by: Restyled.io Co-authored-by: saurabhst --- src/python_testing/TC_BRBINFO_4_1.py | 165 +++++++++++++++++---------- 1 file changed, 102 insertions(+), 63 deletions(-) diff --git a/src/python_testing/TC_BRBINFO_4_1.py b/src/python_testing/TC_BRBINFO_4_1.py index ea230150c27fdb..5e1474adc35038 100644 --- a/src/python_testing/TC_BRBINFO_4_1.py +++ b/src/python_testing/TC_BRBINFO_4_1.py @@ -30,6 +30,7 @@ import chip.clusters as Clusters from chip import ChipDeviceCtrl +from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, SimpleEventCallback, TestStep, async_test_body, default_matter_test_main from mobly import asserts @@ -57,14 +58,25 @@ def desc_TC_BRBINFO_4_1(self) -> str: def steps_TC_BRBINFO_4_1(self) -> list[TestStep]: steps = [ - TestStep("0", "DUT commissioned", is_commissioning=True), - TestStep("0a", "Preconditions"), - TestStep("1a", "TH reads from the ICD the A_IDLE_MODE_DURATION, A_ACTIVE_MODE_DURATION, and ACTIVE_MODE_THRESHOLD attributes"), - TestStep("1b", "Simple KeepActive command w/ subscription. ActiveChanged event received by TH contains PromisedActiveDuration"), - TestStep("2", "Sends 3x KeepActive commands w/ subscription. ActiveChanged event received ONCE and contains PromisedActiveDuration"), - TestStep("3", "TH waits for check-in from TH_ICD to confirm no additional ActiveChanged events are recieved"), - TestStep("4", "KeepActive not returned after 60 minutes of offline ICD"), - ] + TestStep("0", "DUT commissioned and preconditions", is_commissioning=True), + TestStep("1", "TH reads from the ICD the A_IDLE_MODE_DURATION, A_ACTIVE_MODE_DURATION, and ACTIVE_MODE_THRESHOLD attributes"), + TestStep("2", "Setting up subscribe to ActiveChange event"), + TestStep("3", "Check TimeoutMs too low fails"), + TestStep("4", "Check TimeoutMs too high fails"), + TestStep("5", "Check KeepActive successful with valid command parameters lowest possible TimeoutMs"), + TestStep("6", "Validate previous command results in ActiveChanged event shortly after ICD device checks-in"), + TestStep("7", "Check KeepActive successful with valid command parameters highest possible TimeoutMs"), + TestStep("8", "Validate previous command results in ActiveChanged event shortly after ICD device checks-in"), + TestStep("9", "Send multiple KeepActive commands during window where ICD device will not check in"), + TestStep("10", "Validate previous command results in single ActiveChanged event shortly after ICD device checks-in"), + TestStep("11", "Validate we received no additional ActiveChanged event after subsequent ICD check in"), + TestStep("12", "Send KeepActive command with shortest TimeoutMs value while TH_ICD is prevented from sending check-ins"), + TestStep("13", "TH allows TH_ICD to resume sending check-ins after timeout should have expired"), + TestStep("14", "Wait for TH_ICD to check into TH twice, then confirm we have had no new ActiveChanged events reported from DUT"), + TestStep("15", "Send KeepActive command with shortest TimeoutMs value while TH_ICD is prevented from sending check-ins"), + TestStep("16", "Wait 15 seconds then send second KeepActive command with double the TimeoutMs value of the previous step"), + TestStep("17", "TH allows TH_ICD to resume sending check-ins after timeout from step 15 expired but before second timeout from step 16 still valid"), + TestStep("18", "Wait for TH_ICD to check into TH, then confirm we have received new event from DUT")] return steps def _ask_for_vendor_commissioniong_ux_operation(self, discriminator, setupPinCode, setupManualCode, setupQRCode): @@ -77,9 +89,9 @@ def _ask_for_vendor_commissioniong_ux_operation(self, discriminator, setupPinCod f"If using FabricSync Admin test app, you may type:\n" f">>> pairing onnetwork 111 {setupPinCode} --icd-registration true") - async def _send_keep_active_command(self, duration, endpoint_id) -> int: + async def _send_keep_active_command(self, stay_active_duration_ms, timeout_ms, endpoint_id) -> int: logging.info("Sending keep active command") - keep_active = await self.default_controller.SendCommand(nodeid=self.dut_node_id, endpoint=endpoint_id, payload=Clusters.Objects.BridgedDeviceBasicInformation.Commands.KeepActive(stayActiveDuration=duration)) + keep_active = await self.default_controller.SendCommand(nodeid=self.dut_node_id, endpoint=endpoint_id, payload=Clusters.Objects.BridgedDeviceBasicInformation.Commands.KeepActive(stayActiveDuration=stay_active_duration_ms, timeoutMs=timeout_ms)) return keep_active async def _wait_for_active_changed_event(self, timeout_s) -> int: @@ -189,10 +201,6 @@ async def test_TC_BRBINFO_4_1(self): logging.info(f"Dynamic endpoint is {dynamic_endpoint_id}") self.step("0") - - # Preconditions - self.step("0a") - logging.info("Ensuring DUT is commissioned to TH") # Confirms commissioning of DUT on TH as it reads its fature map @@ -205,15 +213,7 @@ async def test_TC_BRBINFO_4_1(self): logging.info("Ensuring ICD is commissioned to TH") - # Confirms commissioning of ICD on TH as it reads its feature map - await self._read_attribute_expect_success( - _ROOT_ENDPOINT_ID, - basic_info_cluster, - basic_info_attributes.FeatureMap, - self.icd_nodeid - ) - - self.step("1a") + self.step("1") idle_mode_duration_s = await self._read_attribute_expect_success( _ROOT_ENDPOINT_ID, @@ -231,9 +231,7 @@ async def test_TC_BRBINFO_4_1(self): ) logging.info(f"ActiveModeDurationMs: {active_mode_duration_ms}") - self.step("1b") - - # Subscription to ActiveChanged + self.step("2") event = brb_info_cluster.Events.ActiveChanged self.q = queue.Queue() urgent = 1 @@ -241,66 +239,107 @@ async def test_TC_BRBINFO_4_1(self): subscription = await self.default_controller.ReadEvent(nodeid=self.dut_node_id, events=[(dynamic_endpoint_id, event, urgent)], reportInterval=[1, 3]) subscription.SetEventUpdateCallback(callback=cb) + self.step("3") stay_active_duration_ms = 1000 - logging.info(f"Sending KeepActiveCommand({stay_active_duration_ms}ms)") - await self._send_keep_active_command(stay_active_duration_ms, dynamic_endpoint_id) - - logging.info("Waiting for ActiveChanged from DUT...") - timeout_s = idle_mode_duration_s + max(active_mode_duration_ms, stay_active_duration_ms)/1000 - promised_active_duration_ms = await self._wait_for_active_changed_event(timeout_s) + keep_active_timeout_ms = 29999 + try: + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + asserts.fail("KeepActive with invalid TimeoutMs was expected to fail") + except InteractionModelError as e: + asserts.assert_equal(e.status, Status.ConstraintError, + "DUT sent back an unexpected error, we were expecting ConstraintError") + self.step("4") + keep_active_timeout_ms = 3600001 + try: + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + asserts.fail("KeepActive with invalid TimeoutMs was expected to fail") + except InteractionModelError as e: + asserts.assert_equal(e.status, Status.ConstraintError, + "DUT sent back an unexpected error, we were expecting ConstraintError") + + self.step("5") + keep_active_timeout_ms = 30000 + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + + self.step("6") + wait_for_icd_checkin_timeout_s = idle_mode_duration_s + max(active_mode_duration_ms, stay_active_duration_ms)/1000 + wait_for_dut_event_subscription_s = 5 + # This will throw exception if timeout is exceeded. + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) + promised_active_duration_ms = await self._wait_for_active_changed_event(timeout_s=wait_for_dut_event_subscription_s) asserts.assert_greater_equal(promised_active_duration_ms, stay_active_duration_ms, "PromisedActiveDuration < StayActiveDuration") + asserts.assert_equal(self.q.qsize(), 0, "Unexpected event received from DUT") - self.step("2") + self.step("7") + keep_active_timeout_ms = 3600000 + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + + self.step("8") + # This will throw exception if timeout is exceeded. + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) + promised_active_duration_ms = await self._wait_for_active_changed_event(timeout_s=wait_for_dut_event_subscription_s) + asserts.assert_greater_equal(promised_active_duration_ms, stay_active_duration_ms, + "PromisedActiveDuration < StayActiveDuration") + asserts.assert_equal(self.q.qsize(), 0, "Unexpected event received from DUT") - # Prevent icd app from sending any check-in messages. + self.step("9") self.pause_th_icd_server(check_state=True) # sends 3x keep active commands stay_active_duration_ms = 2000 + keep_active_timeout_ms = 60000 logging.info(f"Sending first KeepActiveCommand({stay_active_duration_ms})") - await self._send_keep_active_command(stay_active_duration_ms, dynamic_endpoint_id) + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) logging.info(f"Sending second KeepActiveCommand({stay_active_duration_ms})") - await self._send_keep_active_command(stay_active_duration_ms, dynamic_endpoint_id) + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) logging.info(f"Sending third KeepActiveCommand({stay_active_duration_ms})") - await self._send_keep_active_command(stay_active_duration_ms, dynamic_endpoint_id) - self.resume_th_icd_server(check_state=True) + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) - logging.info("Waiting for ActiveChanged from DUT...") - promised_active_duration_ms = await self._wait_for_active_changed_event((idle_mode_duration_s + max(active_mode_duration_ms, stay_active_duration_ms))/1000) + self.step("10") + self.resume_th_icd_server(check_state=True) + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) + promised_active_duration_ms = await self._wait_for_active_changed_event(timeout_s=wait_for_dut_event_subscription_s) asserts.assert_equal(self.q.qsize(), 0, "More than one event received from DUT") - self.step("3") - await self.default_controller.WaitForActive(self.icd_nodeid, stayActiveDurationMs=5000) + self.step("11") + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) asserts.assert_equal(self.q.qsize(), 0, "More than one event received from DUT") - self.step("4") + self.step("12") + self.pause_th_icd_server(check_state=True) + stay_active_duration_ms = 2000 + keep_active_timeout_ms = 30000 + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + + self.step("13") + time.sleep(30) + self.resume_th_icd_server(check_state=True) - logging.info("TH waiting for checkin from TH_ICD...") - await self.default_controller.WaitForActive(self.icd_nodeid, stayActiveDurationMs=10000) - stay_active_duration_ms = 10000 - logging.info(f"Sending KeepActiveCommand({stay_active_duration_ms})") - await self._send_keep_active_command(stay_active_duration_ms, dynamic_endpoint_id) + self.step("14") + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) + asserts.assert_equal(self.q.qsize(), 0, "Unexpected event received from DUT") + self.step("15") self.pause_th_icd_server(check_state=True) - # If we are seeing assertion below fail test assumption is likely incorrect. - # Test assumes after TH waits for check-in from TH_ICD it has enough time to - # call the KeepActive command and pause the app to prevent it from checking in - # after DUT recieved the KeepActive command. Should this assumption be incorrect - # we could look into using existing ICDTestEventTriggerEvent, or adding test - # event trigger that will help suppress check-ins from the TH_ICD_SERVER. - asserts.assert_equal(self.q.qsize(), 0, "") - - if not self.is_ci: - logging.info("Waiting for 60 minutes") - time.sleep(60*60) + stay_active_duration_ms = 2000 + keep_active_timeout_ms = 30000 + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + + self.step("16") + time.sleep(15) + stay_active_duration_ms = 2000 + keep_active_timeout_ms = 60000 + await self._send_keep_active_command(stay_active_duration_ms, keep_active_timeout_ms, dynamic_endpoint_id) + self.step("17") + time.sleep(15) self.resume_th_icd_server(check_state=True) - logging.info("TH waiting for first checkin from TH_ICD...") - await self.default_controller.WaitForActive(self.icd_nodeid, stayActiveDurationMs=10000) - logging.info("TH waiting for second checkin from TH_ICD...") - await self.default_controller.WaitForActive(self.icd_nodeid, stayActiveDurationMs=10000) + self.step("18") + await self.default_controller.WaitForActive(self.icd_nodeid, timeoutSeconds=wait_for_icd_checkin_timeout_s, stayActiveDurationMs=5000) + promised_active_duration_ms = await self._wait_for_active_changed_event(timeout_s=wait_for_dut_event_subscription_s) asserts.assert_equal(self.q.qsize(), 0, "More than one event received from DUT") From 0944982ee0191d3e2aed8a3d3770c0b49cf3943e Mon Sep 17 00:00:00 2001 From: Erwin Pan Date: Wed, 28 Aug 2024 07:44:56 +0800 Subject: [PATCH 11/11] [Chef] BasicVideoPlayer: Fix MediaInput and MediaPlayback not reporting attribute changes due to using AttributeAccessInterface (#35122) * Fix media-input accessinterface 1. SelectInput command won't call reportData since MatterReportingAttributeChangeCallback isn't called 2. PW RPC Writing CurrentInput attribute of MediaInput cluster through won't reportData. The value is also mismatch with PW RPC Read. (But this is still under WIP and not working) * Not writing the MediaInput cluster through RPC * Remove unused function HandleSetCurrentInput * Move all attributes logic to Chef application * Fix typo * Fix CurrentState(Playback Cluster) not report data * Restyled by whitespace * Restyled by clang-format * Set CurrentState & PlaybackSpeed in MediaPlayback cluster * Set some attributes to persist (NVM) * Remove mPlaybackSpeed/mCurrentState/mCurrentInput * modify playSpeed from 1.0 to 1 * Restyled by clang-format * Fix type in error messages * Restyled by clang-format --------- Co-authored-by: Restyled.io --- .../media-input/MediaInputManager.cpp | 51 ++++++-- .../clusters/media-input/MediaInputManager.h | 4 +- .../media-playback/MediaPlaybackManager.cpp | 114 +++++++++++++----- .../media-playback/MediaPlaybackManager.h | 11 +- examples/chef/common/stubs.cpp | 21 ---- ...ootnode_basicvideoplayer_0ff86e943b.matter | 16 +-- .../rootnode_basicvideoplayer_0ff86e943b.zap | 16 +-- 7 files changed, 149 insertions(+), 84 deletions(-) diff --git a/examples/chef/common/clusters/media-input/MediaInputManager.cpp b/examples/chef/common/clusters/media-input/MediaInputManager.cpp index f7e853cc46a40b..53cc22cd65cce8 100644 --- a/examples/chef/common/clusters/media-input/MediaInputManager.cpp +++ b/examples/chef/common/clusters/media-input/MediaInputManager.cpp @@ -15,27 +15,26 @@ * limitations under the License. */ +#include #include +#include + #ifdef MATTER_DM_PLUGIN_MEDIA_INPUT_SERVER #include "MediaInputManager.h" using namespace std; using namespace chip; using namespace chip::app::Clusters::MediaInput; +using Protocols::InteractionModel::Status; -MediaInputManager::MediaInputManager() +MediaInputManager::MediaInputManager(chip::EndpointId endpoint) : mEndpoint(endpoint) { - struct InputData inputData1(1, chip::app::Clusters::MediaInput::InputTypeEnum::kHdmi, "HDMI 1", - "High-Definition Multimedia Interface"); + struct InputData inputData1(1, InputTypeEnum::kHdmi, "HDMI 1", "High-Definition Multimedia Interface"); mInputs.push_back(inputData1); - struct InputData inputData2(2, chip::app::Clusters::MediaInput::InputTypeEnum::kHdmi, "HDMI 2", - "High-Definition Multimedia Interface"); + struct InputData inputData2(2, InputTypeEnum::kHdmi, "HDMI 2", "High-Definition Multimedia Interface"); mInputs.push_back(inputData2); - struct InputData inputData3(3, chip::app::Clusters::MediaInput::InputTypeEnum::kHdmi, "HDMI 3", - "High-Definition Multimedia Interface"); + struct InputData inputData3(3, InputTypeEnum::kHdmi, "HDMI 3", "High-Definition Multimedia Interface"); mInputs.push_back(inputData3); - - mCurrentInput = 1; } CHIP_ERROR MediaInputManager::HandleGetInputList(chip::app::AttributeValueEncoder & aEncoder) @@ -51,16 +50,32 @@ CHIP_ERROR MediaInputManager::HandleGetInputList(chip::app::AttributeValueEncode uint8_t MediaInputManager::HandleGetCurrentInput() { - return mCurrentInput; + uint8_t currentInput = 1; + Status status = Attributes::CurrentInput::Get(mEndpoint, ¤tInput); + if (Status::Success != status) + { + ChipLogError(Zcl, "Unable to get CurrentInput attribute, err:0x%x", to_underlying(status)); + } + return currentInput; } bool MediaInputManager::HandleSelectInput(const uint8_t index) { + if (HandleGetCurrentInput() == index) + { + ChipLogProgress(Zcl, "CurrentInput is same as new value: %u", index); + return true; + } for (auto const & inputData : mInputs) { if (inputData.index == index) { - mCurrentInput = index; + // Sync the CurrentInput to attribute storage while reporting changes + Status status = Attributes::CurrentInput::Set(mEndpoint, index); + if (Status::Success != status) + { + ChipLogError(Zcl, "CurrentInput is not stored successfully, err:0x%x", to_underlying(status)); + } return true; } } @@ -70,11 +85,12 @@ bool MediaInputManager::HandleSelectInput(const uint8_t index) bool MediaInputManager::HandleShowInputStatus() { + uint8_t currentInput = HandleGetCurrentInput(); ChipLogProgress(Zcl, " MediaInputManager::HandleShowInputStatus()"); for (auto const & inputData : mInputs) { ChipLogProgress(Zcl, " [%d] type=%d selected=%d name=%s desc=%s", inputData.index, - static_cast(inputData.inputType), (mCurrentInput == inputData.index ? 1 : 0), + static_cast(inputData.inputType), (currentInput == inputData.index ? 1 : 0), inputData.name.c_str(), inputData.description.c_str()); } return true; @@ -99,4 +115,15 @@ bool MediaInputManager::HandleRenameInput(const uint8_t index, const chip::CharS return false; } + +static std::map> gMediaInputManagerInstance{}; + +void emberAfMediaInputClusterInitCallback(EndpointId endpoint) +{ + ChipLogProgress(Zcl, "TV Linux App: MediaInput::SetDefaultDelegate, endpoint=%x", endpoint); + + gMediaInputManagerInstance[endpoint] = std::make_unique(endpoint); + + SetDefaultDelegate(endpoint, gMediaInputManagerInstance[endpoint].get()); +} #endif // MATTER_DM_PLUGIN_MEDIA_INPUT_SERVER diff --git a/examples/chef/common/clusters/media-input/MediaInputManager.h b/examples/chef/common/clusters/media-input/MediaInputManager.h index e5b88c5a7a67d5..d922d5aa650b59 100644 --- a/examples/chef/common/clusters/media-input/MediaInputManager.h +++ b/examples/chef/common/clusters/media-input/MediaInputManager.h @@ -29,7 +29,7 @@ class MediaInputManager : public chip::app::Clusters::MediaInput::Delegate using InputInfoType = chip::app::Clusters::MediaInput::Structs::InputInfoStruct::Type; public: - MediaInputManager(); + MediaInputManager(chip::EndpointId endpoint); CHIP_ERROR HandleGetInputList(chip::app::AttributeValueEncoder & aEncoder) override; uint8_t HandleGetCurrentInput() override; @@ -63,7 +63,7 @@ class MediaInputManager : public chip::app::Clusters::MediaInput::Delegate }; protected: - uint8_t mCurrentInput; + chip::EndpointId mEndpoint; std::vector mInputs; private: diff --git a/examples/chef/common/clusters/media-playback/MediaPlaybackManager.cpp b/examples/chef/common/clusters/media-playback/MediaPlaybackManager.cpp index 14be124252a7c0..db3af119c5b286 100644 --- a/examples/chef/common/clusters/media-playback/MediaPlaybackManager.cpp +++ b/examples/chef/common/clusters/media-playback/MediaPlaybackManager.cpp @@ -15,26 +15,34 @@ * limitations under the License. */ -#include -#ifdef MATTER_DM_PLUGIN_MEDIA_PLAYBACK_SERVER -#include "MediaPlaybackManager.h" #include #include - +#include #include +#ifdef MATTER_DM_PLUGIN_MEDIA_PLAYBACK_SERVER +#include "MediaPlaybackManager.h" using namespace std; +using namespace chip; +using namespace chip::app; using namespace chip::app::DataModel; using namespace chip::app::Clusters::MediaPlayback; -using namespace chip::Uint8; using chip::CharSpan; using chip::app::AttributeValueEncoder; using chip::app::CommandResponseHelper; +using chip::Protocols::InteractionModel::Status; PlaybackStateEnum MediaPlaybackManager::HandleGetCurrentState() { - return mCurrentState; + PlaybackStateEnum currentState = PlaybackStateEnum::kPlaying; + + Status status = Attributes::CurrentState::Get(mEndpoint, ¤tState); + if (Status::Success != status) + { + ChipLogError(Zcl, "Unable to get CurrentStage attribute, err:0x%x", to_underlying(status)); + } + return currentState; } uint64_t MediaPlaybackManager::HandleGetStartTime() @@ -54,7 +62,14 @@ CHIP_ERROR MediaPlaybackManager::HandleGetSampledPosition(AttributeValueEncoder float MediaPlaybackManager::HandleGetPlaybackSpeed() { - return mPlaybackSpeed; + float playbackSpeed = 1.0; + + Status status = Attributes::PlaybackSpeed::Get(mEndpoint, &playbackSpeed); + if (Status::Success != status) + { + ChipLogError(Zcl, "Unable to get PlaybackSpeed attribute, err:0x%x", to_underlying(status)); + } + return playbackSpeed; } uint64_t MediaPlaybackManager::HandleGetSeekRangeStart() @@ -99,10 +114,34 @@ CHIP_ERROR MediaPlaybackManager::HandleGetAvailableTextTracks(AttributeValueEnco }); } +CHIP_ERROR MediaPlaybackManager::HandleSetCurrentState(PlaybackStateEnum currentState) +{ + Status status = Attributes::CurrentState::Set(mEndpoint, currentState); + + if (Status::Success != status) + { + ChipLogError(Zcl, "Unable to set CurrentState attribute, 0x%x", to_underlying(status)); + } + + return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status); +} + +CHIP_ERROR MediaPlaybackManager::HandleSetPlaybackSpeed(float playbackSpeed) +{ + Status status = Attributes::PlaybackSpeed::Set(mEndpoint, playbackSpeed); + + if (Status::Success != status) + { + ChipLogError(Zcl, "Unable to set PlaybackSpeed attribute, 0x%x", to_underlying(status)); + } + + return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status); +} + void MediaPlaybackManager::HandlePlay(CommandResponseHelper & helper) { - mCurrentState = PlaybackStateEnum::kPlaying; - mPlaybackSpeed = 1; + HandleSetCurrentState(PlaybackStateEnum::kPlaying); + HandleSetPlaybackSpeed(1); Commands::PlaybackResponse::Type response; response.data = chip::MakeOptional(CharSpan::fromCharString("data response")); @@ -112,8 +151,8 @@ void MediaPlaybackManager::HandlePlay(CommandResponseHelper & helper) { - mCurrentState = PlaybackStateEnum::kPaused; - mPlaybackSpeed = 0; + HandleSetCurrentState(PlaybackStateEnum::kPaused); + HandleSetPlaybackSpeed(0); Commands::PlaybackResponse::Type response; response.data = chip::MakeOptional(CharSpan::fromCharString("data response")); @@ -123,8 +162,8 @@ void MediaPlaybackManager::HandlePause(CommandResponseHelper & helper) { - mCurrentState = PlaybackStateEnum::kNotPlaying; - mPlaybackSpeed = 0; + HandleSetCurrentState(PlaybackStateEnum::kNotPlaying); + HandleSetPlaybackSpeed(0); mPlaybackPosition = { 0, chip::app::DataModel::Nullable(0) }; Commands::PlaybackResponse::Type response; @@ -136,7 +175,9 @@ void MediaPlaybackManager::HandleStop(CommandResponseHelper & helper, const chip::Optional & audioAdvanceUnmuted) { - if (mPlaybackSpeed == kPlaybackMaxForwardSpeed) + float playbackSpeed = HandleGetPlaybackSpeed(); + + if (playbackSpeed == kPlaybackMaxForwardSpeed) { // if already at max speed, return error Commands::PlaybackResponse::Type response; @@ -146,13 +187,14 @@ void MediaPlaybackManager::HandleFastForward(CommandResponseHelper kPlaybackMaxForwardSpeed) + HandleSetCurrentState(PlaybackStateEnum::kPlaying); + // Normalize to correct range + playbackSpeed = (playbackSpeed <= 0 ? 1 : playbackSpeed * 2); + if (playbackSpeed > kPlaybackMaxForwardSpeed) { - // don't exceed max speed - mPlaybackSpeed = kPlaybackMaxForwardSpeed; + playbackSpeed = kPlaybackMaxForwardSpeed; } + HandleSetPlaybackSpeed(playbackSpeed); Commands::PlaybackResponse::Type response; response.data = chip::MakeOptional(CharSpan::fromCharString("data response")); @@ -162,8 +204,8 @@ void MediaPlaybackManager::HandleFastForward(CommandResponseHelper & helper) { - mCurrentState = PlaybackStateEnum::kPlaying; - mPlaybackSpeed = 1; + HandleSetCurrentState(PlaybackStateEnum::kPlaying); + HandleSetPlaybackSpeed(1); mPlaybackPosition = { 0, chip::app::DataModel::Nullable(0) }; Commands::PlaybackResponse::Type response; @@ -175,7 +217,9 @@ void MediaPlaybackManager::HandlePrevious(CommandResponseHelper & helper, const chip::Optional & audioAdvanceUnmuted) { - if (mPlaybackSpeed == kPlaybackMaxRewindSpeed) + float playbackSpeed = HandleGetPlaybackSpeed(); + + if (playbackSpeed == kPlaybackMaxRewindSpeed) { // if already at max speed in reverse, return error Commands::PlaybackResponse::Type response; @@ -185,13 +229,14 @@ void MediaPlaybackManager::HandleRewind(CommandResponseHelper= 0 ? -1 : mPlaybackSpeed * 2); - if (mPlaybackSpeed < kPlaybackMaxRewindSpeed) + HandleSetCurrentState(PlaybackStateEnum::kPlaying); + // Normalize to correct range + playbackSpeed = (playbackSpeed >= 0 ? -1 : playbackSpeed * 2); + if (playbackSpeed < kPlaybackMaxRewindSpeed) { - // don't exceed max rewind speed - mPlaybackSpeed = kPlaybackMaxRewindSpeed; + playbackSpeed = kPlaybackMaxRewindSpeed; } + HandleSetPlaybackSpeed(playbackSpeed); Commands::PlaybackResponse::Type response; response.data = chip::MakeOptional(CharSpan::fromCharString("data response")); @@ -249,8 +294,8 @@ void MediaPlaybackManager::HandleSeek(CommandResponseHelper & helper) { - mCurrentState = PlaybackStateEnum::kPlaying; - mPlaybackSpeed = 1; + HandleSetCurrentState(PlaybackStateEnum::kPlaying); + HandleSetPlaybackSpeed(1); mPlaybackPosition = { 0, chip::app::DataModel::Nullable(0) }; Commands::PlaybackResponse::Type response; @@ -338,4 +383,15 @@ uint16_t MediaPlaybackManager::GetClusterRevision(chip::EndpointId endpoint) return clusterRevision; } +static std::map> gMediaPlaybackManagerInstance{}; + +void emberAfMediaPlaybackClusterInitCallback(EndpointId endpoint) +{ + ChipLogProgress(Zcl, "TV Linux App: MediaPlayback::SetDefaultDelegate, endpoint=%x", endpoint); + + gMediaPlaybackManagerInstance[endpoint] = std::make_unique(endpoint); + + SetDefaultDelegate(endpoint, gMediaPlaybackManagerInstance[endpoint].get()); +} + #endif /// MATTER_DM_PLUGIN_MEDIA_PLAYBACK_SERVER diff --git a/examples/chef/common/clusters/media-playback/MediaPlaybackManager.h b/examples/chef/common/clusters/media-playback/MediaPlaybackManager.h index 79bca10a3c8fb6..3f436b70a4b9ae 100644 --- a/examples/chef/common/clusters/media-playback/MediaPlaybackManager.h +++ b/examples/chef/common/clusters/media-playback/MediaPlaybackManager.h @@ -30,6 +30,8 @@ class MediaPlaybackManager : public chip::app::Clusters::MediaPlayback::Delegate using Feature = chip::app::Clusters::MediaPlayback::Feature; public: + MediaPlaybackManager(chip::EndpointId endpoint) : mEndpoint(endpoint){}; + chip::app::Clusters::MediaPlayback::PlaybackStateEnum HandleGetCurrentState() override; uint64_t HandleGetStartTime() override; uint64_t HandleGetDuration() override; @@ -42,6 +44,9 @@ class MediaPlaybackManager : public chip::app::Clusters::MediaPlayback::Delegate CHIP_ERROR HandleGetActiveTextTrack(chip::app::AttributeValueEncoder & aEncoder) override; CHIP_ERROR HandleGetAvailableTextTracks(chip::app::AttributeValueEncoder & aEncoder) override; + CHIP_ERROR HandleSetCurrentState(chip::app::Clusters::MediaPlayback::PlaybackStateEnum currentState); + CHIP_ERROR HandleSetPlaybackSpeed(float playbackSpeed); + void HandlePlay(chip::app::CommandResponseHelper & helper) override; void HandlePause(chip::app::CommandResponseHelper & helper) override; void HandleStop(chip::app::CommandResponseHelper & helper) override; @@ -66,10 +71,9 @@ class MediaPlaybackManager : public chip::app::Clusters::MediaPlayback::Delegate uint16_t GetClusterRevision(chip::EndpointId endpoint) override; protected: + chip::EndpointId mEndpoint; // NOTE: it does not make sense to have default state of playing with a speed of 0, but // the CI test cases expect these values, and need to be fixed. - chip::app::Clusters::MediaPlayback::PlaybackStateEnum mCurrentState = - chip::app::Clusters::MediaPlayback::PlaybackStateEnum::kPlaying; PlaybackPositionType mPlaybackPosition = { 0, chip::app::DataModel::Nullable(0) }; TrackType mActiveAudioTrack = { chip::CharSpan("activeAudioTrackId_0", 20), chip::app::DataModel::Nullable( @@ -101,8 +105,7 @@ class MediaPlaybackManager : public chip::app::Clusters::MediaPlayback::Delegate chip::Optional>( { chip::app::DataModel::MakeNullable(chip::CharSpan("displayName2", 12)) }) }) } }; - float mPlaybackSpeed = 1.0; - uint64_t mStartTime = 0; + uint64_t mStartTime = 0; // Magic number for testing. uint64_t mDuration = 80000; bool mAudioAdvanceMuted = false; diff --git a/examples/chef/common/stubs.cpp b/examples/chef/common/stubs.cpp index 8771bf78dccaf5..f7b4b491451b0e 100644 --- a/examples/chef/common/stubs.cpp +++ b/examples/chef/common/stubs.cpp @@ -238,27 +238,6 @@ void emberAfLowPowerClusterInitCallback(EndpointId endpoint) } #endif -#ifdef MATTER_DM_PLUGIN_MEDIA_INPUT_SERVER -#include "media-input/MediaInputManager.h" -static MediaInputManager mediaInputManager; -void emberAfMediaInputClusterInitCallback(EndpointId endpoint) -{ - ChipLogProgress(Zcl, "TV Linux App: MediaInput::SetDefaultDelegate"); - MediaInput::SetDefaultDelegate(endpoint, &mediaInputManager); -} -#endif - -#ifdef MATTER_DM_PLUGIN_MEDIA_PLAYBACK_SERVER -#include "media-playback/MediaPlaybackManager.h" -static MediaPlaybackManager mediaPlaybackManager; - -void emberAfMediaPlaybackClusterInitCallback(EndpointId endpoint) -{ - ChipLogProgress(Zcl, "TV Linux App: MediaPlayback::SetDefaultDelegate"); - MediaPlayback::SetDefaultDelegate(endpoint, &mediaPlaybackManager); -} -#endif - #ifdef MATTER_DM_PLUGIN_TARGET_NAVIGATOR_SERVER #include "target-navigator/TargetNavigatorManager.h" static TargetNavigatorManager targetNavigatorManager; diff --git a/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.matter b/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.matter index 84f789e40a1110..f76efbed02e901 100644 --- a/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.matter +++ b/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.matter @@ -2522,7 +2522,7 @@ endpoint 1 { server cluster OnOff { - ram attribute onOff default = 0; + persist attribute onOff default = 0; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; @@ -2578,7 +2578,7 @@ endpoint 1 { server cluster TargetNavigator { emits event TargetUpdated; callback attribute targetList; - ram attribute currentTarget default = 0; + persist attribute currentTarget default = 0; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; @@ -2592,11 +2592,11 @@ endpoint 1 { server cluster MediaPlayback { emits event StateChanged; - ram attribute currentState default = 0x00; + persist attribute currentState default = 0x00; ram attribute startTime default = 0x00; ram attribute duration default = 0; callback attribute sampledPosition; - ram attribute playbackSpeed default = 0; + ram attribute playbackSpeed default = 1; ram attribute seekRangeEnd; ram attribute seekRangeStart; callback attribute generatedCommandList; @@ -2622,7 +2622,7 @@ endpoint 1 { server cluster MediaInput { callback attribute inputList; - ram attribute currentInput default = 0x00; + persist attribute currentInput default = 0x00; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; @@ -2661,7 +2661,7 @@ endpoint 1 { server cluster AudioOutput { callback attribute outputList; - ram attribute currentOutput default = 0x00; + persist attribute currentOutput default = 0x00; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; @@ -2692,7 +2692,7 @@ endpoint 2 { } server cluster OnOff { - ram attribute onOff default = 0; + persist attribute onOff default = 0; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; @@ -2706,7 +2706,7 @@ endpoint 2 { } server cluster LevelControl { - ram attribute currentLevel default = 0x00; + persist attribute currentLevel default = 0x00; ram attribute minLevel default = 0x00; ram attribute maxLevel default = 0xFE; ram attribute options default = 0x00; diff --git a/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.zap b/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.zap index 6a4517c2962607..8a6e75210fa6b5 100644 --- a/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.zap +++ b/examples/chef/devices/rootnode_basicvideoplayer_0ff86e943b.zap @@ -2444,7 +2444,7 @@ "side": "server", "type": "boolean", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0", @@ -3080,7 +3080,7 @@ "side": "server", "type": "int8u", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0", @@ -3309,7 +3309,7 @@ "side": "server", "type": "PlaybackStateEnum", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0x00", @@ -3376,7 +3376,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3586,7 +3586,7 @@ "side": "server", "type": "int8u", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0x00", @@ -3982,7 +3982,7 @@ "side": "server", "type": "int8u", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0x00", @@ -4315,7 +4315,7 @@ "side": "server", "type": "boolean", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0", @@ -4503,7 +4503,7 @@ "side": "server", "type": "int8u", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0x00",