blob: 2fb542c768aac0885d61b066f57ddbb0ebfe212e [file] [log] [blame]
/*
* Copyright (c) 2024 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/codegen-data-model-provider/CodegenDataModelProvider.h>
#include <access/AccessControl.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app/CommandHandlerInterface.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/ConcreteClusterPath.h>
#include <app/ConcreteCommandPath.h>
#include <app/EventPathParams.h>
#include <app/RequiredPrivilege.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/util/IMClusterCommandHandler.h>
#include <app/util/af-types.h>
#include <app/util/attribute-storage.h>
#include <app/util/endpoint-config-api.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
// separated out for code-reuse
#include <app/ember_coupling/EventPathValidity.mixin.h>
#include <optional>
#include <variant>
namespace chip {
namespace app {
namespace {
/// Handles going through callback-based enumeration of generated/accepted commands
/// for CommandHandlerInterface based items.
///
/// Offers the ability to focus on some operation for finding a given
/// command id:
/// - FindFirst will return the first found element
/// - FindExact finds the element with the given id
/// - FindNext finds the element following the given id
class EnumeratorCommandFinder
{
public:
using HandlerCallbackFunction = CHIP_ERROR (CommandHandlerInterface::*)(const ConcreteClusterPath &,
CommandHandlerInterface::CommandIdCallback, void *);
enum class Operation
{
kFindFirst, // Find the first value in the list
kFindExact, // Find the given value
kFindNext // Find the value AFTER this value
};
EnumeratorCommandFinder(HandlerCallbackFunction callback) :
mCallback(callback), mOperation(Operation::kFindFirst), mTarget(kInvalidCommandId)
{}
/// Find the given command ID that matches the given operation/path.
///
/// If operation is kFindFirst, then path commandID is ignored. Otherwise it is used as a key to
/// kFindExact or kFindNext.
///
/// Returns:
/// - std::nullopt if no command found using the command handler interface
/// - kInvalidCommandId if the find failed (but command handler interface does provide a list)
/// - valid id if command handler interface usage succeeds
std::optional<CommandId> FindCommandId(Operation operation, const ConcreteCommandPath & path);
/// Uses FindCommandId to find the given command and loads the command entry data
std::optional<DataModel::CommandEntry> FindCommandEntry(Operation operation, const ConcreteCommandPath & path);
private:
HandlerCallbackFunction mCallback;
Operation mOperation;
CommandId mTarget;
std::optional<CommandId> mFound = std::nullopt;
Loop HandlerCallback(CommandId id)
{
switch (mOperation)
{
case Operation::kFindFirst:
mFound = id;
return Loop::Break;
case Operation::kFindExact:
if (mTarget == id)
{
mFound = id; // found it
return Loop::Break;
}
break;
case Operation::kFindNext:
if (mTarget == id)
{
// Once we found the ID, get the first
mOperation = Operation::kFindFirst;
}
break;
}
return Loop::Continue; // keep searching
}
static Loop HandlerCallbackFn(CommandId id, void * context)
{
auto self = static_cast<EnumeratorCommandFinder *>(context);
return self->HandlerCallback(id);
}
};
std::optional<CommandId> EnumeratorCommandFinder::FindCommandId(Operation operation, const ConcreteCommandPath & path)
{
mOperation = operation;
mTarget = path.mCommandId;
CommandHandlerInterface * interface =
CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId);
if (interface == nullptr)
{
return std::nullopt; // no data: no interface
}
CHIP_ERROR err = (interface->*mCallback)(path, HandlerCallbackFn, this);
if (err == CHIP_ERROR_NOT_IMPLEMENTED)
{
return std::nullopt; // no data provided by the interface
}
if (err != CHIP_NO_ERROR)
{
// Report the error here since we lose actual error. This generally should NOT be possible as CommandHandlerInterface
// usually returns unimplemented or should just work for our use case (our callback never fails)
ChipLogError(DataManagement, "Enumerate error: %" CHIP_ERROR_FORMAT, err.Format());
return kInvalidCommandId;
}
return mFound.value_or(kInvalidCommandId);
}
/// Load the cluster information into the specified destination
std::variant<CHIP_ERROR, DataModel::ClusterInfo> LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster)
{
DataVersion * versionPtr = emberAfDataVersionStorage(path);
if (versionPtr == nullptr)
{
ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast<int>(path.mEndpointId),
ChipLogValueMEI(cluster.clusterId));
return CHIP_ERROR_NOT_FOUND;
}
DataModel::ClusterInfo info(*versionPtr);
// TODO: set entry flags:
// info->flags.Set(ClusterQualityFlags::kDiagnosticsData)
return info;
}
/// Converts a EmberAfCluster into a ClusterEntry
std::variant<CHIP_ERROR, DataModel::ClusterEntry> ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster)
{
ConcreteClusterPath clusterPath(endpointId, cluster.clusterId);
auto info = LoadClusterInfo(clusterPath, cluster);
if (CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&info))
{
return *err;
}
if (DataModel::ClusterInfo * infoValue = std::get_if<DataModel::ClusterInfo>(&info))
{
return DataModel::ClusterEntry{
.path = clusterPath,
.info = *infoValue,
};
}
return CHIP_ERROR_INCORRECT_STATE;
}
/// Finds the first server cluster entry for the given endpoint data starting at [start_index]
///
/// Returns an invalid entry if no more server clusters are found
DataModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, unsigned start_index,
unsigned & found_index)
{
for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++)
{
const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
if (!cluster.IsServer())
{
continue;
}
found_index = cluster_idx;
auto entry = ClusterEntryFrom(endpointId, cluster);
if (DataModel::ClusterEntry * entryValue = std::get_if<DataModel::ClusterEntry>(&entry))
{
return *entryValue;
}
#if CHIP_ERROR_LOGGING
if (CHIP_ERROR * errValue = std::get_if<CHIP_ERROR>(&entry))
{
ChipLogError(AppServer, "Failed to load cluster entry: %" CHIP_ERROR_FORMAT, errValue->Format());
}
else
{
// Should NOT be possible: entryFrom has only 2 variants
ChipLogError(AppServer, "Failed to load cluster entry, UNKNOWN entry return type");
}
#endif
}
return DataModel::ClusterEntry::kInvalid;
}
/// Load the attribute information into the specified destination
///
/// `info` is assumed to be default-constructed/clear (i.e. this sets flags, but does not reset them).
void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute,
DataModel::AttributeInfo * info)
{
info->readPrivilege = RequiredPrivilege::ForReadAttribute(path);
if (!attribute.IsReadOnly())
{
info->writePrivilege = RequiredPrivilege::ForWriteAttribute(path);
}
info->flags.Set(DataModel::AttributeQualityFlags::kListAttribute, (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE));
info->flags.Set(DataModel::AttributeQualityFlags::kTimed, attribute.MustUseTimedWrite());
// NOTE: we do NOT provide additional info for:
// - IsExternal/IsSingleton/IsAutomaticallyPersisted is not used by IM handling
// - IsSingleton spec defines it for CLUSTERS where as we have it for ATTRIBUTES
// - Several specification flags are not available (reportable, quieter reporting,
// fixed, source attribution)
// TODO: Set additional flags:
// info->flags.Set(DataModel::AttributeQualityFlags::kFabricScoped)
// info->flags.Set(DataModel::AttributeQualityFlags::kFabricSensitive)
// info->flags.Set(DataModel::AttributeQualityFlags::kChangesOmitted)
}
DataModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, const EmberAfAttributeMetadata & attribute)
{
DataModel::AttributeEntry entry;
entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId);
LoadAttributeInfo(entry.path, attribute, &entry.info);
return entry;
}
DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath, CommandId clusterCommandId)
{
DataModel::CommandEntry entry;
entry.path = ConcreteCommandPath(clusterPath.mEndpointId, clusterPath.mClusterId, clusterCommandId);
entry.info.invokePrivilege = RequiredPrivilege::ForInvokeCommand(entry.path);
entry.info.flags.Set(DataModel::CommandQualityFlags::kTimed, CommandNeedsTimedInvoke(clusterPath.mClusterId, clusterCommandId));
entry.info.flags.Set(DataModel::CommandQualityFlags::kFabricScoped,
CommandIsFabricScoped(clusterPath.mClusterId, clusterCommandId));
return entry;
}
std::optional<DataModel::CommandEntry> EnumeratorCommandFinder::FindCommandEntry(Operation operation,
const ConcreteCommandPath & path)
{
std::optional<CommandId> id = FindCommandId(operation, path);
if (!id.has_value())
{
return std::nullopt;
}
return (*id == kInvalidCommandId) ? DataModel::CommandEntry::kInvalid : CommandEntryFrom(path, *id);
}
// TODO: DeviceTypeEntry content is IDENTICAL to EmberAfDeviceType, so centralizing
// to a common type is probably better. Need to figure out dependencies since
// this would make ember return datamodel-provider types.
// See: https://github.com/project-chip/connectedhomeip/issues/35889
DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & other)
{
DataModel::DeviceTypeEntry entry;
entry.deviceTypeId = other.deviceId;
entry.deviceTypeVersion = other.deviceVersion;
return entry;
}
// Explicitly compare for identical entries. note that types are different,
// so you must do `a == b` and the `b == a` will not work.
bool operator==(const DataModel::DeviceTypeEntry & a, const EmberAfDeviceType & b)
{
return (a.deviceTypeId == b.deviceId) && (a.deviceTypeVersion == b.deviceVersion);
}
/// Find the `index` where one of the following holds:
/// - types[index - 1] == previous OR
/// - index == types.size() // i.e. not found or there is no next
///
/// hintWherePreviousMayBe represents a search hint where previous may exist.
unsigned FindNextDeviceTypeIndex(Span<const EmberAfDeviceType> types, const DataModel::DeviceTypeEntry & previous,
unsigned hintWherePreviousMayBe)
{
if (hintWherePreviousMayBe < types.size())
{
// this is a valid hint ... see if we are lucky
if (previous == types[hintWherePreviousMayBe])
{
return hintWherePreviousMayBe + 1; // return the next index
}
}
// hint was not useful. We have to do a full search
for (unsigned idx = 0; idx < types.size(); idx++)
{
if (previous == types[idx])
{
return idx + 1;
}
}
// cast should be safe as we know we do not have that many types
return static_cast<unsigned>(types.size());
}
const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);
} // namespace
std::optional<CommandId> CodegenDataModelProvider::EmberCommandListIterator::First(const CommandId * list)
{
VerifyOrReturnValue(list != nullptr, std::nullopt);
mCurrentList = mCurrentHint = list;
VerifyOrReturnValue(*mCurrentList != kInvalidCommandId, std::nullopt);
return *mCurrentList;
}
std::optional<CommandId> CodegenDataModelProvider::EmberCommandListIterator::Next(const CommandId * list, CommandId previousId)
{
VerifyOrReturnValue(list != nullptr, std::nullopt);
VerifyOrReturnValue(previousId != kInvalidCommandId, std::nullopt);
if (mCurrentList != list)
{
// invalidate the hint if switching lists...
mCurrentHint = nullptr;
mCurrentList = list;
}
if ((mCurrentHint == nullptr) || (*mCurrentHint != previousId))
{
// we did not find a usable hint. Search from the to set the hint
mCurrentHint = mCurrentList;
while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != previousId))
{
mCurrentHint++;
}
}
VerifyOrReturnValue(*mCurrentHint == previousId, std::nullopt);
// hint is valid and can be used immediately
mCurrentHint++; // this is the next value
return (*mCurrentHint == kInvalidCommandId) ? std::nullopt : std::make_optional(*mCurrentHint);
}
bool CodegenDataModelProvider::EmberCommandListIterator::Exists(const CommandId * list, CommandId toCheck)
{
VerifyOrReturnValue(list != nullptr, false);
VerifyOrReturnValue(toCheck != kInvalidCommandId, false);
if (mCurrentList != list)
{
// invalidate the hint if switching lists...
mCurrentHint = nullptr;
mCurrentList = list;
}
// maybe already positioned correctly
if ((mCurrentHint != nullptr) && (*mCurrentHint == toCheck))
{
return true;
}
// move and try to find it
mCurrentHint = mCurrentList;
while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != toCheck))
{
mCurrentHint++;
}
return (*mCurrentHint == toCheck);
}
std::optional<DataModel::ActionReturnStatus> CodegenDataModelProvider::Invoke(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments,
CommandHandler * handler)
{
CommandHandlerInterface * handler_interface =
CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(request.path.mEndpointId, request.path.mClusterId);
if (handler_interface)
{
CommandHandlerInterface::HandlerContext context(*handler, request.path, input_arguments);
handler_interface->InvokeCommand(context);
// If the command was handled, don't proceed any further and return successfully.
if (context.mCommandHandled)
{
return std::nullopt;
}
}
// Ember always sets the return in the handler
DispatchSingleClusterCommand(request.path, input_arguments, handler);
return std::nullopt;
}
bool CodegenDataModelProvider::EndpointExists(EndpointId endpoint)
{
return (emberAfIndexFromEndpoint(endpoint) != kEmberInvalidEndpointIndex);
}
EndpointId CodegenDataModelProvider::FirstEndpoint()
{
// find the first enabled index
const uint16_t lastEndpointIndex = emberAfEndpointCount();
for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++)
{
if (emberAfEndpointIndexIsEnabled(endpoint_idx))
{
mEndpointIterationHint = endpoint_idx;
return emberAfEndpointFromIndex(endpoint_idx);
}
}
// No enabled endpoint found. Give up
return kInvalidEndpointId;
}
std::optional<unsigned> CodegenDataModelProvider::TryFindEndpointIndex(EndpointId id) const
{
const uint16_t lastEndpointIndex = emberAfEndpointCount();
if ((mEndpointIterationHint < lastEndpointIndex) && emberAfEndpointIndexIsEnabled(mEndpointIterationHint) &&
(id == emberAfEndpointFromIndex(mEndpointIterationHint)))
{
return std::make_optional(mEndpointIterationHint);
}
// Linear search, this may be slow
uint16_t idx = emberAfIndexFromEndpoint(id);
if (idx == kEmberInvalidEndpointIndex)
{
return std::nullopt;
}
return std::make_optional<unsigned>(idx);
}
EndpointId CodegenDataModelProvider::NextEndpoint(EndpointId before)
{
const uint16_t lastEndpointIndex = emberAfEndpointCount();
std::optional<unsigned> before_idx = TryFindEndpointIndex(before);
if (!before_idx.has_value())
{
return kInvalidEndpointId;
}
// find the first enabled index
for (uint16_t endpoint_idx = static_cast<uint16_t>(*before_idx + 1); endpoint_idx < lastEndpointIndex; endpoint_idx++)
{
if (emberAfEndpointIndexIsEnabled(endpoint_idx))
{
mEndpointIterationHint = endpoint_idx;
return emberAfEndpointFromIndex(endpoint_idx);
}
}
// No enabled enpoint after "before" was found, give up
return kInvalidEndpointId;
}
DataModel::ClusterEntry CodegenDataModelProvider::FirstCluster(EndpointId endpointId)
{
const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId);
VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid);
VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid);
VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid);
return FirstServerClusterEntry(endpointId, endpoint, 0, mClusterIterationHint);
}
std::optional<unsigned> CodegenDataModelProvider::TryFindServerClusterIndex(const EmberAfEndpointType * endpoint,
ClusterId id) const
{
const unsigned clusterCount = endpoint->clusterCount;
if (mClusterIterationHint < clusterCount)
{
const EmberAfCluster & cluster = endpoint->cluster[mClusterIterationHint];
if (cluster.IsServer() && (cluster.clusterId == id))
{
return std::make_optional(mClusterIterationHint);
}
}
// linear search, this may be slow
// does NOT use emberAfClusterIndex to not iterate over endpoints as we have
// already found the correct endpoint
for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++)
{
const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
if (cluster.IsServer() && (cluster.clusterId == id))
{
return std::make_optional(cluster_idx);
}
}
return std::nullopt;
}
DataModel::ClusterEntry CodegenDataModelProvider::NextCluster(const ConcreteClusterPath & before)
{
// TODO: This search still seems slow (ember will loop). Should use index hints as long
// as ember API supports it
const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId);
VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid);
VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid);
VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid);
std::optional<unsigned> cluster_idx = TryFindServerClusterIndex(endpoint, before.mClusterId);
if (!cluster_idx.has_value())
{
return DataModel::ClusterEntry::kInvalid;
}
return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mClusterIterationHint);
}
std::optional<DataModel::ClusterInfo> CodegenDataModelProvider::GetClusterInfo(const ConcreteClusterPath & path)
{
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, std::nullopt);
auto info = LoadClusterInfo(path, *cluster);
if (CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&info))
{
#if CHIP_ERROR_LOGGING
ChipLogError(AppServer, "Failed to load cluster info: %" CHIP_ERROR_FORMAT, err->Format());
#else
(void) err->Format();
#endif
return std::nullopt;
}
return std::make_optional(std::get<DataModel::ClusterInfo>(info));
}
DataModel::AttributeEntry CodegenDataModelProvider::FirstAttribute(const ConcreteClusterPath & path)
{
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid);
VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid);
VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid);
mAttributeIterationHint = 0;
return AttributeEntryFrom(path, cluster->attributes[0]);
}
std::optional<unsigned> CodegenDataModelProvider::TryFindAttributeIndex(const EmberAfCluster * cluster, AttributeId id) const
{
const unsigned attributeCount = cluster->attributeCount;
// attempt to find this based on the embedded hint
if ((mAttributeIterationHint < attributeCount) && (cluster->attributes[mAttributeIterationHint].attributeId == id))
{
return std::make_optional(mAttributeIterationHint);
}
// linear search is required. This may be slow
for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++)
{
if (cluster->attributes[attribute_idx].attributeId == id)
{
return std::make_optional(attribute_idx);
}
}
return std::nullopt;
}
const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const ConcreteClusterPath & path)
{
if (mPreviouslyFoundCluster.has_value() && (mPreviouslyFoundCluster->path == path) &&
(mEmberMetadataStructureGeneration == emberAfMetadataStructureGeneration()))
{
return mPreviouslyFoundCluster->cluster;
}
const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId);
if (cluster != nullptr)
{
mPreviouslyFoundCluster = std::make_optional<ClusterReference>(path, cluster);
mEmberMetadataStructureGeneration = emberAfMetadataStructureGeneration();
}
return cluster;
}
DataModel::AttributeEntry CodegenDataModelProvider::NextAttribute(const ConcreteAttributePath & before)
{
const EmberAfCluster * cluster = FindServerCluster(before);
VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid);
VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid);
VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid);
// find the given attribute in the list and then return the next one
std::optional<unsigned> attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId);
if (!attribute_idx.has_value())
{
return DataModel::AttributeEntry::kInvalid;
}
unsigned next_idx = *attribute_idx + 1;
if (next_idx < cluster->attributeCount)
{
mAttributeIterationHint = next_idx;
return AttributeEntryFrom(before, cluster->attributes[next_idx]);
}
// iteration complete
return DataModel::AttributeEntry::kInvalid;
}
std::optional<DataModel::AttributeInfo> CodegenDataModelProvider::GetAttributeInfo(const ConcreteAttributePath & path)
{
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, std::nullopt);
VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt);
VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt);
std::optional<unsigned> attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId);
if (!attribute_idx.has_value())
{
return std::nullopt;
}
DataModel::AttributeInfo info;
LoadAttributeInfo(path, cluster->attributes[*attribute_idx], &info);
return std::make_optional(info);
}
DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand(const ConcreteClusterPath & path)
{
auto handlerInterfaceValue = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateAcceptedCommands)
.FindCommandEntry(EnumeratorCommandFinder::Operation::kFindFirst,
ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId));
if (handlerInterfaceValue.has_value())
{
return *handlerInterfaceValue;
}
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, DataModel::CommandEntry::kInvalid);
std::optional<CommandId> commandId = mAcceptedCommandsIterator.First(cluster->acceptedCommandList);
VerifyOrReturnValue(commandId.has_value(), DataModel::CommandEntry::kInvalid);
return CommandEntryFrom(path, *commandId);
}
DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const ConcreteCommandPath & before)
{
// TODO: `Next` redirecting to a callback is slow O(n^2).
// see https://github.com/project-chip/connectedhomeip/issues/35790
auto handlerInterfaceValue = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateAcceptedCommands)
.FindCommandEntry(EnumeratorCommandFinder::Operation::kFindNext, before);
if (handlerInterfaceValue.has_value())
{
return *handlerInterfaceValue;
}
const EmberAfCluster * cluster = FindServerCluster(before);
VerifyOrReturnValue(cluster != nullptr, DataModel::CommandEntry::kInvalid);
std::optional<CommandId> commandId = mAcceptedCommandsIterator.Next(cluster->acceptedCommandList, before.mCommandId);
VerifyOrReturnValue(commandId.has_value(), DataModel::CommandEntry::kInvalid);
return CommandEntryFrom(before, *commandId);
}
std::optional<DataModel::CommandInfo> CodegenDataModelProvider::GetAcceptedCommandInfo(const ConcreteCommandPath & path)
{
auto handlerInterfaceValue = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateAcceptedCommands)
.FindCommandEntry(EnumeratorCommandFinder::Operation::kFindExact, path);
if (handlerInterfaceValue.has_value())
{
return handlerInterfaceValue->IsValid() ? std::make_optional(handlerInterfaceValue->info) : std::nullopt;
}
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, std::nullopt);
VerifyOrReturnValue(mAcceptedCommandsIterator.Exists(cluster->acceptedCommandList, path.mCommandId), std::nullopt);
return CommandEntryFrom(path, path.mCommandId).info;
}
ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand(const ConcreteClusterPath & path)
{
std::optional<CommandId> commandId =
EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateGeneratedCommands)
.FindCommandId(EnumeratorCommandFinder::Operation::kFindFirst,
ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId));
if (commandId.has_value())
{
return *commandId == kInvalidCommandId ? kInvalidCommandPath
: ConcreteCommandPath(path.mEndpointId, path.mClusterId, *commandId);
}
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, kInvalidCommandPath);
commandId = mGeneratedCommandsIterator.First(cluster->generatedCommandList);
VerifyOrReturnValue(commandId.has_value(), kInvalidCommandPath);
return ConcreteCommandPath(path.mEndpointId, path.mClusterId, *commandId);
}
ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const ConcreteCommandPath & before)
{
// TODO: `Next` redirecting to a callback is slow O(n^2).
// see https://github.com/project-chip/connectedhomeip/issues/35790
auto nextId = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateGeneratedCommands)
.FindCommandId(EnumeratorCommandFinder::Operation::kFindNext, before);
if (nextId.has_value())
{
return (*nextId == kInvalidCommandId) ? kInvalidCommandPath
: ConcreteCommandPath(before.mEndpointId, before.mClusterId, *nextId);
}
const EmberAfCluster * cluster = FindServerCluster(before);
VerifyOrReturnValue(cluster != nullptr, kInvalidCommandPath);
std::optional<CommandId> commandId = mGeneratedCommandsIterator.Next(cluster->generatedCommandList, before.mCommandId);
VerifyOrReturnValue(commandId.has_value(), kInvalidCommandPath);
return ConcreteCommandPath(before.mEndpointId, before.mClusterId, *commandId);
}
std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint)
{
// Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
// index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
// during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
//
// Not actually needed for `First`, however this makes First and Next consistent.
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
if (!endpoint_index.has_value())
{
return std::nullopt;
}
CHIP_ERROR err = CHIP_NO_ERROR;
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
if (deviceTypes.empty())
{
return std::nullopt;
}
// we start at the beginning
mDeviceTypeIterationHint = 0;
return DeviceTypeEntryFromEmber(deviceTypes[0]);
}
std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous)
{
// Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
// index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
// during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
if (!endpoint_index.has_value())
{
return std::nullopt;
}
CHIP_ERROR err = CHIP_NO_ERROR;
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
unsigned idx = FindNextDeviceTypeIndex(deviceTypes, previous, mDeviceTypeIterationHint);
if (idx >= deviceTypes.size())
{
return std::nullopt;
}
mDeviceTypeIterationHint = idx;
return DeviceTypeEntryFromEmber(deviceTypes[idx]);
}
bool CodegenDataModelProvider::EventPathIncludesAccessibleConcretePath(const EventPathParams & path,
const Access::SubjectDescriptor & descriptor)
{
if (!path.HasWildcardEndpointId())
{
// No need to check whether the endpoint is enabled, because
// emberAfFindEndpointType returns null for disabled endpoints.
return HasValidEventPathForEndpoint(path.mEndpointId, path, descriptor);
}
for (uint16_t endpointIndex = 0; endpointIndex < emberAfEndpointCount(); ++endpointIndex)
{
if (!emberAfEndpointIndexIsEnabled(endpointIndex))
{
continue;
}
if (HasValidEventPathForEndpoint(emberAfEndpointFromIndex(endpointIndex), path, descriptor))
{
return true;
}
}
return false;
}
} // namespace app
} // namespace chip