Skip to content

Commit

Permalink
[IM] AttributePathExpandIterator for iterating over list of ClusterIn…
Browse files Browse the repository at this point in the history
…fo-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
  • Loading branch information
erjiaqing authored and pull[bot] committed Jul 8, 2022
1 parent fac1857 commit 4354776
Show file tree
Hide file tree
Showing 14 changed files with 968 additions and 6 deletions.
182 changes: 182 additions & 0 deletions src/app/AttributePathExpandIterator.cpp
Original file line number Diff line number Diff line change
@@ -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 <app/AttributePathExpandIterator.h>

#include <app-common/zap-generated/att-storage.h>
#include <app/ClusterInfo.h>
#include <app/ConcreteAttributePath.h>
#include <app/EventManagement.h>
#include <app/InteractionModelDelegate.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPTLVDebug.hpp>
#include <lib/support/CodeUtils.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/logging/CHIPLogging.h>

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<ClusterId> emberAfGetNthClusterId(chip::EndpointId endpoint, uint8_t n, bool server);
extern Optional<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 {

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<uint16_t>(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<uint8_t>(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<uint16_t>(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
122 changes: 122 additions & 0 deletions src/app/AttributePathExpandIterator.h
Original file line number Diff line number Diff line change
@@ -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 <app/ClusterInfo.h>
#include <app/ConcreteAttributePath.h>
#include <app/EventManagement.h>
#include <app/InteractionModelDelegate.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPTLVDebug.hpp>
#include <lib/support/CodeUtils.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <protocols/Protocols.h>
#include <system/SystemPacketBuffer.h>

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
4 changes: 2 additions & 2 deletions src/app/AttributePathParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions src/app/ClusterInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/app/ConcreteAttributePath.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace app {
*/
struct ConcreteAttributePath
{
ConcreteAttributePath() {}
ConcreteAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId) :
mEndpointId(aEndpointId), mClusterId(aClusterId), mAttributeId(aAttributeId)
{}
Expand Down
2 changes: 2 additions & 0 deletions src/app/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ chip_test_suite("tests") {
output_name = "libAppTests"

test_sources = [
"TestAttributePathExpandIterator.cpp",
"TestAttributeValueEncoder.cpp",
"TestBuilderParser.cpp",
"TestCHIPDeviceCallbacksMgr.cpp",
Expand All @@ -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",
]
Expand Down
Loading

0 comments on commit 4354776

Please sign in to comment.