/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // separated out for code-reuse #include #include #include 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 FindCommandId(Operation operation, const ConcreteCommandPath & path); /// Uses FindCommandId to find the given command and loads the command entry data std::optional FindCommandEntry(Operation operation, const ConcreteCommandPath & path); private: HandlerCallbackFunction mCallback; Operation mOperation; CommandId mTarget; std::optional 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(context); return self->HandlerCallback(id); } }; std::optional 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 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(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 ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) { ConcreteClusterPath clusterPath(endpointId, cluster.clusterId); auto info = LoadClusterInfo(clusterPath, cluster); if (CHIP_ERROR * err = std::get_if(&info)) { return *err; } if (DataModel::ClusterInfo * infoValue = std::get_if(&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(&entry)) { return *entryValue; } #if CHIP_ERROR_LOGGING if (CHIP_ERROR * errValue = std::get_if(&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 EnumeratorCommandFinder::FindCommandEntry(Operation operation, const ConcreteCommandPath & path) { std::optional 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 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(types.size()); } const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); } // namespace std::optional CodegenDataModelProvider::EmberCommandListIterator::First(const CommandId * list) { VerifyOrReturnValue(list != nullptr, std::nullopt); mCurrentList = mCurrentHint = list; VerifyOrReturnValue(*mCurrentList != kInvalidCommandId, std::nullopt); return *mCurrentList; } std::optional 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 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 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(idx); } EndpointId CodegenDataModelProvider::NextEndpoint(EndpointId before) { const uint16_t lastEndpointIndex = emberAfEndpointCount(); std::optional before_idx = TryFindEndpointIndex(before); if (!before_idx.has_value()) { return kInvalidEndpointId; } // find the first enabled index for (uint16_t endpoint_idx = static_cast(*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 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 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 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(&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(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 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(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 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 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 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 = 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 = mAcceptedCommandsIterator.Next(cluster->acceptedCommandList, before.mCommandId); VerifyOrReturnValue(commandId.has_value(), DataModel::CommandEntry::kInvalid); return CommandEntryFrom(before, *commandId); } std::optional 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 = 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 = mGeneratedCommandsIterator.Next(cluster->generatedCommandList, before.mCommandId); VerifyOrReturnValue(commandId.has_value(), kInvalidCommandPath); return ConcreteCommandPath(before.mEndpointId, before.mClusterId, *commandId); } std::optional 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 endpoint_index = TryFindEndpointIndex(endpoint); if (!endpoint_index.has_value()) { return std::nullopt; } CHIP_ERROR err = CHIP_NO_ERROR; Span deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err); if (deviceTypes.empty()) { return std::nullopt; } // we start at the beginning mDeviceTypeIterationHint = 0; return DeviceTypeEntryFromEmber(deviceTypes[0]); } std::optional 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 endpoint_index = TryFindEndpointIndex(endpoint); if (!endpoint_index.has_value()) { return std::nullopt; } CHIP_ERROR err = CHIP_NO_ERROR; Span 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