blob: b0815da48e0c085b6eecfbea18235ec7fff654c8 [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/data-model-provider/Provider.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>
#include <lib/support/CodeUtils.h>
#include <optional>
#include <variant>
namespace chip {
namespace app {
namespace detail {
Loop EnumeratorCommandFinder::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
}
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)
{
#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
// 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());
#endif
return kInvalidCommandId;
}
return mFound.value_or(kInvalidCommandId);
}
} // namespace detail
using detail::EnumeratorCommandFinder;
namespace {
const chip::CommandId * AcceptedCommands(const EmberAfCluster & cluster)
{
return cluster.acceptedCommandList;
}
const chip::CommandId * GeneratedCommands(const EmberAfCluster & cluster)
{
return cluster.generatedCommandList;
}
/// 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)
{
#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast<int>(path.mEndpointId),
ChipLogValueMEI(cluster.clusterId));
#endif
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 && CHIP_CONFIG_DATA_MODEL_EXTRA_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));
entry.info.flags.Set(DataModel::CommandQualityFlags::kLargeMessage,
CommandHasLargePayload(clusterPath.mClusterId, clusterCommandId));
return entry;
}
// 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 && CHIP_CONFIG_DATA_MODEL_EXTRA_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;
}
CommandId CodegenDataModelProvider::FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder,
detail::EnumeratorCommandFinder::Operation operation,
CodegenDataModelProvider::EmberCommandListIterator & emberIterator,
CommandListGetter commandListGetter)
{
std::optional<CommandId> handlerCommandId = handlerFinder.FindCommandId(operation, path);
if (handlerCommandId.has_value())
{
return *handlerCommandId;
}
const EmberAfCluster * cluster = FindServerCluster(path);
VerifyOrReturnValue(cluster != nullptr, kInvalidCommandId);
const CommandId * commandList = commandListGetter(*cluster);
switch (operation)
{
case EnumeratorCommandFinder::Operation::kFindFirst:
return emberIterator.First(commandList).value_or(kInvalidCommandId);
case EnumeratorCommandFinder::Operation::kFindNext:
return emberIterator.Next(commandList, path.mCommandId).value_or(kInvalidCommandId);
case EnumeratorCommandFinder::Operation::kFindExact:
default:
return emberIterator.Exists(commandList, path.mCommandId) ? path.mCommandId : kInvalidCommandId;
}
}
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)
{
EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
CommandId commandId =
FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder,
detail::EnumeratorCommandFinder::Operation::kFindFirst, mAcceptedCommandsIterator, AcceptedCommands);
VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid);
return CommandEntryFrom(path, commandId);
}
DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const ConcreteCommandPath & before)
{
EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext,
mAcceptedCommandsIterator, AcceptedCommands);
VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid);
return CommandEntryFrom(before, commandId);
}
std::optional<DataModel::CommandInfo> CodegenDataModelProvider::GetAcceptedCommandInfo(const ConcreteCommandPath & path)
{
EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
CommandId commandId = FindCommand(path, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindExact,
mAcceptedCommandsIterator, AcceptedCommands);
VerifyOrReturnValue(commandId != kInvalidCommandId, std::nullopt);
return CommandEntryFrom(path, commandId).info;
}
ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand(const ConcreteClusterPath & path)
{
EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands);
CommandId commandId =
FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder,
detail::EnumeratorCommandFinder::Operation::kFindFirst, mGeneratedCommandsIterator, GeneratedCommands);
VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath);
return ConcreteCommandPath(path.mEndpointId, path.mClusterId, commandId);
}
ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const ConcreteCommandPath & before)
{
EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands);
CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext,
mGeneratedCommandsIterator, GeneratedCommands);
VerifyOrReturnValue(commandId != kInvalidCommandId, 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]);
}
} // namespace app
} // namespace chip