diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index b3583fd6b7c57c..9ee6ce6ad8ad14 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -56,6 +56,8 @@ #include "Options.h" +#include + using namespace chip; using namespace chip::Credentials; using namespace chip::DeviceLayer; diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 5fa15d41afd031..61f1d1834a0dfa 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -93,6 +93,8 @@ static_library("app") { "MessageDef/TimedRequest.cpp", "MessageDef/WriteRequest.cpp", "MessageDef/WriteResponse.cpp", + "PathIterator.cpp", + "PathIterator.h", "ReadClient.cpp", "ReadHandler.cpp", "WriteClient.cpp", diff --git a/src/app/ConcreteAttributePath.h b/src/app/ConcreteAttributePath.h index b3b44a8a2f359c..ac04da37c1283f 100644 --- a/src/app/ConcreteAttributePath.h +++ b/src/app/ConcreteAttributePath.h @@ -28,6 +28,11 @@ namespace app { */ struct ConcreteAttributePath { + static constexpr EndpointId kInvalidEndpointId = 0xFFFF; + static constexpr ClusterId kInvalidClusterId = 0xFFFF'FFFF; + static constexpr AttributeId kInvalidAttributeId = 0xFFFF'FFFF; + + ConcreteAttributePath() {} ConcreteAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId) : mEndpointId(aEndpointId), mClusterId(aClusterId), mAttributeId(aAttributeId) {} @@ -37,9 +42,9 @@ struct ConcreteAttributePath return mEndpointId == other.mEndpointId && mClusterId == other.mClusterId && mAttributeId == other.mAttributeId; } - EndpointId mEndpointId = 0; - ClusterId mClusterId = 0; - AttributeId mAttributeId = 0; + EndpointId mEndpointId = kInvalidEndpointId; + ClusterId mClusterId = kInvalidClusterId; + AttributeId mAttributeId = kInvalidAttributeId; }; } // namespace app } // namespace chip diff --git a/src/app/PathIterator.cpp b/src/app/PathIterator.cpp new file mode 100644 index 00000000000000..df181335178be3 --- /dev/null +++ b/src/app/PathIterator.cpp @@ -0,0 +1,189 @@ +/* + * + * 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 defines read handler for a CHIP Interaction Data model + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; + +typedef uint8_t EmberAfClusterMask; +#define CLUSTER_MASK_SERVER (0x40) + +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 chip::ClusterId emberAfGetNthClusterId(chip::EndpointId endpoint, uint8_t n, bool server); +extern chip::AttributeId emberAfGetServerAttributeIdByIndex(chip::EndpointId endpoint, chip::ClusterId cluster, + uint16_t attributeIndex); +extern uint8_t emberAfClusterIndex(EndpointId endpoint, ClusterId clusterId, EmberAfClusterMask mask); + +namespace chip { +namespace app { + +void PathIterator::Reset(ClusterInfo * aClusterInfo) +{ + mpClusterInfo = aClusterInfo; + + // Reset iterator state + mEndpointIndex = UINT16_MAX; + mClusterIndex = UINT8_MAX; + mAttributeIndex = UINT16_MAX; + + Proceed(); +} + +void PathIterator::PrepareEndpointIndexRange(const Optional & aEndpointId, uint16_t * aBeginEndpointIndex, + uint16_t * aEndEndpointIndex) +{ + if (!aEndpointId.HasValue()) + { + *aBeginEndpointIndex = 0; + *aEndEndpointIndex = emberAfEndpointCount(); + } + else + { + *aBeginEndpointIndex = emberAfIndexFromEndpoint(aEndpointId.Value()); + *aEndEndpointIndex = *aBeginEndpointIndex + 1; + } +} + +void PathIterator::PrepareClusterIndexRange(EndpointId aEndpointId, const Optional & aClusterId, + uint8_t * aBeginClusterIndex, uint8_t * aEndClusterIndex) +{ + if (!aClusterId.HasValue()) + { + *aBeginClusterIndex = 0; + *aEndClusterIndex = emberAfClusterCount(aEndpointId, true /* server */); + } + else + { + *aBeginClusterIndex = emberAfClusterIndex(aEndpointId, aClusterId.Value(), CLUSTER_MASK_SERVER); + // If the given cluster id does not exists on the given endpoint, it will return uint8(0xFF), then endClusterIndex + // will be 0 + *aEndClusterIndex = *aBeginClusterIndex + 1; + } +} + +void PathIterator::PrepareAttributeIndexRange(EndpointId aEndpointId, ClusterId aClusterId, + const Optional & aAttributeId, uint16_t * aBeginAttributeIndex, + uint16_t * aEndAttributeIndex) +{ + if (!aAttributeId.HasValue()) + { + *aBeginAttributeIndex = 0; + *aEndAttributeIndex = emberAfGetServerAttributeCount(aEndpointId, aClusterId); + } + else + { + *aBeginAttributeIndex = emberAfGetServerAttributeIndexByAttributeId(aEndpointId, aClusterId, aAttributeId.Value()); + *aEndAttributeIndex = *aBeginAttributeIndex + 1; + } +} + +bool PathIterator::Proceed() +{ + for (; mpClusterInfo != nullptr; (mpClusterInfo = mpClusterInfo->mpNext, 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.Value(); + mOutputPath.mClusterId = mpClusterInfo->mClusterId.Value(); + mOutputPath.mAttributeId = mpClusterInfo->mFieldId.Value(); + + // Prepare for next iteration + (mpClusterInfo = mpClusterInfo->mpNext, mEndpointIndex = UINT16_MAX); + return true; + } + uint16_t beginEndpointIndex, endEndpointIndex; + PrepareEndpointIndexRange(mpClusterInfo->mEndpointId, &beginEndpointIndex, &endEndpointIndex); + + if (mEndpointIndex == UINT16_MAX) + { + // If we have not started iterating over the endpoints yet. + mEndpointIndex = beginEndpointIndex; + mClusterIndex = UINT8_MAX; + } + + for (; mEndpointIndex < endEndpointIndex; (mEndpointIndex++, mClusterIndex = UINT8_MAX, mAttributeIndex = UINT16_MAX)) + { + EndpointId endpointId = emberAfEndpointFromIndex(mEndpointIndex); + uint8_t beginClusterIndex, endClusterIndex; + PrepareClusterIndexRange(endpointId, mpClusterInfo->mClusterId, &beginClusterIndex, &endClusterIndex); + + if (mClusterIndex == UINT8_MAX) + { + // If we have not started iterating over the clusters yet. + mClusterIndex = beginClusterIndex; + mAttributeIndex = UINT16_MAX; + } + + for (; mClusterIndex < endClusterIndex; (mClusterIndex++, mAttributeIndex = UINT16_MAX)) + { + ClusterId clusterId = emberAfGetNthClusterId(endpointId, mClusterIndex, true /* server */); + uint16_t beginAttributeIndex, endAttributeIndex; + PrepareAttributeIndexRange(endpointId, clusterId, mpClusterInfo->mFieldId, &beginAttributeIndex, + &endAttributeIndex); + if (mAttributeIndex == UINT16_MAX) + { + // If we have not started iterating over the attributes yet. + mAttributeIndex = beginAttributeIndex; + } + + if (mAttributeIndex < endAttributeIndex) + { + mOutputPath.mAttributeId = emberAfGetServerAttributeIdByIndex(endpointId, clusterId, mAttributeIndex); + 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/PathIterator.h b/src/app/PathIterator.h new file mode 100644 index 00000000000000..51ddcbbef23e8b --- /dev/null +++ b/src/app/PathIterator.h @@ -0,0 +1,93 @@ +/* + * + * 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 defines read handler for a CHIP Interaction Data model + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +/** + * PathIterator is used for iterate over a chain of cluster info. + * The PathIterator is copiable, however, the given cluster info must be valid when calling proceed. + * + * PathIterator will expand attribute paths with wildcards. + * + * The typical use of PathIterator may look like: + * ConcreteAttributePath path; + * for (PathIterator iterator(clusterInfo); iterator.Get(path); iterator.Proceed()) {...} + */ +class PathIterator +{ +public: + PathIterator(ClusterInfo * aClusterInfo) { Reset(aClusterInfo); } + // Init initialized / resets the state of the path iterator. + void Reset(ClusterInfo * aClusterInfo); + /** + * Proceed the iterator to the next attribute path in the given cluster info. + */ + bool Proceed(); + /** + * Fills the aPath with the path the iterator current points to. + * Return false if the iterator is not pointing a valid path (i.e. it has exhausted the cluster info). + */ + bool Get(ConcreteAttributePath & aPath) + { + aPath = mOutputPath; + return mOutputPath.mEndpointId != ConcreteAttributePath::kInvalidEndpointId; + } + bool Valid() { return mpClusterInfo != nullptr; } + +private: + ClusterInfo * mpClusterInfo; + + uint16_t mEndpointIndex; + uint8_t mClusterIndex; + uint16_t mAttributeIndex; + + ConcreteAttributePath mOutputPath = ConcreteAttributePath(); + + void PrepareEndpointIndexRange(const Optional & aEndpointId, uint16_t * aBeginEndpointIndex, + uint16_t * aEndEndpointIndex); + void PrepareClusterIndexRange(EndpointId aEndpointId, const Optional & aClusterId, uint8_t * aBeginClusterIndex, + uint8_t * aEndClusterIndex); + void PrepareAttributeIndexRange(EndpointId aEndpointId, ClusterId aClusterId, const Optional & aAttributeId, + uint16_t * aBeginAttributeIndex, uint16_t * aEndAttributeIndex); +}; +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 9bfd53e0622530..a812d75cff0931 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -50,6 +50,7 @@ chip_test_suite("tests") { "TestEventPathParams.cpp", "TestInteractionModelEngine.cpp", "TestMessageDef.cpp", + "TestPathIterator.cpp", "TestStatusResponse.cpp", ] @@ -69,6 +70,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/TestPathIterator.cpp b/src/app/tests/TestPathIterator.cpp new file mode 100644 index 00000000000000..e27463689dc06c --- /dev/null +++ b/src/app/tests/TestPathIterator.cpp @@ -0,0 +1,279 @@ +/* + * + * 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 defines read handler for a CHIP Interaction Data model + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace chip; + +namespace { + +using P = app::ConcreteAttributePath; + +void TestAllWildcard(nlTestSuite * apSuite, void * apContext) +{ + app::ClusterInfo clusInfo; + + app::ConcreteAttributePath path; + P paths[] = { + P(0xFFFE, 0xFFF1'0001, 0x0000'FFFD), P(0xFFFE, 0xFFF1'0001, 0x0000'FFFC), P(0xFFFE, 0xFFF1'0002, 0x0000'FFFD), + P(0xFFFE, 0xFFF1'0002, 0x0000'FFFC), P(0xFFFE, 0xFFF1'0002, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0001, 0x0000'FFFD), + P(0xFFFD, 0xFFF1'0001, 0x0000'FFFC), P(0xFFFD, 0xFFF1'0002, 0x0000'FFFD), P(0xFFFD, 0xFFF1'0002, 0x0000'FFFC), + P(0xFFFD, 0xFFF1'0002, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0002, 0xFFF1'0002), P(0xFFFD, 0xFFF1'0003, 0x0000'FFFD), + P(0xFFFD, 0xFFF1'0003, 0x0000'FFFC), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0002), + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), P(0xFFFC, 0xFFF1'0001, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0001, 0x0000'FFFC), + P(0xFFFC, 0xFFF1'0001, 0xFFF1'0001), P(0xFFFC, 0xFFF1'0002, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0002, 0x0000'FFFC), + P(0xFFFC, 0xFFF1'0002, 0xFFF1'0001), P(0xFFFC, 0xFFF1'0002, 0xFFF1'0002), P(0xFFFC, 0xFFF1'0002, 0xFFF1'0003), + P(0xFFFC, 0xFFF1'0002, 0xFFF1'0004), P(0xFFFC, 0xFFF1'0003, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0003, 0x0000'FFFC), + P(0xFFFC, 0xFFF1'0004, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0004, 0x0000'FFFC), + }; + + size_t index = 0; + + for (app::PathIterator iter(&clusInfo); iter.Get(path); iter.Proceed()) + { + 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.SetValue(Test::MockClusterId(3)); + clusInfo.mFieldId.SetValue(Test::MockAttributeId(3)); + + app::ConcreteAttributePath path; + P paths[] = { + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), + }; + + size_t index = 0; + + for (app::PathIterator iter(&clusInfo); iter.Get(path); iter.Proceed()) + { + 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.SetValue(Test::kMockEndpoint3); + clusInfo.mFieldId.SetValue(app::Clusters::Globals::Attributes::ClusterRevision::Id); + + app::ConcreteAttributePath path; + P paths[] = { + P(0xFFFC, 0xFFF1'0001, 0x0000'FFFD), + P(0xFFFC, 0xFFF1'0002, 0x0000'FFFD), + P(0xFFFC, 0xFFF1'0003, 0x0000'FFFD), + P(0xFFFC, 0xFFF1'0004, 0x0000'FFFD), + }; + + size_t index = 0; + + for (app::PathIterator iter(&clusInfo); iter.Get(path); iter.Proceed()) + { + 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.SetValue(Test::kMockEndpoint2); + clusInfo.mClusterId.SetValue(Test::MockClusterId(3)); + + app::ConcreteAttributePath path; + P paths[] = { + P(0xFFFD, 0xFFF1'0003, 0x0000'FFFD), P(0xFFFD, 0xFFF1'0003, 0x0000'FFFC), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0001), + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0002), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), + }; + + size_t index = 0; + + for (app::PathIterator iter(&clusInfo); iter.Get(path); iter.Proceed()) + { + 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.SetValue(Test::kMockEndpoint2); + clusInfo.mClusterId.SetValue(Test::MockClusterId(3)); + clusInfo.mFieldId.SetValue(Test::MockAttributeId(3)); + + app::ConcreteAttributePath path; + P paths[] = { + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), + }; + + size_t index = 0; + + for (app::PathIterator iter(&clusInfo); iter.Get(path); iter.Proceed()) + { + 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.SetValue(Test::MockClusterId(3)); + clusInfo2.mFieldId.SetValue(Test::MockAttributeId(3)); + + app::ClusterInfo clusInfo3; + clusInfo3.mEndpointId.SetValue(Test::kMockEndpoint3); + clusInfo3.mFieldId.SetValue(app::Clusters::Globals::Attributes::ClusterRevision::Id); + + app::ClusterInfo clusInfo4; + clusInfo4.mEndpointId.SetValue(Test::kMockEndpoint2); + clusInfo4.mClusterId.SetValue(Test::MockClusterId(3)); + + app::ClusterInfo clusInfo5; + clusInfo5.mEndpointId.SetValue(Test::kMockEndpoint2); + clusInfo5.mClusterId.SetValue(Test::MockClusterId(3)); + clusInfo5.mFieldId.SetValue(Test::MockAttributeId(3)); + + clusInfo1.mpNext = &clusInfo2; + clusInfo2.mpNext = &clusInfo3; + clusInfo3.mpNext = &clusInfo4; + clusInfo4.mpNext = &clusInfo5; + + app::ConcreteAttributePath path; + P paths[] = { + P(0xFFFE, 0xFFF1'0001, 0x0000'FFFD), P(0xFFFE, 0xFFF1'0001, 0x0000'FFFC), P(0xFFFE, 0xFFF1'0002, 0x0000'FFFD), + P(0xFFFE, 0xFFF1'0002, 0x0000'FFFC), P(0xFFFE, 0xFFF1'0002, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0001, 0x0000'FFFD), + P(0xFFFD, 0xFFF1'0001, 0x0000'FFFC), P(0xFFFD, 0xFFF1'0002, 0x0000'FFFD), P(0xFFFD, 0xFFF1'0002, 0x0000'FFFC), + P(0xFFFD, 0xFFF1'0002, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0002, 0xFFF1'0002), P(0xFFFD, 0xFFF1'0003, 0x0000'FFFD), + P(0xFFFD, 0xFFF1'0003, 0x0000'FFFC), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0002), + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), P(0xFFFC, 0xFFF1'0001, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0001, 0x0000'FFFC), + P(0xFFFC, 0xFFF1'0001, 0xFFF1'0001), P(0xFFFC, 0xFFF1'0002, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0002, 0x0000'FFFC), + P(0xFFFC, 0xFFF1'0002, 0xFFF1'0001), P(0xFFFC, 0xFFF1'0002, 0xFFF1'0002), P(0xFFFC, 0xFFF1'0002, 0xFFF1'0003), + P(0xFFFC, 0xFFF1'0002, 0xFFF1'0004), P(0xFFFC, 0xFFF1'0003, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0003, 0x0000'FFFC), + P(0xFFFC, 0xFFF1'0004, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0004, 0x0000'FFFC), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), + P(0xFFFC, 0xFFF1'0001, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0002, 0x0000'FFFD), P(0xFFFC, 0xFFF1'0003, 0x0000'FFFD), + P(0xFFFC, 0xFFF1'0004, 0x0000'FFFD), P(0xFFFD, 0xFFF1'0003, 0x0000'FFFD), P(0xFFFD, 0xFFF1'0003, 0x0000'FFFC), + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0001), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0002), P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), + P(0xFFFD, 0xFFF1'0003, 0xFFF1'0003), + }; + + size_t index = 0; + + for (app::PathIterator iter(&clusInfo1); iter.Get(path); iter.Proceed()) + { + 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 = +{ + "TestPathIterator", + &sTests[0], + TestSetup, + TestTeardown, +}; +// clang-format on + +} // namespace + +int TestPathIterator() +{ + nlTestRunner(&sSuite, nullptr); + return (nlTestRunnerStats(&sSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestPathIterator) diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index d600c92748d997..9cf1e1b1717b05 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -1143,6 +1144,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 MEI::kWildcard if cluster does not exists. +ClusterId emberAfGetNthClusterId(EndpointId endpoint, uint8_t n, bool server) +{ + EmberAfCluster * cluster = emberAfGetNthCluster(endpoint, n, server); + if (cluster == nullptr) + { + return app::ConcreteAttributePath::kInvalidClusterId; + } + return 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) @@ -1357,3 +1371,37 @@ 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; +} + +chip::AttributeId emberAfGetServerAttributeIdByIndex(chip::EndpointId endpoint, chip::ClusterId cluster, uint16_t attributeIndex) +{ + EmberAfCluster * clusterObj = emberAfFindCluster(endpoint, cluster, CLUSTER_MASK_SERVER); + VerifyOrReturnError(clusterObj != nullptr, 0); + if (clusterObj->attributeCount <= attributeIndex) + { + return app::ConcreteAttributePath::kInvalidAttributeId; + } + return clusterObj->attributes[attributeIndex].attributeId; +} diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index 8a4b9075cf43d0..3486e71899e320 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -149,10 +149,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 app::ConcreteAttributePath::kInvalidClusterId if cluster does not exists. +chip::ClusterId 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); @@ -240,7 +245,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() amberAfGetNthClusterId() // - Use emberAfGetClusterCountForEndpoint() with emberAfGetClusterByIndex() // // Don't mix them. @@ -253,6 +258,22 @@ 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 attributs of the specific cluster under the endpoint. Is useful for the application to get the basic infomation +// of or iterate over the attributes. +// Returns 0 if the cluster or endpoint do not exists. +uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpoint, chip::ClusterId cluster); + +// Get the index of attribut of the specific cluster under the endpoint. Is useful for the application to get the basic infomation +// of or iterate over the attributes. +// Returns UINT16_MAX if the attribute, cluster or endpoint do not exists. +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 chip::ConcreteAttributePath::kInvalidAttributeId if the attribute does not exists. +chip::AttributeId 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..2312cfcf1441de --- /dev/null +++ b/src/app/util/mock/attribute-storage.cpp @@ -0,0 +1,186 @@ +/* + * + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Mock Functions +typedef uint8_t EmberAfClusterMask; +#define CLUSTER_MASK_SERVER (0x40) + +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 UINT16_MAX; +} + +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; + } + } + return UINT16_MAX; + } + } + return UINT16_MAX; +} + +chip::EndpointId emberAfEndpointFromIndex(uint16_t index) +{ + return index < ArraySize(endpoints) ? endpoints[index] : app::ConcreteAttributePath::kInvalidEndpointId; +} + +chip::ClusterId emberAfGetNthClusterId(chip::EndpointId endpoint, uint8_t n, bool server) +{ + if (n >= emberAfClusterCount(endpoint, server)) + { + return app::ConcreteAttributePath::kInvalidClusterId; + } + return clusters[clusterIndex[emberAfIndexFromEndpoint(endpoint)] + n]; +} + +chip::AttributeId 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 app::ConcreteAttributePath::kInvalidAttributeId; + } + return attributes[clusterAttributeOffset + index]; + } + } + return app::ConcreteAttributePath::kInvalidAttributeId; +} + +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; +}