Matter SDK Coverage Report
Current view: top level - data-model-providers/codegen - CodegenDataModelProvider.cpp (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 96.2 % 390 375
Test Date: 2025-01-17 19:00:11 Functions: 100.0 % 49 49

            Line data    Source code
       1              : /*
       2              :  *    Copyright (c) 2024 Project CHIP Authors
       3              :  *    All rights reserved.
       4              :  *
       5              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       6              :  *    you may not use this file except in compliance with the License.
       7              :  *    You may obtain a copy of the License at
       8              :  *
       9              :  *        http://www.apache.org/licenses/LICENSE-2.0
      10              :  *
      11              :  *    Unless required by applicable law or agreed to in writing, software
      12              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      13              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      14              :  *    See the License for the specific language governing permissions and
      15              :  *    limitations under the License.
      16              :  */
      17              : #include <data-model-providers/codegen/CodegenDataModelProvider.h>
      18              : 
      19              : #include <access/AccessControl.h>
      20              : #include <app-common/zap-generated/attribute-type.h>
      21              : #include <app/CommandHandlerInterface.h>
      22              : #include <app/CommandHandlerInterfaceRegistry.h>
      23              : #include <app/ConcreteClusterPath.h>
      24              : #include <app/ConcreteCommandPath.h>
      25              : #include <app/EventPathParams.h>
      26              : #include <app/RequiredPrivilege.h>
      27              : #include <app/data-model-provider/MetadataTypes.h>
      28              : #include <app/data-model-provider/Provider.h>
      29              : #include <app/util/IMClusterCommandHandler.h>
      30              : #include <app/util/af-types.h>
      31              : #include <app/util/attribute-storage.h>
      32              : #include <app/util/endpoint-config-api.h>
      33              : #include <app/util/persistence/AttributePersistenceProvider.h>
      34              : #include <app/util/persistence/DefaultAttributePersistenceProvider.h>
      35              : #include <lib/core/CHIPError.h>
      36              : #include <lib/core/DataModelTypes.h>
      37              : #include <lib/support/CodeUtils.h>
      38              : #include <lib/support/SpanSearchValue.h>
      39              : 
      40              : #include <optional>
      41              : #include <variant>
      42              : 
      43              : namespace chip {
      44              : namespace app {
      45              : namespace detail {
      46              : 
      47           18 : Loop EnumeratorCommandFinder::HandlerCallback(CommandId id)
      48              : {
      49           18 :     switch (mOperation)
      50              :     {
      51            3 :     case Operation::kFindFirst:
      52            3 :         mFound = id;
      53            3 :         return Loop::Break;
      54           11 :     case Operation::kFindExact:
      55           11 :         if (mTarget == id)
      56              :         {
      57            7 :             mFound = id; // found it
      58            7 :             return Loop::Break;
      59              :         }
      60            4 :         break;
      61            4 :     case Operation::kFindNext:
      62            4 :         if (mTarget == id)
      63              :         {
      64              :             // Once we found the ID, get the first
      65            3 :             mOperation = Operation::kFindFirst;
      66              :         }
      67            4 :         break;
      68              :     }
      69            8 :     return Loop::Continue; // keep searching
      70              : }
      71              : 
      72          178 : std::optional<CommandId> EnumeratorCommandFinder::FindCommandId(Operation operation, const ConcreteCommandPath & path)
      73              : {
      74          178 :     mOperation = operation;
      75          178 :     mTarget    = path.mCommandId;
      76              : 
      77              :     CommandHandlerInterface * interface =
      78          178 :         CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId);
      79              : 
      80          178 :     if (interface == nullptr)
      81              :     {
      82          152 :         return std::nullopt; // no data: no interface
      83              :     }
      84              : 
      85           26 :     CHIP_ERROR err = (interface->*mCallback)(path, HandlerCallbackFn, this);
      86           26 :     if (err == CHIP_ERROR_NOT_IMPLEMENTED)
      87              :     {
      88            8 :         return std::nullopt; // no data provided by the interface
      89              :     }
      90              : 
      91           18 :     if (err != CHIP_NO_ERROR)
      92              :     {
      93              : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
      94              :         // Report the error here since we lose actual error. This generally should NOT be possible as CommandHandlerInterface
      95              :         // usually returns unimplemented or should just work for our use case (our callback never fails)
      96            0 :         ChipLogError(DataManagement, "Enumerate error: %" CHIP_ERROR_FORMAT, err.Format());
      97              : #endif
      98            0 :         return kInvalidCommandId;
      99              :     }
     100              : 
     101           18 :     return mFound.value_or(kInvalidCommandId);
     102              : }
     103              : 
     104              : } // namespace detail
     105              : 
     106              : using detail::EnumeratorCommandFinder;
     107              : 
     108              : namespace {
     109              : 
     110              : /// Search by device type within a span of EmberAfDeviceType (finds the device type that matches the given
     111              : /// DataModel::DeviceTypeEntry)
     112              : struct ByDeviceType
     113              : {
     114              :     using Key  = DataModel::DeviceTypeEntry;
     115              :     using Type = const EmberAfDeviceType;
     116            9 :     static Span<Type> GetSpan(Span<const EmberAfDeviceType> & data) { return data; }
     117            7 :     static bool HasKey(const Key & id, const Type & instance)
     118              :     {
     119            7 :         return (instance.deviceId == id.deviceTypeId) && (instance.deviceVersion == id.deviceTypeRevision);
     120              :     }
     121              : };
     122              : 
     123          112 : const CommandId * AcceptedCommands(const EmberAfCluster & cluster)
     124              : {
     125          112 :     return cluster.acceptedCommandList;
     126              : }
     127              : 
     128           35 : const CommandId * GeneratedCommands(const EmberAfCluster & cluster)
     129              : {
     130           35 :     return cluster.generatedCommandList;
     131              : }
     132              : 
     133              : /// Load the cluster information into the specified destination
     134         5221 : std::variant<CHIP_ERROR, DataModel::ClusterInfo> LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster)
     135              : {
     136         5221 :     DataVersion * versionPtr = emberAfDataVersionStorage(path);
     137         5221 :     if (versionPtr == nullptr)
     138              :     {
     139              : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     140            0 :         ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast<int>(path.mEndpointId),
     141              :                      ChipLogValueMEI(cluster.clusterId));
     142              : #endif
     143            0 :         return CHIP_ERROR_NOT_FOUND;
     144              :     }
     145              : 
     146         5221 :     DataModel::ClusterInfo info(*versionPtr);
     147              :     // TODO: set entry flags:
     148              :     //   info->flags.Set(ClusterQualityFlags::kDiagnosticsData)
     149         5221 :     return info;
     150              : }
     151              : 
     152              : /// Converts a EmberAfCluster into a ClusterEntry
     153          348 : std::variant<CHIP_ERROR, DataModel::ClusterEntry> ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster)
     154              : {
     155          348 :     ConcreteClusterPath clusterPath(endpointId, cluster.clusterId);
     156          348 :     auto info = LoadClusterInfo(clusterPath, cluster);
     157              : 
     158          348 :     if (CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&info))
     159              :     {
     160            0 :         return *err;
     161              :     }
     162              : 
     163          348 :     if (DataModel::ClusterInfo * infoValue = std::get_if<DataModel::ClusterInfo>(&info))
     164              :     {
     165          348 :         return DataModel::ClusterEntry{
     166              :             .path = clusterPath,
     167              :             .info = *infoValue,
     168          348 :         };
     169              :     }
     170            0 :     return CHIP_ERROR_INCORRECT_STATE;
     171              : }
     172              : 
     173              : /// Finds the first server cluster entry for the given endpoint data starting at [start_index]
     174              : ///
     175              : /// Returns an invalid entry if no more server clusters are found
     176          500 : DataModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, unsigned start_index,
     177              :                                                 unsigned & found_index)
     178              : {
     179         1893 :     for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++)
     180              :     {
     181         1741 :         const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
     182         1741 :         if (!cluster.IsServer())
     183              :         {
     184         1393 :             continue;
     185              :         }
     186              : 
     187          348 :         found_index = cluster_idx;
     188          348 :         auto entry  = ClusterEntryFrom(endpointId, cluster);
     189              : 
     190          348 :         if (DataModel::ClusterEntry * entryValue = std::get_if<DataModel::ClusterEntry>(&entry))
     191              :         {
     192          348 :             return *entryValue;
     193              :         }
     194              : 
     195              : #if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     196            0 :         if (CHIP_ERROR * errValue = std::get_if<CHIP_ERROR>(&entry))
     197              :         {
     198            0 :             ChipLogError(AppServer, "Failed to load cluster entry: %" CHIP_ERROR_FORMAT, errValue->Format());
     199              :         }
     200              :         else
     201              :         {
     202              :             // Should NOT be possible: entryFrom has only 2 variants
     203            0 :             ChipLogError(AppServer, "Failed to load cluster entry, UNKNOWN entry return type");
     204              :         }
     205              : #endif
     206              :     }
     207              : 
     208          152 :     return DataModel::ClusterEntry::kInvalid;
     209              : }
     210              : 
     211           26 : ClusterId FirstClientClusterId(const EmberAfEndpointType * endpoint, unsigned start_index, unsigned & found_index)
     212              : {
     213           50 :     for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++)
     214              :     {
     215           48 :         const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
     216           48 :         if (!cluster.IsClient())
     217              :         {
     218           24 :             continue;
     219              :         }
     220              : 
     221           24 :         found_index = cluster_idx;
     222           24 :         return cluster.clusterId;
     223              :     }
     224              : 
     225            2 :     return kInvalidClusterId;
     226              : }
     227              : 
     228              : /// Load the attribute information into the specified destination
     229              : ///
     230              : /// `info` is assumed to be default-constructed/clear (i.e. this sets flags, but does not reset them).
     231         7224 : void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute,
     232              :                        DataModel::AttributeInfo * info)
     233              : {
     234         7224 :     info->readPrivilege = RequiredPrivilege::ForReadAttribute(path);
     235         7224 :     if (!attribute.IsReadOnly())
     236              :     {
     237         3547 :         info->writePrivilege = RequiredPrivilege::ForWriteAttribute(path);
     238              :     }
     239              : 
     240         7224 :     info->flags.Set(DataModel::AttributeQualityFlags::kListAttribute, (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE));
     241         7224 :     info->flags.Set(DataModel::AttributeQualityFlags::kTimed, attribute.MustUseTimedWrite());
     242              : 
     243              :     // NOTE: we do NOT provide additional info for:
     244              :     //    - IsExternal/IsSingleton/IsAutomaticallyPersisted is not used by IM handling
     245              :     //    - IsSingleton spec defines it for CLUSTERS where as we have it for ATTRIBUTES
     246              :     //    - Several specification flags are not available (reportable, quieter reporting,
     247              :     //      fixed, source attribution)
     248              : 
     249              :     // TODO: Set additional flags:
     250              :     // info->flags.Set(DataModel::AttributeQualityFlags::kFabricScoped)
     251              :     // info->flags.Set(DataModel::AttributeQualityFlags::kFabricSensitive)
     252              :     // info->flags.Set(DataModel::AttributeQualityFlags::kChangesOmitted)
     253         7224 : }
     254              : 
     255         2555 : DataModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, const EmberAfAttributeMetadata & attribute)
     256              : {
     257         2555 :     DataModel::AttributeEntry entry;
     258              : 
     259         2555 :     entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId);
     260         2555 :     LoadAttributeInfo(entry.path, attribute, &entry.info);
     261              : 
     262         2555 :     return entry;
     263              : }
     264              : 
     265          101 : DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath, CommandId clusterCommandId)
     266              : {
     267          101 :     DataModel::CommandEntry entry;
     268          101 :     entry.path                 = ConcreteCommandPath(clusterPath.mEndpointId, clusterPath.mClusterId, clusterCommandId);
     269          101 :     entry.info.invokePrivilege = RequiredPrivilege::ForInvokeCommand(entry.path);
     270              : 
     271          101 :     entry.info.flags.Set(DataModel::CommandQualityFlags::kTimed, CommandNeedsTimedInvoke(clusterPath.mClusterId, clusterCommandId));
     272              : 
     273          101 :     entry.info.flags.Set(DataModel::CommandQualityFlags::kFabricScoped,
     274          101 :                          CommandIsFabricScoped(clusterPath.mClusterId, clusterCommandId));
     275              : 
     276          101 :     entry.info.flags.Set(DataModel::CommandQualityFlags::kLargeMessage,
     277          101 :                          CommandHasLargePayload(clusterPath.mClusterId, clusterCommandId));
     278          101 :     return entry;
     279              : }
     280              : 
     281              : // TODO: DeviceTypeEntry content is IDENTICAL to EmberAfDeviceType, so centralizing
     282              : //       to a common type is probably better. Need to figure out dependencies since
     283              : //       this would make ember return datamodel-provider types.
     284              : //       See: https://github.com/project-chip/connectedhomeip/issues/35889
     285            9 : std::optional<DataModel::DeviceTypeEntry> DeviceTypeEntryFromEmber(const EmberAfDeviceType * other)
     286              : {
     287            9 :     if (other == nullptr)
     288              :     {
     289            4 :         return std::nullopt;
     290              :     }
     291              : 
     292           10 :     return DataModel::DeviceTypeEntry{
     293            5 :         .deviceTypeId       = other->deviceId,
     294            5 :         .deviceTypeRevision = other->deviceVersion,
     295            5 :     };
     296              : }
     297              : 
     298              : const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);
     299              : 
     300          153 : std::optional<DataModel::EndpointInfo> GetEndpointInfoAtIndex(uint16_t endpointIndex)
     301              : {
     302          153 :     VerifyOrReturnValue(emberAfEndpointIndexIsEnabled(endpointIndex), std::nullopt);
     303          153 :     EndpointId parent = emberAfParentEndpointFromIndex(endpointIndex);
     304          153 :     if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kFullFamily)
     305              :     {
     306          131 :         return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kFullFamily);
     307              :     }
     308           22 :     if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kTree)
     309              :     {
     310           22 :         return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kTree);
     311              :     }
     312            0 :     return std::nullopt;
     313              : }
     314              : 
     315          189 : DataModel::EndpointEntry FirstEndpointEntry(unsigned start_index, uint16_t & found_index)
     316              : {
     317              :     // find the first enabled index after the start index
     318          189 :     const uint16_t lastEndpointIndex = emberAfEndpointCount();
     319          212 :     for (uint16_t endpoint_idx = static_cast<uint16_t>(start_index); endpoint_idx < lastEndpointIndex; endpoint_idx++)
     320              :     {
     321          173 :         if (emberAfEndpointIndexIsEnabled(endpoint_idx))
     322              :         {
     323          150 :             found_index                            = endpoint_idx;
     324          150 :             DataModel::EndpointEntry endpointEntry = DataModel::EndpointEntry::kInvalid;
     325          150 :             endpointEntry.id                       = emberAfEndpointFromIndex(endpoint_idx);
     326          150 :             auto endpointInfo                      = GetEndpointInfoAtIndex(endpoint_idx);
     327              :             // The endpoint info should have value as this endpoint should be valid at this time
     328          150 :             VerifyOrDie(endpointInfo.has_value());
     329          150 :             endpointEntry.info = endpointInfo.value();
     330          150 :             return endpointEntry;
     331              :         }
     332              :     }
     333              : 
     334              :     // No enabled endpoint found. Give up
     335           39 :     return DataModel::EndpointEntry::kInvalid;
     336              : }
     337              : 
     338           11 : bool operator==(const DataModel::Provider::SemanticTag & tagA, const DataModel::Provider::SemanticTag & tagB)
     339              : {
     340              :     // Label is an optional and nullable value of CharSpan. Optional and Nullable have overload for ==,
     341              :     // But `==` is deleted for CharSpan. Here we only check whether the string is the same.
     342           11 :     if (tagA.label.HasValue() != tagB.label.HasValue())
     343              :     {
     344            3 :         return false;
     345              :     }
     346            8 :     if (tagA.label.HasValue())
     347              :     {
     348            8 :         if (tagA.label.Value().IsNull() != tagB.label.Value().IsNull())
     349              :         {
     350            0 :             return false;
     351              :         }
     352            8 :         if (!tagA.label.Value().IsNull())
     353              :         {
     354            8 :             if (!tagA.label.Value().Value().data_equal(tagB.label.Value().Value()))
     355              :             {
     356            3 :                 return false;
     357              :             }
     358              :         }
     359              :     }
     360            5 :     return (tagA.tag == tagB.tag) && (tagA.mfgCode == tagB.mfgCode) && (tagA.namespaceID == tagB.namespaceID);
     361              : }
     362              : 
     363            5 : std::optional<unsigned> FindNextSemanticTagIndex(EndpointId endpoint, const DataModel::Provider::SemanticTag & previous,
     364              :                                                  unsigned hintWherePreviousMayBe)
     365              : {
     366            5 :     DataModel::Provider::SemanticTag hintTag;
     367              :     // Check whether the hint is the previous tag
     368            5 :     if (GetSemanticTagForEndpointAtIndex(endpoint, hintWherePreviousMayBe, hintTag) == CHIP_NO_ERROR)
     369              :     {
     370            5 :         if (previous == hintTag)
     371              :         {
     372            2 :             return std::make_optional(hintWherePreviousMayBe + 1);
     373              :         }
     374              :     }
     375              :     // If the hint is not the previous tag, iterate over all the tags to find the index for the previous tag
     376            3 :     unsigned index = 0;
     377              :     // Ensure that the next index is in the range
     378           14 :     while (GetSemanticTagForEndpointAtIndex(endpoint, index + 1, hintTag) == CHIP_NO_ERROR &&
     379           14 :            GetSemanticTagForEndpointAtIndex(endpoint, index, hintTag) == CHIP_NO_ERROR)
     380              :     {
     381            6 :         if (previous == hintTag)
     382              :         {
     383            1 :             return std::make_optional(index + 1);
     384              :         }
     385            5 :         index++;
     386              :     }
     387            2 :     return std::nullopt;
     388            5 : }
     389              : 
     390              : DefaultAttributePersistenceProvider gDefaultAttributePersistence;
     391              : 
     392              : } // namespace
     393              : 
     394          162 : CHIP_ERROR CodegenDataModelProvider::Startup(DataModel::InteractionModelContext context)
     395              : {
     396          162 :     ReturnErrorOnFailure(DataModel::Provider::Startup(context));
     397              : 
     398              :     // Ember NVM requires have a data model provider. attempt to create one if one is not available
     399              :     //
     400              :     // It is not a critical failure to not have one, however if one is not set up, ember NVM operations
     401              :     // will error out with a `persistence not available`.
     402          162 :     if (GetAttributePersistenceProvider() == nullptr)
     403              :     {
     404              : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     405          162 :         ChipLogProgress(DataManagement, "Ember attribute persistence requires setting up");
     406              : #endif
     407          162 :         if (mPersistentStorageDelegate != nullptr)
     408              :         {
     409            1 :             ReturnErrorOnFailure(gDefaultAttributePersistence.Init(mPersistentStorageDelegate));
     410            1 :             SetAttributePersistenceProvider(&gDefaultAttributePersistence);
     411              : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     412              :         }
     413              :         else
     414              :         {
     415          161 :             ChipLogError(DataManagement, "No storage delegate available, will not set up attribute persistence.");
     416              : #endif
     417              :         }
     418              :     }
     419              : 
     420          162 :     return CHIP_NO_ERROR;
     421              : }
     422              : 
     423            6 : std::optional<CommandId> CodegenDataModelProvider::EmberCommandListIterator::First(const CommandId * list)
     424              : {
     425            6 :     VerifyOrReturnValue(list != nullptr, std::nullopt);
     426            4 :     mCurrentList = mCurrentHint = list;
     427              : 
     428            4 :     VerifyOrReturnValue(*mCurrentList != kInvalidCommandId, std::nullopt);
     429            4 :     return *mCurrentList;
     430              : }
     431              : 
     432           65 : std::optional<CommandId> CodegenDataModelProvider::EmberCommandListIterator::Next(const CommandId * list, CommandId previousId)
     433              : {
     434           65 :     VerifyOrReturnValue(list != nullptr, std::nullopt);
     435           65 :     VerifyOrReturnValue(previousId != kInvalidCommandId, std::nullopt);
     436              : 
     437           65 :     if (mCurrentList != list)
     438              :     {
     439              :         // invalidate the hint if switching lists...
     440            4 :         mCurrentHint = nullptr;
     441            4 :         mCurrentList = list;
     442              :     }
     443              : 
     444           65 :     if ((mCurrentHint == nullptr) || (*mCurrentHint != previousId))
     445              :     {
     446              :         // we did not find a usable hint. Search from the to set the hint
     447           59 :         mCurrentHint = mCurrentList;
     448           88 :         while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != previousId))
     449              :         {
     450           29 :             mCurrentHint++;
     451              :         }
     452              :     }
     453              : 
     454           65 :     VerifyOrReturnValue(*mCurrentHint == previousId, std::nullopt);
     455              : 
     456              :     // hint is valid and can be used immediately
     457           55 :     mCurrentHint++; // this is the next value
     458           55 :     return (*mCurrentHint == kInvalidCommandId) ? std::nullopt : std::make_optional(*mCurrentHint);
     459              : }
     460              : 
     461           76 : bool CodegenDataModelProvider::EmberCommandListIterator::Exists(const CommandId * list, CommandId toCheck)
     462              : {
     463           76 :     VerifyOrReturnValue(list != nullptr, false);
     464           73 :     VerifyOrReturnValue(toCheck != kInvalidCommandId, false);
     465              : 
     466           73 :     if (mCurrentList != list)
     467              :     {
     468              :         // invalidate the hint if switching lists...
     469            5 :         mCurrentHint = nullptr;
     470            5 :         mCurrentList = list;
     471              :     }
     472              : 
     473              :     // maybe already positioned correctly
     474           73 :     if ((mCurrentHint != nullptr) && (*mCurrentHint == toCheck))
     475              :     {
     476           57 :         return true;
     477              :     }
     478              : 
     479              :     // move and try to find it
     480           16 :     mCurrentHint = mCurrentList;
     481           38 :     while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != toCheck))
     482              :     {
     483           22 :         mCurrentHint++;
     484              :     }
     485              : 
     486           16 :     return (*mCurrentHint == toCheck);
     487              : }
     488              : 
     489            5 : std::optional<DataModel::ActionReturnStatus> CodegenDataModelProvider::Invoke(const DataModel::InvokeRequest & request,
     490              :                                                                               TLV::TLVReader & input_arguments,
     491              :                                                                               CommandHandler * handler)
     492              : {
     493              :     CommandHandlerInterface * handler_interface =
     494            5 :         CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(request.path.mEndpointId, request.path.mClusterId);
     495              : 
     496            5 :     if (handler_interface)
     497              :     {
     498            3 :         CommandHandlerInterface::HandlerContext context(*handler, request.path, input_arguments);
     499            3 :         handler_interface->InvokeCommand(context);
     500              : 
     501              :         // If the command was handled, don't proceed any further and return successfully.
     502            3 :         if (context.mCommandHandled)
     503              :         {
     504            3 :             return std::nullopt;
     505              :         }
     506              :     }
     507              : 
     508              :     // Ember always sets the return in the handler
     509            2 :     DispatchSingleClusterCommand(request.path, input_arguments, handler);
     510            2 :     return std::nullopt;
     511              : }
     512              : 
     513            2 : bool CodegenDataModelProvider::EndpointExists(EndpointId endpoint)
     514              : {
     515            2 :     return (emberAfIndexFromEndpoint(endpoint) != kEmberInvalidEndpointIndex);
     516              : }
     517              : 
     518            5 : std::optional<DataModel::EndpointInfo> CodegenDataModelProvider::GetEndpointInfo(EndpointId endpoint)
     519              : {
     520            5 :     std::optional<unsigned> endpoint_idx = TryFindEndpointIndex(endpoint);
     521            5 :     if (endpoint_idx.has_value())
     522              :     {
     523            3 :         return GetEndpointInfoAtIndex(static_cast<uint16_t>(*endpoint_idx));
     524              :     }
     525            2 :     return std::nullopt;
     526              : }
     527              : 
     528           55 : DataModel::EndpointEntry CodegenDataModelProvider::FirstEndpoint()
     529              : {
     530           55 :     return FirstEndpointEntry(0, mEndpointIterationHint);
     531              : }
     532              : 
     533          150 : std::optional<unsigned> CodegenDataModelProvider::TryFindEndpointIndex(EndpointId id) const
     534              : {
     535          150 :     const uint16_t lastEndpointIndex = emberAfEndpointCount();
     536              : 
     537          300 :     if ((mEndpointIterationHint < lastEndpointIndex) && emberAfEndpointIndexIsEnabled(mEndpointIterationHint) &&
     538          150 :         (id == emberAfEndpointFromIndex(mEndpointIterationHint)))
     539              :     {
     540          133 :         return std::make_optional(mEndpointIterationHint);
     541              :     }
     542              : 
     543              :     // Linear search, this may be slow
     544           17 :     uint16_t idx = emberAfIndexFromEndpoint(id);
     545           17 :     if (idx == kEmberInvalidEndpointIndex)
     546              :     {
     547            4 :         return std::nullopt;
     548              :     }
     549              : 
     550           13 :     return std::make_optional<unsigned>(idx);
     551              : }
     552              : 
     553          136 : DataModel::EndpointEntry CodegenDataModelProvider::NextEndpoint(EndpointId before)
     554              : {
     555          136 :     std::optional<unsigned> before_idx = TryFindEndpointIndex(before);
     556          136 :     if (!before_idx.has_value())
     557              :     {
     558            2 :         return DataModel::EndpointEntry::kInvalid;
     559              :     }
     560          134 :     return FirstEndpointEntry(*before_idx + 1, mEndpointIterationHint);
     561              : }
     562              : 
     563          244 : DataModel::ClusterEntry CodegenDataModelProvider::FirstServerCluster(EndpointId endpointId)
     564              : {
     565          244 :     const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId);
     566          244 :     VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid);
     567          242 :     VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid);
     568          242 :     VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid);
     569              : 
     570          242 :     return FirstServerClusterEntry(endpointId, endpoint, 0, mServerClusterIterationHint);
     571              : }
     572              : 
     573          276 : std::optional<unsigned> CodegenDataModelProvider::TryFindClusterIndex(const EmberAfEndpointType * endpoint, ClusterId id,
     574              :                                                                       ClusterSide side) const
     575              : {
     576          276 :     const unsigned clusterCount = endpoint->clusterCount;
     577          276 :     unsigned hint               = side == ClusterSide::kServer ? mServerClusterIterationHint : mClientClusterIterationHint;
     578              : 
     579          276 :     if (hint < clusterCount)
     580              :     {
     581          276 :         const EmberAfCluster & cluster = endpoint->cluster[hint];
     582          276 :         if (((side == ClusterSide::kServer) && cluster.IsServer()) || ((side == ClusterSide::kClient) && cluster.IsClient()))
     583              :         {
     584          274 :             if (cluster.clusterId == id)
     585              :             {
     586          254 :                 return std::make_optional(hint);
     587              :             }
     588              :         }
     589              :     }
     590              : 
     591              :     // linear search, this may be slow
     592              :     // does NOT use emberAfClusterIndex to not iterate over endpoints as we have
     593              :     // already found the correct endpoint
     594           56 :     for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++)
     595              :     {
     596           52 :         const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
     597           52 :         if (((side == ClusterSide::kServer) && !cluster.IsServer()) || ((side == ClusterSide::kClient) && !cluster.IsClient()))
     598              :         {
     599           26 :             continue;
     600              :         }
     601           26 :         if (cluster.clusterId == id)
     602              :         {
     603           18 :             return std::make_optional(cluster_idx);
     604              :         }
     605              :     }
     606              : 
     607            4 :     return std::nullopt;
     608              : }
     609              : 
     610          261 : DataModel::ClusterEntry CodegenDataModelProvider::NextServerCluster(const ConcreteClusterPath & before)
     611              : {
     612              :     // TODO: This search still seems slow (ember will loop). Should use index hints as long
     613              :     //       as ember API supports it
     614          261 :     const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId);
     615              : 
     616          261 :     VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid);
     617          260 :     VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid);
     618          260 :     VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid);
     619              : 
     620          260 :     std::optional<unsigned> cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kServer);
     621          260 :     if (!cluster_idx.has_value())
     622              :     {
     623            2 :         return DataModel::ClusterEntry::kInvalid;
     624              :     }
     625              : 
     626          258 :     return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mServerClusterIterationHint);
     627              : }
     628              : 
     629         5005 : std::optional<DataModel::ClusterInfo> CodegenDataModelProvider::GetServerClusterInfo(const ConcreteClusterPath & path)
     630              : {
     631         5005 :     const EmberAfCluster * cluster = FindServerCluster(path);
     632              : 
     633         5005 :     VerifyOrReturnValue(cluster != nullptr, std::nullopt);
     634              : 
     635         4873 :     auto info = LoadClusterInfo(path, *cluster);
     636              : 
     637         4873 :     if (CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&info))
     638              :     {
     639              : #if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     640            0 :         ChipLogError(AppServer, "Failed to load cluster info: %" CHIP_ERROR_FORMAT, err->Format());
     641              : #else
     642              :         (void) err->Format();
     643              : #endif
     644            0 :         return std::nullopt;
     645              :     }
     646              : 
     647         4873 :     return std::make_optional(std::get<DataModel::ClusterInfo>(info));
     648              : }
     649              : 
     650           14 : ConcreteClusterPath CodegenDataModelProvider::FirstClientCluster(EndpointId endpointId)
     651              : {
     652           14 :     const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId);
     653           14 :     VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId));
     654           12 :     VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(endpointId, kInvalidClusterId));
     655           12 :     VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId));
     656              : 
     657           12 :     return ConcreteClusterPath(endpointId, FirstClientClusterId(endpoint, 0, mClientClusterIterationHint));
     658              : }
     659              : 
     660           17 : ConcreteClusterPath CodegenDataModelProvider::NextClientCluster(const ConcreteClusterPath & before)
     661              : {
     662              :     // TODO: This search still seems slow (ember will loop). Should use index hints as long
     663              :     //       as ember API supports it
     664           17 :     const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId);
     665              : 
     666           17 :     VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId));
     667           16 :     VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId));
     668           16 :     VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId));
     669              : 
     670           16 :     std::optional<unsigned> cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kClient);
     671           16 :     if (!cluster_idx.has_value())
     672              :     {
     673            2 :         return ConcreteClusterPath(before.mEndpointId, kInvalidClusterId);
     674              :     }
     675              : 
     676           14 :     return ConcreteClusterPath(before.mEndpointId, FirstClientClusterId(endpoint, *cluster_idx + 1, mClientClusterIterationHint));
     677              : }
     678              : 
     679          751 : DataModel::AttributeEntry CodegenDataModelProvider::FirstAttribute(const ConcreteClusterPath & path)
     680              : {
     681          751 :     const EmberAfCluster * cluster = FindServerCluster(path);
     682              : 
     683          751 :     VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid);
     684          747 :     VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid);
     685          747 :     VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid);
     686              : 
     687          747 :     mAttributeIterationHint = 0;
     688          747 :     return AttributeEntryFrom(path, cluster->attributes[0]);
     689              : }
     690              : 
     691         8155 : std::optional<unsigned> CodegenDataModelProvider::TryFindAttributeIndex(const EmberAfCluster * cluster, AttributeId id) const
     692              : {
     693         8155 :     const unsigned attributeCount = cluster->attributeCount;
     694              : 
     695              :     // attempt to find this based on the embedded hint
     696         8155 :     if ((mAttributeIterationHint < attributeCount) && (cluster->attributes[mAttributeIterationHint].attributeId == id))
     697              :     {
     698         5062 :         return std::make_optional(mAttributeIterationHint);
     699              :     }
     700              : 
     701              :     // linear search is required. This may be slow
     702        12289 :     for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++)
     703              :     {
     704              : 
     705        11059 :         if (cluster->attributes[attribute_idx].attributeId == id)
     706              :         {
     707         1863 :             return std::make_optional(attribute_idx);
     708              :         }
     709              :     }
     710              : 
     711         1230 :     return std::nullopt;
     712              : }
     713              : 
     714        14467 : const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const ConcreteClusterPath & path)
     715              : {
     716        28063 :     if (mPreviouslyFoundCluster.has_value() && (mPreviouslyFoundCluster->path == path) &&
     717        13596 :         (mEmberMetadataStructureGeneration == emberAfMetadataStructureGeneration()))
     718              : 
     719              :     {
     720        13506 :         return mPreviouslyFoundCluster->cluster;
     721              :     }
     722              : 
     723          961 :     const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId);
     724          961 :     if (cluster != nullptr)
     725              :     {
     726          416 :         mPreviouslyFoundCluster           = std::make_optional<ClusterReference>(path, cluster);
     727          416 :         mEmberMetadataStructureGeneration = emberAfMetadataStructureGeneration();
     728              :     }
     729          961 :     return cluster;
     730              : }
     731              : 
     732          178 : CommandId CodegenDataModelProvider::FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder,
     733              :                                                 detail::EnumeratorCommandFinder::Operation operation,
     734              :                                                 CodegenDataModelProvider::EmberCommandListIterator & emberIterator,
     735              :                                                 CommandListGetter commandListGetter)
     736              : {
     737              : 
     738          178 :     std::optional<CommandId> handlerCommandId = handlerFinder.FindCommandId(operation, path);
     739          178 :     if (handlerCommandId.has_value())
     740              :     {
     741           18 :         return *handlerCommandId;
     742              :     }
     743              : 
     744          160 :     const EmberAfCluster * cluster = FindServerCluster(path);
     745          160 :     VerifyOrReturnValue(cluster != nullptr, kInvalidCommandId);
     746              : 
     747          147 :     const CommandId * commandList = commandListGetter(*cluster);
     748              : 
     749          147 :     switch (operation)
     750              :     {
     751            6 :     case EnumeratorCommandFinder::Operation::kFindFirst:
     752            6 :         return emberIterator.First(commandList).value_or(kInvalidCommandId);
     753           65 :     case EnumeratorCommandFinder::Operation::kFindNext:
     754           65 :         return emberIterator.Next(commandList, path.mCommandId).value_or(kInvalidCommandId);
     755           76 :     case EnumeratorCommandFinder::Operation::kFindExact:
     756              :     default:
     757           76 :         return emberIterator.Exists(commandList, path.mCommandId) ? path.mCommandId : kInvalidCommandId;
     758              :     }
     759              : }
     760              : 
     761         2261 : DataModel::AttributeEntry CodegenDataModelProvider::NextAttribute(const ConcreteAttributePath & before)
     762              : {
     763         2261 :     const EmberAfCluster * cluster = FindServerCluster(before);
     764         2261 :     VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid);
     765         2257 :     VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid);
     766         2257 :     VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid);
     767              : 
     768              :     // find the given attribute in the list and then return the next one
     769         2257 :     std::optional<unsigned> attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId);
     770         2257 :     if (!attribute_idx.has_value())
     771              :     {
     772            1 :         return DataModel::AttributeEntry::kInvalid;
     773              :     }
     774              : 
     775         2256 :     unsigned next_idx = *attribute_idx + 1;
     776         2256 :     if (next_idx < cluster->attributeCount)
     777              :     {
     778         1808 :         mAttributeIterationHint = next_idx;
     779         1808 :         return AttributeEntryFrom(before, cluster->attributes[next_idx]);
     780              :     }
     781              : 
     782              :     // iteration complete
     783          448 :     return DataModel::AttributeEntry::kInvalid;
     784              : }
     785              : 
     786         6290 : std::optional<DataModel::AttributeInfo> CodegenDataModelProvider::GetAttributeInfo(const ConcreteAttributePath & path)
     787              : {
     788         6290 :     const EmberAfCluster * cluster = FindServerCluster(path);
     789              : 
     790         6290 :     VerifyOrReturnValue(cluster != nullptr, std::nullopt);
     791         5898 :     VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt);
     792         5898 :     VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt);
     793              : 
     794         5898 :     std::optional<unsigned> attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId);
     795              : 
     796         5898 :     if (!attribute_idx.has_value())
     797              :     {
     798         1229 :         return std::nullopt;
     799              :     }
     800              : 
     801         4669 :     DataModel::AttributeInfo info;
     802         4669 :     LoadAttributeInfo(path, cluster->attributes[*attribute_idx], &info);
     803         4669 :     return std::make_optional(info);
     804              : }
     805              : 
     806            9 : DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand(const ConcreteClusterPath & path)
     807              : {
     808            9 :     EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
     809              : 
     810              :     CommandId commandId =
     811            9 :         FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder,
     812            9 :                     detail::EnumeratorCommandFinder::Operation::kFindFirst, mAcceptedCommandsIterator, AcceptedCommands);
     813              : 
     814            9 :     VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid);
     815            3 :     return CommandEntryFrom(path, commandId);
     816              : }
     817              : 
     818           35 : DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const ConcreteCommandPath & before)
     819              : {
     820              : 
     821           35 :     EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
     822           35 :     CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext,
     823           35 :                                       mAcceptedCommandsIterator, AcceptedCommands);
     824              : 
     825           35 :     VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid);
     826           23 :     return CommandEntryFrom(before, commandId);
     827              : }
     828              : 
     829           92 : std::optional<DataModel::CommandInfo> CodegenDataModelProvider::GetAcceptedCommandInfo(const ConcreteCommandPath & path)
     830              : {
     831              : 
     832           92 :     EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
     833           92 :     CommandId commandId = FindCommand(path, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindExact,
     834           92 :                                       mAcceptedCommandsIterator, AcceptedCommands);
     835              : 
     836           92 :     VerifyOrReturnValue(commandId != kInvalidCommandId, std::nullopt);
     837           75 :     return CommandEntryFrom(path, commandId).info;
     838              : }
     839              : 
     840            9 : ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand(const ConcreteClusterPath & path)
     841              : {
     842            9 :     EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands);
     843              :     CommandId commandId =
     844            9 :         FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder,
     845            9 :                     detail::EnumeratorCommandFinder::Operation::kFindFirst, mGeneratedCommandsIterator, GeneratedCommands);
     846              : 
     847            9 :     VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath);
     848            3 :     return ConcreteCommandPath(path.mEndpointId, path.mClusterId, commandId);
     849              : }
     850              : 
     851           33 : ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const ConcreteCommandPath & before)
     852              : {
     853           33 :     EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands);
     854              : 
     855           33 :     CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext,
     856           33 :                                       mGeneratedCommandsIterator, GeneratedCommands);
     857              : 
     858           33 :     VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath);
     859           21 :     return ConcreteCommandPath(before.mEndpointId, before.mClusterId, commandId);
     860              : }
     861              : 
     862            3 : std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint)
     863              : {
     864              :     // Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
     865              :     // index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
     866              :     // during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
     867              :     //
     868              :     // Not actually needed for `First`, however this makes First and Next consistent.
     869            3 :     std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
     870            3 :     if (!endpoint_index.has_value())
     871              :     {
     872            0 :         return std::nullopt;
     873              :     }
     874              : 
     875            3 :     CHIP_ERROR err                            = CHIP_NO_ERROR;
     876            3 :     Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
     877            3 :     SpanSearchValue<chip::Span<const EmberAfDeviceType>> searchable(&deviceTypes);
     878              : 
     879            3 :     return DeviceTypeEntryFromEmber(searchable.First<ByDeviceType>(mDeviceTypeIterationHint).Value());
     880              : }
     881              : 
     882            6 : std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::NextDeviceType(EndpointId endpoint,
     883              :                                                                                    const DataModel::DeviceTypeEntry & previous)
     884              : {
     885              :     // Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
     886              :     // index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
     887              :     // during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
     888            6 :     std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
     889            6 :     if (!endpoint_index.has_value())
     890              :     {
     891            0 :         return std::nullopt;
     892              :     }
     893              : 
     894            6 :     CHIP_ERROR err                                  = CHIP_NO_ERROR;
     895            6 :     chip::Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
     896            6 :     SpanSearchValue<chip::Span<const EmberAfDeviceType>> searchable(&deviceTypes);
     897              : 
     898            6 :     return DeviceTypeEntryFromEmber(searchable.Next<ByDeviceType>(previous, mDeviceTypeIterationHint).Value());
     899              : }
     900              : 
     901            2 : std::optional<DataModel::Provider::SemanticTag> CodegenDataModelProvider::GetFirstSemanticTag(EndpointId endpoint)
     902              : {
     903            2 :     Clusters::Descriptor::Structs::SemanticTagStruct::Type tag;
     904              :     // we start at the beginning
     905            2 :     mSemanticTagIterationHint = 0;
     906            2 :     if (GetSemanticTagForEndpointAtIndex(endpoint, 0, tag) == CHIP_NO_ERROR)
     907              :     {
     908            1 :         return std::make_optional(tag);
     909              :     }
     910            1 :     return std::nullopt;
     911            2 : }
     912              : 
     913            5 : std::optional<DataModel::Provider::SemanticTag> CodegenDataModelProvider::GetNextSemanticTag(EndpointId endpoint,
     914              :                                                                                              const SemanticTag & previous)
     915              : {
     916            5 :     Clusters::Descriptor::Structs::SemanticTagStruct::Type tag;
     917            5 :     std::optional<unsigned> idx = FindNextSemanticTagIndex(endpoint, previous, mSemanticTagIterationHint);
     918            5 :     if (idx.has_value() && GetSemanticTagForEndpointAtIndex(endpoint, *idx, tag) == CHIP_NO_ERROR)
     919              :     {
     920            3 :         return std::make_optional(tag);
     921              :     }
     922            2 :     return std::nullopt;
     923            5 : }
     924              : 
     925              : } // namespace app
     926              : } // namespace chip
        

Generated by: LCOV version 2.0-1