From 3132114f7cf1922139ce413fb40f091aae37e106 Mon Sep 17 00:00:00 2001 From: Song GUO Date: Fri, 12 Nov 2021 21:57:10 +0800 Subject: [PATCH] [IM] AttributePathExpandIterator for iterating over list of ClusterInfo-s with wildcard (#11303) * [IM] Implementing PathIterator for iterating attribute paths with wildcard fields * Address comments * Update Test case, remove TODO for group path * Fix * Update * Fix --- src/app/AttributePathExpandIterator.cpp | 182 ++++++++++ src/app/AttributePathExpandIterator.h | 122 +++++++ src/app/AttributePathParams.h | 4 +- src/app/BUILD.gn | 3 + src/app/ClusterInfo.h | 3 +- src/app/ConcreteAttributePath.h | 1 + src/app/tests/BUILD.gn | 2 + .../tests/TestAttributePathExpandIterator.cpp | 323 ++++++++++++++++++ src/app/util/attribute-storage.cpp | 47 +++ src/app/util/attribute-storage.h | 24 +- src/app/util/mock/BUILD.gn | 27 ++ src/app/util/mock/Constants.h | 45 +++ src/app/util/mock/attribute-storage.cpp | 190 +++++++++++ src/lib/core/DataModelTypes.h | 1 + 14 files changed, 968 insertions(+), 6 deletions(-) create mode 100644 src/app/AttributePathExpandIterator.cpp create mode 100644 src/app/AttributePathExpandIterator.h create mode 100644 src/app/tests/TestAttributePathExpandIterator.cpp create mode 100644 src/app/util/mock/BUILD.gn create mode 100644 src/app/util/mock/Constants.h create mode 100644 src/app/util/mock/attribute-storage.cpp diff --git a/src/app/AttributePathExpandIterator.cpp b/src/app/AttributePathExpandIterator.cpp new file mode 100644 index 00000000000000..f77160df52cdd8 --- /dev/null +++ b/src/app/AttributePathExpandIterator.cpp @@ -0,0 +1,182 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; + +// TODO: Need to make it so that declarations of things that don't depend on generated files are not intermixed in af.h with +// dependencies on generated files, so we don't have to re-declare things here. +// Note: Some of the generated files that depended by af.h are gen_config.h and gen_tokens.h +typedef uint8_t EmberAfClusterMask; + +extern uint16_t emberAfEndpointCount(void); +extern uint16_t emberAfIndexFromEndpoint(EndpointId endpoint); +extern uint8_t emberAfClusterCount(EndpointId endpoint, bool server); +extern uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpoint, chip::ClusterId cluster); +extern uint16_t emberAfGetServerAttributeIndexByAttributeId(chip::EndpointId endpoint, chip::ClusterId cluster, + chip::AttributeId attributeId); +extern chip::EndpointId emberAfEndpointFromIndex(uint16_t index); +extern Optional emberAfGetNthClusterId(chip::EndpointId endpoint, uint8_t n, bool server); +extern Optional emberAfGetServerAttributeIdByIndex(chip::EndpointId endpoint, chip::ClusterId cluster, + uint16_t attributeIndex); +extern uint8_t emberAfClusterIndex(EndpointId endpoint, ClusterId clusterId, EmberAfClusterMask mask); + +namespace chip { +namespace app { + +AttributePathExpandIterator::AttributePathExpandIterator(ClusterInfo * aClusterInfo) +{ + mpClusterInfo = aClusterInfo; + + // Reset iterator state + mEndpointIndex = UINT16_MAX; + mClusterIndex = UINT8_MAX; + mAttributeIndex = UINT16_MAX; + + // Make the iterator ready to emit the first valid path in the list. + Next(); +} + +void AttributePathExpandIterator::PrepareEndpointIndexRange(const ClusterInfo & aClusterInfo) +{ + if (aClusterInfo.HasWildcardEndpointId()) + { + mEndpointIndex = 0; + mEndEndpointIndex = emberAfEndpointCount(); + } + else + { + mEndpointIndex = emberAfIndexFromEndpoint(aClusterInfo.mEndpointId); + // If the given cluster id does not exist on the given endpoint, it will return uint16(0xFFFF), then endEndpointIndex + // will be 0, means we should iterate a null endpoint set (skip it). + mEndEndpointIndex = static_cast(mEndpointIndex + 1); + } +} + +void AttributePathExpandIterator::PrepareClusterIndexRange(const ClusterInfo & aClusterInfo, EndpointId aEndpointId) +{ + if (aClusterInfo.HasWildcardClusterId()) + { + mClusterIndex = 0; + mEndClusterIndex = emberAfClusterCount(aEndpointId, true /* server */); + } + else + { + mClusterIndex = emberAfClusterIndex(aEndpointId, aClusterInfo.mClusterId, CLUSTER_MASK_SERVER); + // If the given cluster id does not exist on the given endpoint, it will return uint8(0xFF), then endClusterIndex + // will be 0, means we should iterate a null cluster set (skip it). + mEndClusterIndex = static_cast(mClusterIndex + 1); + } +} + +void AttributePathExpandIterator::PrepareAttributeIndexRange(const ClusterInfo & aClusterInfo, EndpointId aEndpointId, + ClusterId aClusterId) +{ + if (aClusterInfo.HasWildcardAttributeId()) + { + mAttributeIndex = 0; + mEndAttributeIndex = emberAfGetServerAttributeCount(aEndpointId, aClusterId); + } + else + { + mAttributeIndex = emberAfGetServerAttributeIndexByAttributeId(aEndpointId, aClusterId, aClusterInfo.mAttributeId); + // If the given attribute id does not exist on the given endpoint, it will return uint16(0xFFFF), then endAttributeIndex + // will be 0, means we should iterate a null attribute set (skip it). + mEndAttributeIndex = static_cast(mAttributeIndex + 1); + } +} + +bool AttributePathExpandIterator::Next() +{ + for (; mpClusterInfo != nullptr; (mpClusterInfo = mpClusterInfo->mpNext, mEndpointIndex = UINT16_MAX)) + { + if (mEndpointIndex == UINT16_MAX) + { + // Special case: If this is a concrete path, we just return its value as-is. + if (!mpClusterInfo->HasWildcard()) + { + mOutputPath.mEndpointId = mpClusterInfo->mEndpointId; + mOutputPath.mClusterId = mpClusterInfo->mClusterId; + mOutputPath.mAttributeId = mpClusterInfo->mAttributeId; + + // Prepare for next iteration + mEndpointIndex = mEndEndpointIndex = 0; + return true; + } + + PrepareEndpointIndexRange(*mpClusterInfo); + mClusterIndex = UINT8_MAX; + } + + for (; mEndpointIndex < mEndEndpointIndex; (mEndpointIndex++, mClusterIndex = UINT8_MAX, mAttributeIndex = UINT16_MAX)) + { + EndpointId endpointId = emberAfEndpointFromIndex(mEndpointIndex); + + if (mClusterIndex == UINT8_MAX) + { + PrepareClusterIndexRange(*mpClusterInfo, endpointId); + mAttributeIndex = UINT16_MAX; + } + + for (; mClusterIndex < mEndClusterIndex; (mClusterIndex++, mAttributeIndex = UINT16_MAX)) + { + // emberAfGetNthClusterId must return a valid cluster id here since we have verified the mClusterIndex does + // not exceed the mEndClusterIndex. + ClusterId clusterId = emberAfGetNthClusterId(endpointId, mClusterIndex, true /* server */).Value(); + if (mAttributeIndex == UINT16_MAX) + { + PrepareAttributeIndexRange(*mpClusterInfo, endpointId, clusterId); + } + + if (mAttributeIndex < mEndAttributeIndex) + { + // GetServerAttributeIdByIdex must return a valid attribute here since we have verified the mAttributeIndex does + // not exceed the mEndAttributeIndex. + mOutputPath.mAttributeId = emberAfGetServerAttributeIdByIndex(endpointId, clusterId, mAttributeIndex).Value(); + mOutputPath.mClusterId = clusterId; + mOutputPath.mEndpointId = endpointId; + mAttributeIndex++; + // We found a valid attribute path, now return and increase the attribute index for next iteration. + // Return true will skip the increment of mClusterIndex, mEndpointIndex and mpClusterInfo. + return true; + } + // We have exhausted all attributes of this cluster, continue iterating over attributes of next cluster. + } + // We have exhausted all clusters of this endpoint, continue iterating over clusters of next endpoint. + } + // We have exhausted all endpoints in this cluster info, continue iterating over next cluster info item. + } + + // Reset to default, invalid value. + mOutputPath = ConcreteAttributePath(); + return false; +} +} // namespace app +} // namespace chip diff --git a/src/app/AttributePathExpandIterator.h b/src/app/AttributePathExpandIterator.h new file mode 100644 index 00000000000000..23e5e63237d268 --- /dev/null +++ b/src/app/AttributePathExpandIterator.h @@ -0,0 +1,122 @@ +/* + * + * Copyright (c) 2021 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. + */ + +/** + * @file + * Defines an iterator for iterating all possible paths from a list of ClusterInfo-s according to spec section 8.9.2.2 (Valid + * Attribute Paths) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +/** + * AttributePathExpandIterator is used to iterate over a linked list of ClusterInfo-s. + * The AttributePathExpandIterator is copiable, however, the given cluster info must be valid when calling Next(). + * + * AttributePathExpandIterator will expand attribute paths with wildcards, and only emit existing paths for ClusterInfo with + * wildcards. For ClusterInfo with a concrete path (i.e. does not contain wildcards), AttributePathExpandIterator will emit them + * as-is. + * + * The typical use of AttributePathExpandIterator may look like: + * ConcreteAttributePath path; + * for (AttributePathExpandIterator iterator(clusterInfo); iterator.Get(path); iterator.Next()) {...} + * + * The iterator does not copy the given ClusterInfo, The given ClusterInfo must be valid when using the iterator. + * If the set of endpoints, clusters, or attributes that are supported changes, AttributePathExpandIterator must be reinitialized. + * + * A initialized iterator will return the first valid path, no need to call Next() before calling Get() for the first time. + * + * Note: The Next() and Get() are two separate operations by design since a possible call of this iterator might be: + * - Get() + * - Chunk full, return + * - In a new chunk, Get() + * + * TODO: The ClusterInfo may support a group id, the iterator should be able to call group data provider to expand the group id. + */ +class AttributePathExpandIterator +{ +public: + AttributePathExpandIterator(ClusterInfo * aClusterInfo); + + /** + * Proceed the iterator to the next attribute path in the given cluster info. + * + * Returns false if AttributePathExpandIterator has exhausted all paths in the given ClusterInfo list. + */ + bool Next(); + + /** + * Fills the aPath with the path the iterator currently points to. + * Returns false if the iterator is not pointing to a valid path (i.e. it has exhausted the cluster info). + */ + bool Get(ConcreteAttributePath & aPath) + { + aPath = mOutputPath; + return Valid(); + } + + /** + * Returns if the iterator is valid (not exhausted). An iterator is exhausted if and only if: + * - Next() is called after iterating last path. + * - Iterator is initialized with a null ClusterInfo. + */ + inline bool Valid() const { return mpClusterInfo != nullptr; } + +private: + ClusterInfo * mpClusterInfo; + + uint16_t mEndpointIndex, mEndEndpointIndex; + // Note: should use decltype(EmberAfEndpointType::clusterCount) here, but af-types is including app specific generated files. + uint8_t mClusterIndex, mEndClusterIndex; + uint16_t mAttributeIndex, mEndAttributeIndex; + + ConcreteAttributePath mOutputPath; + + /** + * Prepare*IndexRange will update mBegin*Index and mEnd*Index variables. + * If ClusterInfo contains a wildcard field, it will set mBegin*Index to 0 and mEnd*Index to count. + * Or it will set mBegin*Index to the index of the Endpoint/Cluster/Attribute, and mEnd*Index to mBegin*Index + 1. + * + * If the Endpoint/Cluster/Attribute does not exist, mBegin*Index will be UINT*_MAX, and mEnd*Inde will be 0. + * + * The index can be used with emberAfEndpointFromIndex, emberAfGetNthClusterId and emberAfGetServerAttributeIdByIndex. + */ + void PrepareEndpointIndexRange(const ClusterInfo & aClusterInfo); + void PrepareClusterIndexRange(const ClusterInfo & aClusterInfo, EndpointId aEndpointId); + void PrepareAttributeIndexRange(const ClusterInfo & aClusterInfo, EndpointId aEndpointId, ClusterId aClusterId); +}; +} // namespace app +} // namespace chip diff --git a/src/app/AttributePathParams.h b/src/app/AttributePathParams.h index 3a696a26d8d9b2..855587f3661acd 100644 --- a/src/app/AttributePathParams.h +++ b/src/app/AttributePathParams.h @@ -58,12 +58,12 @@ struct AttributePathParams */ bool IsValidAttributePath() const { return HasWildcardListIndex() || !HasWildcardAttributeId(); } - inline bool HasWildcardEndpointId() const { return mEndpointId == ClusterInfo::kInvalidEndpointId; } + inline bool HasWildcardEndpointId() const { return mEndpointId == kInvalidEndpointId; } inline bool HasWildcardClusterId() const { return mClusterId == ClusterInfo::kInvalidClusterId; } inline bool HasWildcardAttributeId() const { return mAttributeId == ClusterInfo::kInvalidAttributeId; } inline bool HasWildcardListIndex() const { return mListIndex == ClusterInfo::kInvalidListIndex; } - EndpointId mEndpointId = ClusterInfo::kInvalidEndpointId; + EndpointId mEndpointId = kInvalidEndpointId; ClusterId mClusterId = ClusterInfo::kInvalidClusterId; AttributeId mAttributeId = ClusterInfo::kInvalidAttributeId; ListIndex mListIndex = ClusterInfo::kInvalidListIndex; diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 05c460035c7125..7db33c8e0fb495 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -35,7 +35,10 @@ static_library("app") { output_name = "libCHIPDataModel" sources = [ + "AttributePathExpandIterator.cpp", + "AttributePathExpandIterator.h", "AttributePathParams.cpp", + "AttributePathParams.h", "Command.cpp", "Command.h", "CommandHandler.cpp", diff --git a/src/app/ClusterInfo.h b/src/app/ClusterInfo.h index b7dad7d88b8a5e..c07d3491623b9f 100644 --- a/src/app/ClusterInfo.h +++ b/src/app/ClusterInfo.h @@ -37,8 +37,7 @@ struct ClusterInfo private: // Allow AttributePathParams access these constants. friend struct AttributePathParams; - // Endpoint Id is a uint16 number, and should between 0 and 0xFFFE - static constexpr EndpointId kInvalidEndpointId = 0xFFFF; + // The ClusterId, AttributeId and EventId are MEIs, // 0xFFFF is not a valid manufacturer code, thus 0xFFFF'FFFF is not a valid MEI static constexpr ClusterId kInvalidClusterId = 0xFFFF'FFFF; diff --git a/src/app/ConcreteAttributePath.h b/src/app/ConcreteAttributePath.h index b3b44a8a2f359c..8b05580fa12c8a 100644 --- a/src/app/ConcreteAttributePath.h +++ b/src/app/ConcreteAttributePath.h @@ -28,6 +28,7 @@ namespace app { */ struct ConcreteAttributePath { + ConcreteAttributePath() {} ConcreteAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId) : mEndpointId(aEndpointId), mClusterId(aClusterId), mAttributeId(aAttributeId) {} diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 1cb62b6cc93d83..d58ba38ec83e56 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -41,6 +41,7 @@ chip_test_suite("tests") { output_name = "libAppTests" test_sources = [ + "TestAttributePathExpandIterator.cpp", "TestAttributeValueEncoder.cpp", "TestBuilderParser.cpp", "TestCHIPDeviceCallbacksMgr.cpp", @@ -65,6 +66,7 @@ chip_test_suite("tests") { "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/tests:helpers", "${chip_root}/src/app/util:device_callbacks_manager", + "${chip_root}/src/app/util/mock:mock_ember", "${chip_root}/src/lib/core", "${nlunit_test_root}:nlunit-test", ] diff --git a/src/app/tests/TestAttributePathExpandIterator.cpp b/src/app/tests/TestAttributePathExpandIterator.cpp new file mode 100644 index 00000000000000..b13457c6981e7d --- /dev/null +++ b/src/app/tests/TestAttributePathExpandIterator.cpp @@ -0,0 +1,323 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace chip; +using namespace chip::Test; +using namespace chip::app; + +namespace { + +using P = app::ConcreteAttributePath; + +void TestAllWildcard(nlTestSuite * apSuite, void * apContext) +{ + app::ClusterInfo clusInfo; + + app::ConcreteAttributePath path; + P paths[] = { + { kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint1, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint1, MockClusterId(2), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint1, MockClusterId(2), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(1), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(2), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(2), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(2), MockAttributeId(2) }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(2) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + { kMockEndpoint3, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(1), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint3, MockClusterId(1), MockAttributeId(1) }, + { kMockEndpoint3, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(2), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(1) }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(2) }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(3) }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(4) }, + { kMockEndpoint3, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(3), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint3, MockClusterId(4), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(4), Clusters::Globals::Attributes::FeatureMap::Id }, + }; + + size_t index = 0; + + for (app::AttributePathExpandIterator iter(&clusInfo); iter.Get(path); iter.Next()) + { + ChipLogDetail(AppServer, "Visited Attribute: 0x%04" PRIX16 " / " ChipLogFormatMEI " / " ChipLogFormatMEI, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + NL_TEST_ASSERT(apSuite, index < ArraySize(paths) && paths[index] == path); + index++; + } + NL_TEST_ASSERT(apSuite, index == ArraySize(paths)); +} + +void TestWildcardEndpoint(nlTestSuite * apSuite, void * apContext) +{ + app::ClusterInfo clusInfo; + clusInfo.mClusterId = Test::MockClusterId(3); + clusInfo.mAttributeId = Test::MockAttributeId(3); + + app::ConcreteAttributePath path; + P paths[] = { + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + }; + + size_t index = 0; + + for (app::AttributePathExpandIterator iter(&clusInfo); iter.Get(path); iter.Next()) + { + ChipLogDetail(AppServer, "Visited Attribute: 0x%04" PRIX16 " / " ChipLogFormatMEI " / " ChipLogFormatMEI, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + NL_TEST_ASSERT(apSuite, index < ArraySize(paths) && paths[index] == path); + index++; + } + NL_TEST_ASSERT(apSuite, index == ArraySize(paths)); +} + +void TestWildcardCluster(nlTestSuite * apSuite, void * apContext) +{ + app::ClusterInfo clusInfo; + clusInfo.mEndpointId = Test::kMockEndpoint3; + clusInfo.mAttributeId = app::Clusters::Globals::Attributes::ClusterRevision::Id; + + app::ConcreteAttributePath path; + P paths[] = { + { kMockEndpoint3, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(4), Clusters::Globals::Attributes::ClusterRevision::Id }, + }; + + size_t index = 0; + + for (app::AttributePathExpandIterator iter(&clusInfo); iter.Get(path); iter.Next()) + { + ChipLogDetail(AppServer, "Visited Attribute: 0x%04" PRIX16 " / " ChipLogFormatMEI " / " ChipLogFormatMEI, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + NL_TEST_ASSERT(apSuite, index < ArraySize(paths) && paths[index] == path); + index++; + } + NL_TEST_ASSERT(apSuite, index == ArraySize(paths)); +} + +void TestWildcardAttribute(nlTestSuite * apSuite, void * apContext) +{ + app::ClusterInfo clusInfo; + clusInfo.mEndpointId = Test::kMockEndpoint2; + clusInfo.mClusterId = Test::MockClusterId(3); + + app::ConcreteAttributePath path; + P paths[] = { + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(2) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + }; + + size_t index = 0; + + for (app::AttributePathExpandIterator iter(&clusInfo); iter.Get(path); iter.Next()) + { + ChipLogDetail(AppServer, "Visited Attribute: 0x%04" PRIX16 " / " ChipLogFormatMEI " / " ChipLogFormatMEI, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + NL_TEST_ASSERT(apSuite, index < ArraySize(paths) && paths[index] == path); + index++; + } + NL_TEST_ASSERT(apSuite, index == ArraySize(paths)); +} + +void TestNoWildcard(nlTestSuite * apSuite, void * apContext) +{ + app::ClusterInfo clusInfo; + clusInfo.mEndpointId = Test::kMockEndpoint2; + clusInfo.mClusterId = Test::MockClusterId(3); + clusInfo.mAttributeId = Test::MockAttributeId(3); + + app::ConcreteAttributePath path; + P paths[] = { + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + }; + + size_t index = 0; + + for (app::AttributePathExpandIterator iter(&clusInfo); iter.Get(path); iter.Next()) + { + ChipLogDetail(AppServer, "Visited Attribute: 0x%04" PRIX16 " / " ChipLogFormatMEI " / " ChipLogFormatMEI, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + NL_TEST_ASSERT(apSuite, index < ArraySize(paths) && paths[index] == path); + index++; + } + NL_TEST_ASSERT(apSuite, index == ArraySize(paths)); +} + +void TestMultipleClusInfo(nlTestSuite * apSuite, void * apContext) +{ + + app::ClusterInfo clusInfo1; + + app::ClusterInfo clusInfo2; + clusInfo2.mClusterId = Test::MockClusterId(3); + clusInfo2.mAttributeId = Test::MockAttributeId(3); + + app::ClusterInfo clusInfo3; + clusInfo3.mEndpointId = Test::kMockEndpoint3; + clusInfo3.mAttributeId = app::Clusters::Globals::Attributes::ClusterRevision::Id; + + app::ClusterInfo clusInfo4; + clusInfo4.mEndpointId = Test::kMockEndpoint2; + clusInfo4.mClusterId = Test::MockClusterId(3); + + app::ClusterInfo clusInfo5; + clusInfo5.mEndpointId = Test::kMockEndpoint2; + clusInfo5.mClusterId = Test::MockClusterId(3); + clusInfo5.mAttributeId = Test::MockAttributeId(3); + + clusInfo1.mpNext = &clusInfo2; + clusInfo2.mpNext = &clusInfo3; + clusInfo3.mpNext = &clusInfo4; + clusInfo4.mpNext = &clusInfo5; + + app::ConcreteAttributePath path; + P paths[] = { + { kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint1, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint1, MockClusterId(2), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint1, MockClusterId(2), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(1), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(2), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(2), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(2), MockAttributeId(2) }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(2) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + { kMockEndpoint3, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(1), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint3, MockClusterId(1), MockAttributeId(1) }, + { kMockEndpoint3, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(2), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(1) }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(2) }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(3) }, + { kMockEndpoint3, MockClusterId(2), MockAttributeId(4) }, + { kMockEndpoint3, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(3), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint3, MockClusterId(4), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(4), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + { kMockEndpoint3, MockClusterId(1), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(2), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint3, MockClusterId(4), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::ClusterRevision::Id }, + { kMockEndpoint2, MockClusterId(3), Clusters::Globals::Attributes::FeatureMap::Id }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(1) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(2) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + { kMockEndpoint2, MockClusterId(3), MockAttributeId(3) }, + }; + + size_t index = 0; + + for (app::AttributePathExpandIterator iter(&clusInfo1); iter.Get(path); iter.Next()) + { + ChipLogDetail(AppServer, "Visited Attribute: 0x%04" PRIX16 " / " ChipLogFormatMEI " / " ChipLogFormatMEI, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + NL_TEST_ASSERT(apSuite, index < ArraySize(paths) && paths[index] == path); + index++; + } + NL_TEST_ASSERT(apSuite, index == ArraySize(paths)); +} + +static int TestSetup(void * inContext) +{ + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +static int TestTeardown(void * inContext) +{ + return SUCCESS; +} + +/** + * Test Suite. It lists all the test functions. + */ + +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("TestAllWildcard", TestAllWildcard), + NL_TEST_DEF("TestWildcardEndpoint", TestWildcardEndpoint), + NL_TEST_DEF("TestWildcardCluster", TestWildcardCluster), + NL_TEST_DEF("TestWildcardAttribute", TestWildcardAttribute), + NL_TEST_DEF("TestNoWildcard", TestNoWildcard), + NL_TEST_DEF("TestMultipleClusInfo", TestMultipleClusInfo), + NL_TEST_SENTINEL() +}; +// clang-format on + +// clang-format off +nlTestSuite sSuite = +{ + "TestAttributePathExpandIterator", + &sTests[0], + TestSetup, + TestTeardown, +}; +// clang-format on + +} // namespace + +int TestAttributePathExpandIterator() +{ + nlTestRunner(&sSuite, nullptr); + return (nlTestRunnerStats(&sSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestAttributePathExpandIterator) diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index fedddb9ea5a572..6d435f5ead7b89 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -1150,6 +1151,19 @@ EmberAfCluster * emberAfGetNthCluster(EndpointId endpoint, uint8_t n, bool serve return NULL; } +// Returns the cluster id of Nth server or client cluster, +// depending on server toggle. +// Returns Optional::Missing() if cluster does not exist. +Optional emberAfGetNthClusterId(EndpointId endpoint, uint8_t n, bool server) +{ + EmberAfCluster * cluster = emberAfGetNthCluster(endpoint, n, server); + if (cluster == nullptr) + { + return Optional::Missing(); + } + return Optional(cluster->clusterId); +} + // Returns number of clusters put into the passed cluster list // for the given endpoint and client/server polarity uint8_t emberAfGetClustersFromEndpoint(EndpointId endpoint, ClusterId * clusterList, uint8_t listLen, bool server) @@ -1364,3 +1378,36 @@ app::AttributeAccessInterface * findAttributeAccessOverride(EndpointId endpointI return nullptr; } + +uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpoint, chip::ClusterId cluster) +{ + EmberAfCluster * clusterObj = emberAfFindCluster(endpoint, cluster, CLUSTER_MASK_SERVER); + VerifyOrReturnError(clusterObj != nullptr, 0); + return clusterObj->attributeCount; +} + +uint16_t emberAfGetServerAttributeIndexByAttributeId(chip::EndpointId endpoint, chip::ClusterId cluster, + chip::AttributeId attributeId) +{ + EmberAfCluster * clusterObj = emberAfFindCluster(endpoint, cluster, CLUSTER_MASK_SERVER); + VerifyOrReturnError(clusterObj != nullptr, UINT16_MAX); + + for (uint16_t i = 0; i < clusterObj->attributeCount; i++) + { + if (clusterObj->attributes[i].attributeId == attributeId) + { + return i; + } + } + return UINT16_MAX; +} + +Optional emberAfGetServerAttributeIdByIndex(EndpointId endpoint, ClusterId cluster, uint16_t attributeIndex) +{ + EmberAfCluster * clusterObj = emberAfFindCluster(endpoint, cluster, CLUSTER_MASK_SERVER); + if (clusterObj == nullptr || clusterObj->attributeCount <= attributeIndex) + { + return Optional::Missing(); + } + return Optional(clusterObj->attributes[attributeIndex].attributeId); +} diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index 641412b92f1833..e843a50a9cc298 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -151,10 +151,15 @@ uint8_t emberAfClusterIndex(chip::EndpointId endpoint, chip::ClusterId clusterId // otherwise number of client clusters on this endpoint uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server); -// Returns the clusterId of Nth server or client cluster, +// Returns the cluster of Nth server or client cluster, // depending on server toggle. EmberAfCluster * emberAfGetNthCluster(chip::EndpointId endpoint, uint8_t n, bool server); +// Returns the clusterId of Nth server or client cluster, +// depending on server toggle. +// Returns Optional::Missing if cluster does not exist. +chip::Optional emberAfGetNthClusterId(chip::EndpointId endpoint, uint8_t n, bool server); + // Returns number of clusters put into the passed cluster list // for the given endpoint and client/server polarity uint8_t emberAfGetClustersFromEndpoint(chip::EndpointId endpoint, chip::ClusterId * clusterList, uint8_t listLen, bool server); @@ -242,7 +247,7 @@ bool emberAfEndpointIsEnabled(chip::EndpointId endpoint); // and those indexes may be DIFFERENT than the indexes returned from // emberAfGetNthCluster(). In other words: // -// - Use emberAfGetClustersFromEndpoint() with emberAfGetNthCluster() +// - Use emberAfGetClustersFromEndpoint() with emberAfGetNthCluster() emberAfGetNthClusterId() // - Use emberAfGetClusterCountForEndpoint() with emberAfGetClusterByIndex() // // Don't mix them. @@ -255,6 +260,21 @@ EmberAfStatus emberAfSetDynamicEndpoint(uint16_t index, chip::EndpointId id, Emb chip::EndpointId emberAfClearDynamicEndpoint(uint16_t index); uint16_t emberAfGetDynamicIndexFromEndpoint(chip::EndpointId id); +// Get the number of attributes of the specific cluster under the endpoint. +// Returns 0 if the cluster does not exist. +uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpoint, chip::ClusterId cluster); + +// Get the index of the given attribute of the specific cluster under the endpoint. +// Returns UINT16_MAX if the attribute does not exist. +uint16_t emberAfGetServerAttributeIndexByAttributeId(chip::EndpointId endpoint, chip::ClusterId cluster, + chip::AttributeId attributeId); + +// Get the attribute id at the attributeIndex of the cluster under the endpoint. This function is useful for iterating over the +// attributes. +// Returns Optional::Missing() if the attribute does not exist. +chip::Optional emberAfGetServerAttributeIdByIndex(chip::EndpointId endpoint, chip::ClusterId cluster, + uint16_t attributeIndex); + /** * Register an attribute access override. It will remain registered until * the endpoint it's registered for is disabled (or until shutdown if it's diff --git a/src/app/util/mock/BUILD.gn b/src/app/util/mock/BUILD.gn new file mode 100644 index 00000000000000..4ffcbf1c85034d --- /dev/null +++ b/src/app/util/mock/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright (c) 2021 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") + +source_set("mock_ember") { + sources = [ "attribute-storage.cpp" ] + + public_deps = [ + "${chip_root}/src/app", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", + ] + + cflags = [ "-Wconversion" ] +} diff --git a/src/app/util/mock/Constants.h b/src/app/util/mock/Constants.h new file mode 100644 index 00000000000000..2c515c8ebf1e95 --- /dev/null +++ b/src/app/util/mock/Constants.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2021 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. + */ + +/** + * @file + * This file contains the constants for the mocked attribute-storage.cpp + */ + +#pragma once + +#include + +namespace chip { +namespace Test { +constexpr EndpointId kMockEndpoint1 = 0xFFFE; +constexpr EndpointId kMockEndpoint2 = 0xFFFD; +constexpr EndpointId kMockEndpoint3 = 0xFFFC; + +constexpr AttributeId MockAttributeId(const uint16_t & id) +{ + return (0xFFF1'0000 | id); +} + +constexpr AttributeId MockClusterId(const uint16_t & id) +{ + return (0xFFF1'0000 | id); +} + +} // namespace Test +} // namespace chip diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp new file mode 100644 index 00000000000000..f9364ea9a37115 --- /dev/null +++ b/src/app/util/mock/attribute-storage.cpp @@ -0,0 +1,190 @@ +/* + * + * Copyright (c) 2021 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. + */ + +/** + * @file + * This file contains the mock implementation for the generated attribute-storage.cpp + * - It contains three endpoints, 0xFFFE, 0xFFFD, 0xFFFC + * - It contains four clusters: 0xFFF1'0001 to 0xFFF1'0004 + * - All cluster has two global attribute (0x0000'FFFC, 0x0000'FFFD) + * - Some clusters has some cluster-specific attributes, with 0xFFF1 prefix. + * + * Note: The ember's attribute-storage.cpp will include some app specific generated files. So we cannot use it directly. This + * might be fixed with a mock endpoint-config.h + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef uint8_t EmberAfClusterMask; + +using namespace chip; +using namespace chip::Test; +using namespace chip::app; + +namespace { + +EndpointId endpoints[] = { kMockEndpoint1, kMockEndpoint2, kMockEndpoint3 }; +uint16_t clusterIndex[] = { 0, 2, 5 }; +uint8_t clusterCount[] = { 2, 3, 4 }; +ClusterId clusters[] = { MockClusterId(1), MockClusterId(2), MockClusterId(1), MockClusterId(2), MockClusterId(3), + MockClusterId(1), MockClusterId(2), MockClusterId(3), MockClusterId(4) }; +uint16_t attributeIndex[] = { 0, 2, 5, 7, 11, 16, 19, 25, 27 }; +uint16_t attributeCount[] = { 2, 3, 2, 4, 5, 3, 6, 2, 2 }; +AttributeId attributes[] = { + // clang-format off + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, MockAttributeId(1), + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, MockAttributeId(1), + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), MockAttributeId(4), + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, + Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id + // clang-format on +}; + +} // namespace + +uint16_t emberAfEndpointCount(void) +{ + return ArraySize(endpoints); +} + +uint16_t emberAfIndexFromEndpoint(chip::EndpointId endpoint) +{ + for (uint16_t i = 0; i < ArraySize(endpoints); i++) + { + if (endpoints[i] == endpoint) + { + return i; + } + } + return UINT16_MAX; +} + +uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server) +{ + for (uint16_t i = 0; i < ArraySize(endpoints); i++) + { + if (endpoints[i] == endpoint) + { + return clusterCount[i]; + } + } + return 0; +} + +uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpoint, chip::ClusterId cluster) +{ + uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint); + uint16_t clusterCountOnEndpoint = emberAfClusterCount(endpoint, true); + for (uint16_t i = 0; i < clusterCountOnEndpoint; i++) + { + if (clusters[i + clusterIndex[endpointIndex]] == cluster) + { + return attributeCount[i + clusterIndex[endpointIndex]]; + } + } + return 0; +} + +uint16_t emberAfGetServerAttributeIndexByAttributeId(chip::EndpointId endpoint, chip::ClusterId cluster, + chip::AttributeId attributeId) +{ + uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint); + uint16_t clusterCountOnEndpoint = emberAfClusterCount(endpoint, true); + for (uint16_t i = 0; i < clusterCountOnEndpoint; i++) + { + if (clusters[i + clusterIndex[endpointIndex]] == cluster) + { + uint16_t clusterAttributeOffset = attributeIndex[i + clusterIndex[endpointIndex]]; + for (uint16_t j = 0; j < emberAfGetServerAttributeCount(endpoint, cluster); j++) + { + if (attributes[clusterAttributeOffset + j] == attributeId) + { + return j; + } + } + break; + } + } + return UINT16_MAX; +} + +chip::EndpointId emberAfEndpointFromIndex(uint16_t index) +{ + VerifyOrDie(index < ArraySize(endpoints)); + return endpoints[index]; +} + +chip::Optional emberAfGetNthClusterId(chip::EndpointId endpoint, uint8_t n, bool server) +{ + if (n >= emberAfClusterCount(endpoint, server)) + { + return chip::Optional::Missing(); + } + return chip::Optional(clusters[clusterIndex[emberAfIndexFromEndpoint(endpoint)] + n]); +} + +chip::Optional emberAfGetServerAttributeIdByIndex(chip::EndpointId endpoint, chip::ClusterId cluster, + uint16_t index) +{ + uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint); + uint16_t clusterCountOnEndpoint = emberAfClusterCount(endpoint, true); + for (uint16_t i = 0; i < clusterCountOnEndpoint; i++) + { + if (clusters[i + clusterIndex[endpointIndex]] == cluster) + { + uint16_t clusterAttributeOffset = attributeIndex[i + clusterIndex[endpointIndex]]; + if (index < emberAfGetServerAttributeCount(endpoint, cluster)) + { + return Optional(attributes[clusterAttributeOffset + index]); + } + break; + } + } + return Optional::Missing(); +} + +uint8_t emberAfClusterIndex(chip::EndpointId endpoint, chip::ClusterId cluster, EmberAfClusterMask mask) +{ + uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint); + uint16_t clusterCountOnEndpoint = emberAfClusterCount(endpoint, true); + for (uint8_t i = 0; i < clusterCountOnEndpoint; i++) + { + if (clusters[i + clusterIndex[endpointIndex]] == cluster) + { + return i; + } + } + return UINT8_MAX; +} diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h index ef81e350c58559..ef5f98d742f77e 100644 --- a/src/lib/core/DataModelTypes.h +++ b/src/lib/core/DataModelTypes.h @@ -43,5 +43,6 @@ typedef uint16_t ListIndex; typedef uint32_t TransactionId; static constexpr FabricIndex kUndefinedFabricIndex = 0; +static constexpr EndpointId kInvalidEndpointId = 0xFFFF; } // namespace chip