| /* |
| * |
| * 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/AttributePathParams.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/EventManagement.h> |
| #include <app/GlobalAttributes.h> |
| #include <app/util/att-storage.h> |
| #include <app/util/endpoint-config-api.h> |
| #include <lib/core/CHIPCore.h> |
| #include <lib/core/TLVDebug.h> |
| #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(); |
| 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); |
| extern bool emberAfEndpointIndexIsEnabled(uint16_t index); |
| |
| namespace chip { |
| namespace app { |
| |
| AttributePathExpandIterator::AttributePathExpandIterator(SingleLinkedListNode<AttributePathParams> * aAttributePath) |
| { |
| mpAttributePath = aAttributePath; |
| |
| // Reset iterator state |
| mEndpointIndex = UINT16_MAX; |
| mClusterIndex = UINT8_MAX; |
| mAttributeIndex = UINT16_MAX; |
| |
| static_assert(std::numeric_limits<decltype(mGlobalAttributeIndex)>::max() >= ArraySize(GlobalAttributesNotInMetadata), |
| "Our index won't be able to hold the value we need to hold."); |
| static_assert(std::is_same<decltype(mGlobalAttributeIndex), uint8_t>::value, |
| "If this changes audit all uses where we set to UINT8_MAX"); |
| mGlobalAttributeIndex = UINT8_MAX; |
| |
| // Make the iterator ready to emit the first valid path in the list. |
| Next(); |
| } |
| |
| void AttributePathExpandIterator::PrepareEndpointIndexRange(const AttributePathParams & aAttributePath) |
| { |
| if (aAttributePath.HasWildcardEndpointId()) |
| { |
| mEndpointIndex = 0; |
| mEndEndpointIndex = emberAfEndpointCount(); |
| } |
| else |
| { |
| mEndpointIndex = emberAfIndexFromEndpoint(aAttributePath.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 AttributePathParams & aAttributePath, EndpointId aEndpointId) |
| { |
| if (aAttributePath.HasWildcardClusterId()) |
| { |
| mClusterIndex = 0; |
| mEndClusterIndex = emberAfClusterCount(aEndpointId, true /* server */); |
| } |
| else |
| { |
| mClusterIndex = emberAfClusterIndex(aEndpointId, aAttributePath.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 AttributePathParams & aAttributePath, EndpointId aEndpointId, |
| ClusterId aClusterId) |
| { |
| if (aAttributePath.HasWildcardAttributeId()) |
| { |
| mAttributeIndex = 0; |
| mEndAttributeIndex = emberAfGetServerAttributeCount(aEndpointId, aClusterId); |
| mGlobalAttributeIndex = 0; |
| mGlobalAttributeEndIndex = ArraySize(GlobalAttributesNotInMetadata); |
| } |
| else |
| { |
| mAttributeIndex = emberAfGetServerAttributeIndexByAttributeId(aEndpointId, aClusterId, aAttributePath.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); |
| if (mAttributeIndex == UINT16_MAX) |
| { |
| // Check whether this is a non-metadata global attribute. |
| // |
| // Default to the max value, which will correspond (after we add 1 |
| // and overflow to 0 for the max index) to us not going through |
| // non-metadata global attributes for this attribute. |
| mGlobalAttributeIndex = UINT8_MAX; |
| |
| static_assert(ArraySize(GlobalAttributesNotInMetadata) <= UINT8_MAX, "Iterating over at most 256 array entries"); |
| |
| const uint8_t arraySize = static_cast<uint8_t>(ArraySize(GlobalAttributesNotInMetadata)); |
| for (uint8_t idx = 0; idx < arraySize; ++idx) |
| { |
| if (GlobalAttributesNotInMetadata[idx] == aAttributePath.mAttributeId) |
| { |
| mGlobalAttributeIndex = idx; |
| break; |
| } |
| } |
| mGlobalAttributeEndIndex = static_cast<uint8_t>(mGlobalAttributeIndex + 1); |
| } |
| else |
| { |
| mGlobalAttributeIndex = UINT8_MAX; |
| mGlobalAttributeEndIndex = 0; |
| } |
| } |
| } |
| |
| void AttributePathExpandIterator::ResetCurrentCluster() |
| { |
| // If this is a null iterator, or the attribute id of current cluster info is not a wildcard attribute id, then this function |
| // will do nothing, since we won't be expanding the wildcard attribute ids under a cluster. |
| VerifyOrReturn(mpAttributePath != nullptr && mpAttributePath->mValue.HasWildcardAttributeId()); |
| |
| // Otherwise, we will reset the index for iterating the attributes, so we report the attributes for this cluster again. This |
| // will ensure that the client sees a coherent view of the cluster from the reports generated by a single (wildcard) attribute |
| // path in the request. |
| // |
| // Note that when Next() returns, we must be in one of the following states: |
| // - This is not a wildcard path |
| // - We just expanded some attribute id field |
| // - We have exhausted all paths |
| // Only the second case will happen here since the above check will fail for 1 and 3, so the following Next() call must result |
| // in a valid path, which is the first attribute id we will emit for the current cluster. |
| mAttributeIndex = UINT16_MAX; |
| mGlobalAttributeIndex = UINT8_MAX; |
| Next(); |
| } |
| |
| bool AttributePathExpandIterator::Next() |
| { |
| for (; mpAttributePath != nullptr; (mpAttributePath = mpAttributePath->mpNext, mEndpointIndex = UINT16_MAX)) |
| { |
| mOutputPath.mExpanded = mpAttributePath->mValue.IsWildcardPath(); |
| |
| if (mEndpointIndex == UINT16_MAX) |
| { |
| // Special case: If this is a concrete path, we just return its value as-is. |
| if (!mpAttributePath->mValue.IsWildcardPath()) |
| { |
| mOutputPath.mEndpointId = mpAttributePath->mValue.mEndpointId; |
| mOutputPath.mClusterId = mpAttributePath->mValue.mClusterId; |
| mOutputPath.mAttributeId = mpAttributePath->mValue.mAttributeId; |
| |
| // Prepare for next iteration |
| mEndpointIndex = mEndEndpointIndex = 0; |
| return true; |
| } |
| |
| PrepareEndpointIndexRange(mpAttributePath->mValue); |
| mClusterIndex = UINT8_MAX; |
| } |
| |
| for (; mEndpointIndex < mEndEndpointIndex; |
| (mEndpointIndex++, mClusterIndex = UINT8_MAX, mAttributeIndex = UINT16_MAX, mGlobalAttributeIndex = UINT8_MAX)) |
| { |
| if (!emberAfEndpointIndexIsEnabled(mEndpointIndex)) |
| { |
| // Not an enabled endpoint; skip it. |
| continue; |
| } |
| |
| EndpointId endpointId = emberAfEndpointFromIndex(mEndpointIndex); |
| |
| if (mClusterIndex == UINT8_MAX) |
| { |
| PrepareClusterIndexRange(mpAttributePath->mValue, endpointId); |
| mAttributeIndex = UINT16_MAX; |
| mGlobalAttributeIndex = UINT8_MAX; |
| } |
| |
| for (; mClusterIndex < mEndClusterIndex; |
| (mClusterIndex++, mAttributeIndex = UINT16_MAX, mGlobalAttributeIndex = UINT8_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 && mGlobalAttributeIndex == UINT8_MAX) |
| { |
| PrepareAttributeIndexRange(mpAttributePath->mValue, 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 mpAttributePath. |
| return true; |
| } |
| if (mGlobalAttributeIndex < mGlobalAttributeEndIndex) |
| { |
| // Return a path pointing to the next global attribute. |
| mOutputPath.mAttributeId = GlobalAttributesNotInMetadata[mGlobalAttributeIndex]; |
| mOutputPath.mClusterId = clusterId; |
| mOutputPath.mEndpointId = endpointId; |
| mGlobalAttributeIndex++; |
| 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 = ConcreteReadAttributePath(); |
| return false; |
| } |
| } // namespace app |
| } // namespace chip |