blob: e5d1f961434eb290445fe52162c6f9a940db428e [file] [log] [blame]
/*
*
* 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.
*/
#pragma once
#include <app/AttributePathParams.h>
#include <app/ConcreteAttributePath.h>
#include <app/data-model-provider/MetadataList.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/data-model-provider/Provider.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/LinkedList.h>
#include <lib/support/Span.h>
#include <limits>
namespace chip {
namespace app {
/// Handles attribute path expansions
/// Usage:
///
/// - Start iterating by creating an iteration state
///
/// AttributePathExpandIterator::Position position = AttributePathExpandIterator::Position::StartIterating(path);
///
/// - Use the iteration state in a for loop:
///
/// ConcreteAttributePath path;
/// for (AttributePathExpandIterator iterator(position); iterator->Next(path);) {
/// // use `path` here`
/// }
///
/// OR:
///
/// ConcreteAttributePath path;
/// AttributePathExpandIterator iterator(position);
///
/// while (iterator.Next(path)) {
/// // use `path` here`
/// }
///
/// Usage requirements and assumptions:
///
/// - An ` AttributePathExpandIterator::Position` can only be used by a single AttributePathExpandIterator at a time.
///
/// - `position` is automatically updated by the AttributePathExpandIterator, so
/// calling `Next` on the iterator will update the position cursor variable.
///
class AttributePathExpandIterator
{
public:
class Position
{
public:
// Position is treated as a direct member access by the AttributePathExpandIterator, however it is opaque (except copying)
// for external code. We allow friendship here to not have specific get/set for methods (clearer interface and less
// likelihood of extra code usage).
friend class AttributePathExpandIterator;
/// External callers can only ever start iterating on a new path from the beginning
static Position StartIterating(SingleLinkedListNode<AttributePathParams> * path) { return Position(path); }
/// Copies are allowed
Position(const Position &) = default;
Position & operator=(const Position &) = default;
Position() : mAttributePath(nullptr) {}
/// Reset the iterator to the beginning of current cluster if we are in the middle of expanding a wildcard attribute id for
/// some cluster.
///
/// When attributes are changed in the middle of expanding a wildcard attribute, we need to reset the iterator, to provide
/// the client with a consistent state of the cluster.
void IterateFromTheStartOfTheCurrentClusterIfAttributeWildcard()
{
VerifyOrReturn(mAttributePath != nullptr && mAttributePath->mValue.HasWildcardAttributeId());
mOutputPath.mAttributeId = kInvalidAttributeId;
}
protected:
Position(SingleLinkedListNode<AttributePathParams> * path) :
mAttributePath(path), mOutputPath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)
{}
SingleLinkedListNode<AttributePathParams> * mAttributePath;
ConcreteAttributePath mOutputPath;
};
AttributePathExpandIterator(DataModel::Provider * dataModel, Position & position);
// This class may not be copied. A new one should be created when needed and they
// should not overlap.
AttributePathExpandIterator(const AttributePathExpandIterator &) = delete;
AttributePathExpandIterator & operator=(const AttributePathExpandIterator &) = delete;
/// Get the next path of the expansion (if one exists).
///
/// On success, true is returned and `path` is filled with the next path in the
/// expansion.
/// On iteration completion, false is returned and the content of path IS NOT DEFINED.
bool Next(ConcreteAttributePath & path);
private:
static constexpr size_t kInvalidIndex = std::numeric_limits<size_t>::max();
DataModel::Provider * mDataModelProvider;
Position & mPosition;
DataModel::ReadOnlyBuffer<DataModel::EndpointEntry> mEndpoints; // all endpoints
size_t mEndpointIndex = kInvalidIndex;
DataModel::ReadOnlyBuffer<DataModel::ServerClusterEntry> mClusters; // all clusters ON THE CURRENT endpoint
size_t mClusterIndex = kInvalidIndex;
DataModel::ReadOnlyBuffer<DataModel::AttributeEntry> mAttributes; // all attributes ON THE CURRENT cluster
size_t mAttributeIndex = kInvalidIndex;
/// Move to the next endpoint/cluster/attribute triplet that is valid given
/// the current mOutputPath and mpAttributePath.
///
/// returns true if such a next value was found.
bool AdvanceOutputPath();
/// Get the next attribute ID in mOutputPath(endpoint/cluster) if one is available.
/// Will start from the beginning if current mOutputPath.mAttributeId is kInvalidAttributeId
///
/// Respects path expansion/values in mpAttributePath
///
/// Handles Global attributes (which are returned at the end)
std::optional<AttributeId> NextAttributeId();
/// Get the next cluster ID in mOutputPath(endpoint) if one is available.
/// Will start from the beginning if current mOutputPath.mClusterId is kInvalidClusterId
///
/// Respects path expansion/values in mpAttributePath
std::optional<ClusterId> NextClusterId();
/// Get the next endpoint ID in mOutputPath if one is available.
/// Will start from the beginning if current mOutputPath.mEndpointId is kInvalidEndpointId
///
/// Respects path expansion/values in mpAttributePath
std::optional<EndpointId> NextEndpointId();
/// Checks if the given attributeId is valid for the current mOutputPath(endpoint/cluster)
///
/// Meaning that it is known to the data model OR it is a always-there global attribute.
bool IsValidAttributeId(AttributeId attributeId);
};
/// RollbackAttributePathExpandIterator is an AttributePathExpandIterator wrapper that rolls back the Next()
/// call whenever a new `MarkCompleted()` method is not called.
///
/// Example use cases:
///
/// - Iterate over all attributes and process one-by-one, however when the iteration fails, resume at
/// the last failure point:
///
/// RollbackAttributePathExpandIterator iterator(....);
/// ConcreteAttributePath path;
///
/// for ( ; iterator.Next(path); iterator.MarkCompleted()) {
/// if (!CanProcess(path)) {
/// // iterator state IS PRESERVED so that Next() will return the SAME path on the next call.
/// return CHIP_ERROR_TRY_AGAIN_LATER;
/// }
/// }
///
/// - Grab what the next output path would be WITHOUT advancing a state;
///
/// {
/// RollbackAttributePathExpandIterator iterator(...., state);
/// if (iterator.Next(...)) { ... }
/// }
/// // state here is ROLLED BACK (i.e. initializing a new iterator with it will start at the same place as the previous
/// iteration attempt).
///
///
class RollbackAttributePathExpandIterator
{
public:
RollbackAttributePathExpandIterator(DataModel::Provider * dataModel, AttributePathExpandIterator::Position & position) :
mAttributePathExpandIterator(dataModel, position), mPositionTarget(position), mCompletedPosition(position)
{}
~RollbackAttributePathExpandIterator() { mPositionTarget = mCompletedPosition; }
bool Next(ConcreteAttributePath & path) { return mAttributePathExpandIterator.Next(path); }
/// Marks the current iteration completed (so peek does not actually roll back)
void MarkCompleted() { mCompletedPosition = mPositionTarget; }
private:
AttributePathExpandIterator mAttributePathExpandIterator;
AttributePathExpandIterator::Position & mPositionTarget;
AttributePathExpandIterator::Position mCompletedPosition;
};
} // namespace app
} // namespace chip