Matter SDK Coverage Report
Current view: top level - app - InteractionModelEngine.cpp (source / functions) Coverage Total Hit
Test: SHA:5853f10e345717417494f970a7d13b422d94af51 Lines: 82.9 % 998 827
Test Date: 2025-06-30 07:09:23 Functions: 87.0 % 108 94

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2020-2021 Project CHIP Authors
       4              :  *    All rights reserved.
       5              :  *
       6              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       7              :  *    you may not use this file except in compliance with the License.
       8              :  *    You may obtain a copy of the License at
       9              :  *
      10              :  *        http://www.apache.org/licenses/LICENSE-2.0
      11              :  *
      12              :  *    Unless required by applicable law or agreed to in writing, software
      13              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      14              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15              :  *    See the License for the specific language governing permissions and
      16              :  *    limitations under the License.
      17              :  */
      18              : 
      19              : /**
      20              :  *    @file
      21              :  *      This file defines objects for a CHIP Interaction Data model Engine which handle unsolicited IM message, and
      22              :  *      manage different kinds of IM client and handlers.
      23              :  *
      24              :  */
      25              : 
      26              : #include "InteractionModelEngine.h"
      27              : 
      28              : #include <access/AccessRestrictionProvider.h>
      29              : #include <access/Privilege.h>
      30              : #include <access/RequestPath.h>
      31              : #include <access/SubjectDescriptor.h>
      32              : #include <app/AppConfig.h>
      33              : #include <app/CommandHandlerInterfaceRegistry.h>
      34              : #include <app/ConcreteAttributePath.h>
      35              : #include <app/ConcreteClusterPath.h>
      36              : #include <app/EventPathParams.h>
      37              : #include <app/data-model-provider/ActionReturnStatus.h>
      38              : #include <app/data-model-provider/MetadataLookup.h>
      39              : #include <app/data-model-provider/MetadataTypes.h>
      40              : #include <app/data-model-provider/OperationTypes.h>
      41              : #include <app/data-model/List.h>
      42              : #include <app/util/IMClusterCommandHandler.h>
      43              : #include <app/util/af-types.h>
      44              : #include <app/util/endpoint-config-api.h>
      45              : #include <lib/core/CHIPError.h>
      46              : #include <lib/core/DataModelTypes.h>
      47              : #include <lib/core/Global.h>
      48              : #include <lib/core/TLVUtilities.h>
      49              : #include <lib/support/CHIPFaultInjection.h>
      50              : #include <lib/support/CodeUtils.h>
      51              : #include <lib/support/FibonacciUtils.h>
      52              : #include <lib/support/ReadOnlyBuffer.h>
      53              : #include <protocols/interaction_model/StatusCode.h>
      54              : 
      55              : #include <cinttypes>
      56              : 
      57              : namespace chip {
      58              : namespace app {
      59              : namespace {
      60              : 
      61              : /**
      62              :  * Helper to handle wildcard events in the event path.
      63              :  *
      64              :  * Validates that ACL access is permitted to:
      65              :  *    - Cluster::View in case the path is a wildcard for the event id
      66              :  *    - Event read if the path is a concrete event path
      67              :  */
      68           18 : bool MayHaveAccessibleEventPathForEndpointAndCluster(DataModel::Provider * aProvider, const ConcreteClusterPath & path,
      69              :                                                      const EventPathParams & aEventPath,
      70              :                                                      const Access::SubjectDescriptor & aSubjectDescriptor)
      71              : {
      72           18 :     Access::RequestPath requestPath{ .cluster     = path.mClusterId,
      73           18 :                                      .endpoint    = path.mEndpointId,
      74           18 :                                      .requestType = Access::RequestType::kEventReadRequest };
      75              : 
      76           18 :     Access::Privilege requiredPrivilege = Access::Privilege::kView;
      77              : 
      78           18 :     if (!aEventPath.HasWildcardEventId())
      79              :     {
      80           12 :         requestPath.entityId = aEventPath.mEventId;
      81              : 
      82              :         DataModel::EventEntry eventInfo;
      83           12 :         if (aProvider->EventInfo(
      84              :                 {
      85           12 :                     aEventPath.mEndpointId,
      86           12 :                     aEventPath.mClusterId,
      87           12 :                     aEventPath.mEventId,
      88              :                 },
      89           24 :                 eventInfo) != CHIP_NO_ERROR)
      90              :         {
      91              :             // Non-wildcard path is not valid, so the event represented by the path is not accessible
      92              :             // (because it does not exist at all).
      93            0 :             return false;
      94              :         }
      95           12 :         requiredPrivilege = eventInfo.readPrivilege;
      96              :     }
      97              : 
      98           18 :     return (Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, requiredPrivilege) == CHIP_NO_ERROR);
      99              : }
     100              : 
     101           18 : bool MayHaveAccessibleEventPathForEndpoint(DataModel::Provider * aProvider, EndpointId aEndpoint,
     102              :                                            const EventPathParams & aEventPath, const Access::SubjectDescriptor & aSubjectDescriptor)
     103              : {
     104           18 :     if (!aEventPath.HasWildcardClusterId())
     105              :     {
     106           36 :         return MayHaveAccessibleEventPathForEndpointAndCluster(aProvider, ConcreteClusterPath(aEndpoint, aEventPath.mClusterId),
     107           18 :                                                                aEventPath, aSubjectDescriptor);
     108              :     }
     109              : 
     110            0 :     for (auto & cluster : aProvider->ServerClustersIgnoreError(aEndpoint))
     111              :     {
     112            0 :         if (MayHaveAccessibleEventPathForEndpointAndCluster(aProvider, ConcreteClusterPath(aEndpoint, cluster.clusterId),
     113              :                                                             aEventPath, aSubjectDescriptor))
     114              :         {
     115            0 :             return true;
     116              :         }
     117            0 :     }
     118              : 
     119            0 :     return false;
     120              : }
     121              : 
     122           18 : bool MayHaveAccessibleEventPath(DataModel::Provider * aProvider, const EventPathParams & aEventPath,
     123              :                                 const Access::SubjectDescriptor & subjectDescriptor)
     124              : {
     125           18 :     VerifyOrReturnValue(aProvider != nullptr, false);
     126              : 
     127           18 :     if (!aEventPath.HasWildcardEndpointId())
     128              :     {
     129           18 :         return MayHaveAccessibleEventPathForEndpoint(aProvider, aEventPath.mEndpointId, aEventPath, subjectDescriptor);
     130              :     }
     131              : 
     132            0 :     for (const DataModel::EndpointEntry & ep : aProvider->EndpointsIgnoreError())
     133              :     {
     134            0 :         if (MayHaveAccessibleEventPathForEndpoint(aProvider, ep.id, aEventPath, subjectDescriptor))
     135              :         {
     136            0 :             return true;
     137              :         }
     138            0 :     }
     139            0 :     return false;
     140              : }
     141              : 
     142              : /// Checks if the given path/attributeId, entry are ACL-accessible
     143              : /// for the given subject descriptor
     144          368 : bool IsAccessibleAttributeEntry(const ConcreteAttributePath & path, const Access::SubjectDescriptor & subjectDescriptor,
     145              :                                 const std::optional<DataModel::AttributeEntry> & entry)
     146              : {
     147          368 :     if (!entry.has_value() || !entry->GetReadPrivilege().has_value())
     148              :     {
     149              :         // Entry must be valid and be readable to begin with
     150            4 :         return false;
     151              :     }
     152              : 
     153          364 :     Access::RequestPath requestPath{ .cluster     = path.mClusterId,
     154          364 :                                      .endpoint    = path.mEndpointId,
     155              :                                      .requestType = Access::RequestType::kAttributeReadRequest,
     156          364 :                                      .entityId    = path.mAttributeId };
     157              : 
     158              :     // We know entry has value and GetReadPrivilege has value according to the check above
     159              :     // the assign below is safe.
     160          364 :     const Access::Privilege privilege = *entry->GetReadPrivilege(); // NOLINT(bugprone-unchecked-optional-access)
     161              : 
     162          364 :     return (Access::GetAccessControl().Check(subjectDescriptor, requestPath, privilege) == CHIP_NO_ERROR);
     163              : }
     164              : 
     165              : } // namespace
     166              : 
     167              : class AutoReleaseSubscriptionInfoIterator
     168              : {
     169              : public:
     170            0 :     AutoReleaseSubscriptionInfoIterator(SubscriptionResumptionStorage::SubscriptionInfoIterator * iterator) : mIterator(iterator){};
     171            0 :     ~AutoReleaseSubscriptionInfoIterator() { mIterator->Release(); }
     172              : 
     173            0 :     SubscriptionResumptionStorage::SubscriptionInfoIterator * operator->() const { return mIterator; }
     174              : 
     175              : private:
     176              :     SubscriptionResumptionStorage::SubscriptionInfoIterator * mIterator;
     177              : };
     178              : 
     179              : using Protocols::InteractionModel::Status;
     180              : 
     181              : Global<InteractionModelEngine> sInteractionModelEngine;
     182              : 
     183          375 : InteractionModelEngine::InteractionModelEngine() : mReportingEngine(this) {}
     184              : 
     185         9130 : InteractionModelEngine * InteractionModelEngine::GetInstance()
     186              : {
     187         9130 :     return &sInteractionModelEngine.get();
     188              : }
     189              : 
     190          420 : CHIP_ERROR InteractionModelEngine::Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable,
     191              :                                         reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr,
     192              :                                         SubscriptionResumptionStorage * subscriptionResumptionStorage,
     193              :                                         EventManagement * eventManagement)
     194              : {
     195          420 :     VerifyOrReturnError(apFabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
     196          420 :     VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
     197          420 :     VerifyOrReturnError(reportScheduler != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
     198              : 
     199          420 :     mState                          = State::kInitializing;
     200          420 :     mpExchangeMgr                   = apExchangeMgr;
     201          420 :     mpFabricTable                   = apFabricTable;
     202          420 :     mpCASESessionMgr                = apCASESessionMgr;
     203          420 :     mpSubscriptionResumptionStorage = subscriptionResumptionStorage;
     204          420 :     mReportScheduler                = reportScheduler;
     205              : 
     206          420 :     ReturnErrorOnFailure(mpFabricTable->AddFabricDelegate(this));
     207          420 :     ReturnErrorOnFailure(mpExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id, this));
     208              : 
     209          420 :     mReportingEngine.Init((eventManagement != nullptr) ? eventManagement : &EventManagement::GetInstance());
     210              : 
     211          420 :     StatusIB::RegisterErrorFormatter();
     212              : 
     213          420 :     mState = State::kInitialized;
     214          420 :     return CHIP_NO_ERROR;
     215              : }
     216              : 
     217          406 : void InteractionModelEngine::Shutdown()
     218              : {
     219          406 :     VerifyOrReturn(State::kUninitialized != mState);
     220              : 
     221          297 :     mpExchangeMgr->GetSessionManager()->SystemLayer()->CancelTimer(ResumeSubscriptionsTimerCallback, this);
     222              : 
     223              :     // TODO: individual object clears the entire command handler interface registry.
     224              :     //       This may not be expected as IME does NOT own the command handler interface registry.
     225              :     //
     226              :     //       This is to be cleaned up once InteractionModelEngine maintains a data model fully and
     227              :     //       the code-generation model can do its clear in its shutdown method.
     228          297 :     CommandHandlerInterfaceRegistry::Instance().UnregisterAllHandlers();
     229          297 :     mCommandResponderObjs.ReleaseAll();
     230              : 
     231          297 :     mTimedHandlers.ForEachActiveObject([this](TimedHandler * obj) -> Loop {
     232            1 :         mpExchangeMgr->CloseAllContextsForDelegate(obj);
     233            1 :         return Loop::Continue;
     234              :     });
     235              : 
     236          297 :     mTimedHandlers.ReleaseAll();
     237              : 
     238          297 :     mReadHandlers.ReleaseAll();
     239              : 
     240              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
     241              :     // Shut down any subscription clients that are still around.  They won't be
     242              :     // able to work after this point anyway, since we're about to drop our refs
     243              :     // to them.
     244          297 :     ShutdownAllSubscriptions();
     245              : 
     246              :     //
     247              :     // We hold weak references to ReadClient objects. The application ultimately
     248              :     // actually owns them, so it's on them to eventually shut them down and free them
     249              :     // up.
     250              :     //
     251              :     // However, we should null out their pointers back to us at the very least so that
     252              :     // at destruction time, they won't attempt to reach back here to remove themselves
     253              :     // from this list.
     254              :     //
     255          301 :     for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
     256              :     {
     257            4 :         readClient->mpImEngine = nullptr;
     258            4 :         auto * tmpClient       = readClient->GetNextClient();
     259            4 :         readClient->SetNextClient(nullptr);
     260            4 :         readClient = tmpClient;
     261              :     }
     262              : 
     263              :     //
     264              :     // After that, we just null out our tracker.
     265              :     //
     266          297 :     mpActiveReadClientList = nullptr;
     267              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
     268              : 
     269         1485 :     for (auto & writeHandler : mWriteHandlers)
     270              :     {
     271         1188 :         if (!writeHandler.IsFree())
     272              :         {
     273            0 :             writeHandler.Close();
     274              :         }
     275              :     }
     276              : 
     277          297 :     mReportingEngine.Shutdown();
     278          297 :     mAttributePathPool.ReleaseAll();
     279          297 :     mEventPathPool.ReleaseAll();
     280          297 :     mDataVersionFilterPool.ReleaseAll();
     281          297 :     mpExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id);
     282              : 
     283          297 :     mpCASESessionMgr = nullptr;
     284              : 
     285              :     //
     286              :     // We _should_ be clearing these out, but doing so invites a world
     287              :     // of trouble. #21233 tracks fixing the underlying assumptions to make
     288              :     // this possible.
     289              :     //
     290              :     // mpFabricTable    = nullptr;
     291              :     // mpExchangeMgr    = nullptr;
     292              : 
     293          297 :     mState = State::kUninitialized;
     294              : }
     295              : 
     296           69 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers() const
     297              : {
     298           69 :     return static_cast<uint32_t>(mReadHandlers.Allocated());
     299              : }
     300              : 
     301           51 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers(ReadHandler::InteractionType aType) const
     302              : {
     303           51 :     uint32_t count = 0;
     304              : 
     305           51 :     mReadHandlers.ForEachActiveObject([aType, &count](const ReadHandler * handler) {
     306           73 :         if (handler->IsType(aType))
     307              :         {
     308           61 :             count++;
     309              :         }
     310              : 
     311           73 :         return Loop::Continue;
     312              :     });
     313              : 
     314           51 :     return count;
     315              : }
     316              : 
     317           19 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers(ReadHandler::InteractionType aType, FabricIndex aFabricIndex) const
     318              : {
     319           19 :     uint32_t count = 0;
     320              : 
     321           19 :     mReadHandlers.ForEachActiveObject([aType, aFabricIndex, &count](const ReadHandler * handler) {
     322           66 :         if (handler->IsType(aType) && handler->GetAccessingFabricIndex() == aFabricIndex)
     323              :         {
     324           31 :             count++;
     325              :         }
     326              : 
     327           66 :         return Loop::Continue;
     328              :     });
     329              : 
     330           19 :     return count;
     331              : }
     332              : 
     333         2314 : ReadHandler * InteractionModelEngine::ActiveHandlerAt(unsigned int aIndex)
     334              : {
     335         2314 :     if (aIndex >= mReadHandlers.Allocated())
     336              :     {
     337            0 :         return nullptr;
     338              :     }
     339              : 
     340         2314 :     unsigned int i    = 0;
     341         2314 :     ReadHandler * ret = nullptr;
     342              : 
     343         2314 :     mReadHandlers.ForEachActiveObject([aIndex, &i, &ret](ReadHandler * handler) {
     344        15000 :         if (i == aIndex)
     345              :         {
     346         2314 :             ret = handler;
     347         2314 :             return Loop::Break;
     348              :         }
     349              : 
     350        12686 :         i++;
     351        12686 :         return Loop::Continue;
     352              :     });
     353              : 
     354         2314 :     return ret;
     355              : }
     356              : 
     357            1 : WriteHandler * InteractionModelEngine::ActiveWriteHandlerAt(unsigned int aIndex)
     358              : {
     359            1 :     unsigned int i = 0;
     360              : 
     361            1 :     for (auto & writeHandler : mWriteHandlers)
     362              :     {
     363            1 :         if (!writeHandler.IsFree())
     364              :         {
     365            1 :             if (i == aIndex)
     366              :             {
     367            1 :                 return &writeHandler;
     368              :             }
     369            0 :             i++;
     370              :         }
     371              :     }
     372            0 :     return nullptr;
     373              : }
     374              : 
     375           14 : uint32_t InteractionModelEngine::GetNumActiveWriteHandlers() const
     376              : {
     377           14 :     uint32_t numActive = 0;
     378              : 
     379           70 :     for (auto & writeHandler : mWriteHandlers)
     380              :     {
     381           56 :         if (!writeHandler.IsFree())
     382              :         {
     383            2 :             numActive++;
     384              :         }
     385              :     }
     386              : 
     387           14 :     return numActive;
     388              : }
     389              : 
     390              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
     391            2 : CHIP_ERROR InteractionModelEngine::ShutdownSubscription(const ScopedNodeId & aPeerNodeId, SubscriptionId aSubscriptionId)
     392              : {
     393            2 :     assertChipStackLockedByCurrentThread();
     394            2 :     for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
     395              :     {
     396              :         // Grab the next client now, because we might be about to delete readClient.
     397            2 :         auto * nextClient = readClient->GetNextClient();
     398            6 :         if (readClient->IsSubscriptionType() && readClient->IsMatchingSubscriptionId(aSubscriptionId) &&
     399            6 :             readClient->GetFabricIndex() == aPeerNodeId.GetFabricIndex() && readClient->GetPeerNodeId() == aPeerNodeId.GetNodeId())
     400              :         {
     401            2 :             readClient->Close(CHIP_NO_ERROR);
     402            2 :             return CHIP_NO_ERROR;
     403              :         }
     404            0 :         readClient = nextClient;
     405              :     }
     406              : 
     407            0 :     return CHIP_ERROR_KEY_NOT_FOUND;
     408              : }
     409              : 
     410            0 : void InteractionModelEngine::ShutdownSubscriptions(FabricIndex aFabricIndex, NodeId aPeerNodeId)
     411              : {
     412            0 :     assertChipStackLockedByCurrentThread();
     413            0 :     ShutdownMatchingSubscriptions(MakeOptional(aFabricIndex), MakeOptional(aPeerNodeId));
     414            0 : }
     415            0 : void InteractionModelEngine::ShutdownSubscriptions(FabricIndex aFabricIndex)
     416              : {
     417            0 :     assertChipStackLockedByCurrentThread();
     418            0 :     ShutdownMatchingSubscriptions(MakeOptional(aFabricIndex));
     419            0 : }
     420              : 
     421          297 : void InteractionModelEngine::ShutdownAllSubscriptions()
     422              : {
     423          297 :     assertChipStackLockedByCurrentThread();
     424          297 :     ShutdownMatchingSubscriptions();
     425          297 : }
     426              : 
     427            1 : void InteractionModelEngine::ShutdownAllSubscriptionHandlers()
     428              : {
     429            1 :     mReadHandlers.ForEachActiveObject([&](auto * handler) {
     430            1 :         if (!handler->IsType(ReadHandler::InteractionType::Subscribe))
     431              :         {
     432            0 :             return Loop::Continue;
     433              :         }
     434            1 :         handler->Close();
     435            1 :         return Loop::Continue;
     436              :     });
     437            1 : }
     438              : 
     439          297 : void InteractionModelEngine::ShutdownMatchingSubscriptions(const Optional<FabricIndex> & aFabricIndex,
     440              :                                                            const Optional<NodeId> & aPeerNodeId)
     441              : {
     442              :     // This is assuming that ReadClient::Close will not affect any other
     443              :     // ReadClients in the list.
     444          301 :     for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
     445              :     {
     446              :         // Grab the next client now, because we might be about to delete readClient.
     447            4 :         auto * nextClient = readClient->GetNextClient();
     448            4 :         if (readClient->IsSubscriptionType())
     449              :         {
     450            4 :             bool fabricMatches = !aFabricIndex.HasValue() || (aFabricIndex.Value() == readClient->GetFabricIndex());
     451            4 :             bool nodeIdMatches = !aPeerNodeId.HasValue() || (aPeerNodeId.Value() == readClient->GetPeerNodeId());
     452            4 :             if (fabricMatches && nodeIdMatches)
     453              :             {
     454            4 :                 readClient->Close(CHIP_NO_ERROR);
     455              :             }
     456              :         }
     457            4 :         readClient = nextClient;
     458              :     }
     459          297 : }
     460              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
     461              : 
     462           38 : bool InteractionModelEngine::SubjectHasActiveSubscription(FabricIndex aFabricIndex, NodeId subjectID)
     463              : {
     464           38 :     bool isActive = false;
     465           38 :     mReadHandlers.ForEachActiveObject([aFabricIndex, subjectID, &isActive](ReadHandler * handler) {
     466           52 :         VerifyOrReturnValue(handler->IsType(ReadHandler::InteractionType::Subscribe), Loop::Continue);
     467              : 
     468           52 :         Access::SubjectDescriptor subject = handler->GetSubjectDescriptor();
     469           52 :         VerifyOrReturnValue(subject.fabricIndex == aFabricIndex, Loop::Continue);
     470              : 
     471           35 :         if (subject.authMode == Access::AuthMode::kCase)
     472              :         {
     473           35 :             if (subject.cats.CheckSubjectAgainstCATs(subjectID) || subjectID == subject.subject)
     474              :             {
     475           33 :                 isActive = handler->IsActiveSubscription();
     476              : 
     477              :                 // Exit loop only if isActive is set to true.
     478              :                 // Otherwise keep looking for another subscription that could match the subject.
     479           33 :                 VerifyOrReturnValue(!isActive, Loop::Break);
     480              :             }
     481              :         }
     482              : 
     483           24 :         return Loop::Continue;
     484              :     });
     485              : 
     486           38 :     return isActive;
     487              : }
     488              : 
     489            6 : bool InteractionModelEngine::SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subjectID)
     490              : {
     491            6 :     bool persistedSubMatches = false;
     492              : 
     493              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     494            6 :     auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions();
     495              :     // Verify that we were able to allocate an iterator. If not, we are probably currently trying to resubscribe to our persisted
     496              :     // subscriptions. As such, we assume we have a persisted subscription and return true.
     497              :     // If we don't have a persisted subscription for the given fabric index and subjectID, we will send a Check-In message next time
     498              :     // we transition to ActiveMode.
     499            6 :     VerifyOrReturnValue(iterator, true);
     500              : 
     501            6 :     SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
     502            9 :     while (iterator->Next(subscriptionInfo))
     503              :     {
     504              :         // TODO(#31873): Persistent subscription only stores the NodeID for now. We cannot check if the CAT matches
     505            6 :         if (subscriptionInfo.mFabricIndex == aFabricIndex && subscriptionInfo.mNodeId == subjectID)
     506              :         {
     507            3 :             persistedSubMatches = true;
     508            3 :             break;
     509              :         }
     510              :     }
     511            6 :     iterator->Release();
     512              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     513              : 
     514            6 :     return persistedSubMatches;
     515            6 : }
     516              : 
     517           15 : bool InteractionModelEngine::FabricHasAtLeastOneActiveSubscription(FabricIndex aFabricIndex)
     518              : {
     519           15 :     bool hasActiveSubscription = false;
     520           15 :     mReadHandlers.ForEachActiveObject([aFabricIndex, &hasActiveSubscription](ReadHandler * handler) {
     521           11 :         VerifyOrReturnValue(handler->IsType(ReadHandler::InteractionType::Subscribe), Loop::Continue);
     522              : 
     523           11 :         Access::SubjectDescriptor subject = handler->GetSubjectDescriptor();
     524           11 :         VerifyOrReturnValue(subject.fabricIndex == aFabricIndex, Loop::Continue);
     525              : 
     526            9 :         if ((subject.authMode == Access::AuthMode::kCase) && handler->IsActiveSubscription())
     527              :         {
     528              :             // On first subscription found for fabric, we can immediately stop checking.
     529            5 :             hasActiveSubscription = true;
     530            5 :             return Loop::Break;
     531              :         }
     532              : 
     533            4 :         return Loop::Continue;
     534              :     });
     535              : 
     536           15 :     return hasActiveSubscription;
     537              : }
     538              : 
     539           35 : void InteractionModelEngine::OnDone(CommandResponseSender & apResponderObj)
     540              : {
     541           35 :     mCommandResponderObjs.ReleaseObject(&apResponderObj);
     542           35 : }
     543              : 
     544              : // TODO(#30453): Follow up refactor. Remove need for InteractionModelEngine::OnDone(CommandHandlerImpl).
     545            0 : void InteractionModelEngine::OnDone(CommandHandlerImpl & apCommandObj)
     546              : {
     547              :     // We are no longer expecting to receive this callback. With the introduction of CommandResponseSender, it is now
     548              :     // responsible for receiving this callback.
     549            0 :     VerifyOrDie(false);
     550              : }
     551              : 
     552          866 : void InteractionModelEngine::OnDone(ReadHandler & apReadObj)
     553              : {
     554              :     //
     555              :     // Deleting an item can shift down the contents of the underlying pool storage,
     556              :     // rendering any tracker using positional indexes invalid. Let's reset it,
     557              :     // based on which readHandler we are getting rid of.
     558              :     //
     559          866 :     mReportingEngine.ResetReadHandlerTracker(&apReadObj);
     560              : 
     561          866 :     mReadHandlers.ReleaseObject(&apReadObj);
     562          866 :     TryToResumeSubscriptions();
     563          866 : }
     564              : 
     565          866 : void InteractionModelEngine::TryToResumeSubscriptions()
     566              : {
     567              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     568          866 :     if (!mSubscriptionResumptionScheduled && HasSubscriptionsToResume())
     569              :     {
     570            0 :         mSubscriptionResumptionScheduled            = true;
     571            0 :         auto timeTillNextSubscriptionResumptionSecs = ComputeTimeSecondsTillNextSubscriptionResumption();
     572            0 :         mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(
     573            0 :             System::Clock::Seconds32(timeTillNextSubscriptionResumptionSecs), ResumeSubscriptionsTimerCallback, this);
     574            0 :         mNumSubscriptionResumptionRetries++;
     575            0 :         ChipLogProgress(InteractionModel, "Schedule subscription resumption when failing to establish session, Retries: %" PRIu32,
     576              :                         mNumSubscriptionResumptionRetries);
     577              :     }
     578              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     579          866 : }
     580              : 
     581           35 : Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext,
     582              :                                                       const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
     583              :                                                       bool aIsTimedInvoke)
     584              : {
     585              :     // TODO(#30453): Refactor CommandResponseSender's constructor to accept an exchange context parameter.
     586           35 :     CommandResponseSender * commandResponder = mCommandResponderObjs.CreateObject(this, this);
     587           35 :     if (commandResponder == nullptr)
     588              :     {
     589            0 :         ChipLogProgress(InteractionModel, "no resource for Invoke interaction");
     590            0 :         return Status::Busy;
     591              :     }
     592           35 :     CHIP_FAULT_INJECT(FaultInjection::kFault_IMInvoke_SeparateResponses,
     593              :                       commandResponder->TestOnlyInvokeCommandRequestWithFaultsInjected(
     594              :                           apExchangeContext, std::move(aPayload), aIsTimedInvoke,
     595              :                           CommandHandlerImpl::NlFaultInjectionType::SeparateResponseMessages);
     596              :                       return Status::Success;);
     597           35 :     CHIP_FAULT_INJECT(FaultInjection::kFault_IMInvoke_SeparateResponsesInvertResponseOrder,
     598              :                       commandResponder->TestOnlyInvokeCommandRequestWithFaultsInjected(
     599              :                           apExchangeContext, std::move(aPayload), aIsTimedInvoke,
     600              :                           CommandHandlerImpl::NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder);
     601              :                       return Status::Success;);
     602           35 :     CHIP_FAULT_INJECT(
     603              :         FaultInjection::kFault_IMInvoke_SkipSecondResponse,
     604              :         commandResponder->TestOnlyInvokeCommandRequestWithFaultsInjected(
     605              :             apExchangeContext, std::move(aPayload), aIsTimedInvoke, CommandHandlerImpl::NlFaultInjectionType::SkipSecondResponse);
     606              :         return Status::Success;);
     607           35 :     commandResponder->OnInvokeCommandRequest(apExchangeContext, std::move(aPayload), aIsTimedInvoke);
     608           35 :     return Status::Success;
     609              : }
     610              : 
     611          302 : CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDescriptor & aSubjectDescriptor,
     612              :                                                        AttributePathIBs::Parser & aAttributePathListParser,
     613              :                                                        bool & aHasValidAttributePath, size_t & aRequestedAttributePathCount)
     614              : {
     615          302 :     TLV::TLVReader pathReader;
     616          302 :     aAttributePathListParser.GetReader(&pathReader);
     617          302 :     CHIP_ERROR err = CHIP_NO_ERROR;
     618              : 
     619          302 :     aHasValidAttributePath       = false;
     620          302 :     aRequestedAttributePathCount = 0;
     621              : 
     622          672 :     while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
     623              :     {
     624          370 :         AttributePathIB::Parser path;
     625              :         //
     626              :         // We create an iterator to point to a single item object list that tracks the path we just parsed.
     627              :         // This avoids the 'parse all paths' approach that is employed in ReadHandler since we want to
     628              :         // avoid allocating out of the path store during this minimal initial processing stage.
     629              :         //
     630          370 :         SingleLinkedListNode<AttributePathParams> paramsList;
     631              : 
     632          370 :         ReturnErrorOnFailure(path.Init(pathReader));
     633          370 :         ReturnErrorOnFailure(path.ParsePath(paramsList.mValue));
     634              : 
     635          370 :         if (paramsList.mValue.IsWildcardPath())
     636              :         {
     637              : 
     638           10 :             auto state = AttributePathExpandIterator::Position::StartIterating(&paramsList);
     639           10 :             AttributePathExpandIterator pathIterator(GetDataModelProvider(), state);
     640           10 :             ConcreteAttributePath readPath;
     641           10 :             std::optional<DataModel::AttributeEntry> entry;
     642              : 
     643              :             // The definition of "valid path" is "path exists and ACL allows access". The "path exists" part is handled by
     644              :             // AttributePathExpandIterator. So we just need to check the ACL bits.
     645           10 :             while (pathIterator.Next(readPath, &entry))
     646              :             {
     647              :                 // readPath is based on path expansion, so entire path is a `valid attribute path`.
     648              :                 //
     649              :                 // Here we check if the cluster is accessible at all (at least one attribute) for the
     650              :                 // given entry permissions.
     651            8 :                 if (IsAccessibleAttributeEntry(readPath, aSubjectDescriptor, entry))
     652              :                 {
     653            8 :                     aHasValidAttributePath = true;
     654            8 :                     break;
     655              :                 }
     656              :             }
     657           10 :         }
     658              :         else
     659              :         {
     660          360 :             ConcreteAttributePath concretePath(paramsList.mValue.mEndpointId, paramsList.mValue.mClusterId,
     661          360 :                                                paramsList.mValue.mAttributeId);
     662              : 
     663          360 :             std::optional<DataModel::AttributeEntry> entry = FindAttributeEntry(concretePath);
     664              : 
     665          360 :             if (IsAccessibleAttributeEntry(concretePath, aSubjectDescriptor, entry))
     666              :             {
     667          356 :                 aHasValidAttributePath = true;
     668              :             }
     669              :         }
     670              : 
     671          370 :         aRequestedAttributePathCount++;
     672              :     }
     673              : 
     674          302 :     if (err == CHIP_ERROR_END_OF_TLV)
     675              :     {
     676          302 :         err = CHIP_NO_ERROR;
     677              :     }
     678              : 
     679          302 :     return err;
     680              : }
     681              : 
     682           18 : CHIP_ERROR InteractionModelEngine::ParseEventPaths(const Access::SubjectDescriptor & aSubjectDescriptor,
     683              :                                                    EventPathIBs::Parser & aEventPathListParser, bool & aHasValidEventPath,
     684              :                                                    size_t & aRequestedEventPathCount)
     685              : {
     686           18 :     TLV::TLVReader pathReader;
     687           18 :     aEventPathListParser.GetReader(&pathReader);
     688           18 :     CHIP_ERROR err = CHIP_NO_ERROR;
     689              : 
     690           18 :     aHasValidEventPath       = false;
     691           18 :     aRequestedEventPathCount = 0;
     692              : 
     693           52 :     while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
     694              :     {
     695           34 :         EventPathIB::Parser path;
     696           34 :         ReturnErrorOnFailure(path.Init(pathReader));
     697              : 
     698           34 :         EventPathParams eventPath;
     699           34 :         ReturnErrorOnFailure(path.ParsePath(eventPath));
     700              : 
     701           34 :         ++aRequestedEventPathCount;
     702              : 
     703           34 :         if (aHasValidEventPath)
     704              :         {
     705              :             // Can skip all the rest of the checking.
     706           16 :             continue;
     707              :         }
     708              : 
     709              :         // The definition of "valid path" is "path exists and ACL allows
     710              :         // access".  We need to do some expansion of wildcards to handle that.
     711           18 :         aHasValidEventPath = MayHaveAccessibleEventPath(mDataModelProvider, eventPath, aSubjectDescriptor);
     712              :     }
     713              : 
     714           18 :     if (err == CHIP_ERROR_END_OF_TLV)
     715              :     {
     716           18 :         err = CHIP_NO_ERROR;
     717              :     }
     718              : 
     719           18 :     return err;
     720              : }
     721              : 
     722         1106 : Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest(Messaging::ExchangeContext * apExchangeContext,
     723              :                                                                                  const PayloadHeader & aPayloadHeader,
     724              :                                                                                  System::PacketBufferHandle && aPayload,
     725              :                                                                                  ReadHandler::InteractionType aInteractionType)
     726              : {
     727         1106 :     ChipLogDetail(InteractionModel, "Received %s request",
     728              :                   aInteractionType == ReadHandler::InteractionType::Subscribe ? "Subscribe" : "Read");
     729              : 
     730              :     //
     731              :     // Let's first figure out if the client has sent us a subscribe request and requested we keep any existing
     732              :     // subscriptions from that source.
     733              :     //
     734         1106 :     if (aInteractionType == ReadHandler::InteractionType::Subscribe)
     735              :     {
     736          311 :         System::PacketBufferTLVReader reader;
     737          311 :         bool keepExistingSubscriptions = true;
     738              : 
     739          311 :         if (apExchangeContext->GetSessionHandle()->GetFabricIndex() == kUndefinedFabricIndex)
     740              :         {
     741              :             // Subscriptions must be associated to a fabric.
     742            0 :             return Status::UnsupportedAccess;
     743              :         }
     744              : 
     745          311 :         reader.Init(aPayload.Retain());
     746              : 
     747          311 :         SubscribeRequestMessage::Parser subscribeRequestParser;
     748          311 :         VerifyOrReturnError(subscribeRequestParser.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
     749              : 
     750              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     751          309 :         subscribeRequestParser.PrettyPrint();
     752              : #endif
     753              : 
     754          309 :         VerifyOrReturnError(subscribeRequestParser.GetKeepSubscriptions(&keepExistingSubscriptions) == CHIP_NO_ERROR,
     755              :                             Status::InvalidAction);
     756          309 :         if (!keepExistingSubscriptions)
     757              :         {
     758              :             //
     759              :             // Walk through all existing subscriptions and shut down those whose subscriber matches
     760              :             // that which just came in.
     761              :             //
     762           83 :             mReadHandlers.ForEachActiveObject([apExchangeContext](ReadHandler * handler) {
     763            5 :                 if (handler->IsFromSubscriber(*apExchangeContext))
     764              :                 {
     765            5 :                     ChipLogProgress(InteractionModel,
     766              :                                     "Deleting previous active subscription from NodeId: " ChipLogFormatX64 ", FabricIndex: %u",
     767              :                                     ChipLogValueX64(apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId()),
     768              :                                     apExchangeContext->GetSessionHandle()->GetFabricIndex());
     769            5 :                     handler->Close();
     770              :                 }
     771              : 
     772            5 :                 return Loop::Continue;
     773              :             });
     774              : 
     775              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     776           83 :             if (mpSubscriptionResumptionStorage != nullptr)
     777              :             {
     778            0 :                 SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
     779            0 :                 auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions();
     780              : 
     781            0 :                 while (iterator->Next(subscriptionInfo))
     782              :                 {
     783            0 :                     if (subscriptionInfo.mNodeId == apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId() &&
     784            0 :                         subscriptionInfo.mFabricIndex == apExchangeContext->GetSessionHandle()->GetFabricIndex())
     785              :                     {
     786            0 :                         ChipLogProgress(InteractionModel,
     787              :                                         "Deleting previous non-active subscription from NodeId: " ChipLogFormatX64
     788              :                                         ", FabricIndex: %u, SubscriptionId: 0x%" PRIx32,
     789              :                                         ChipLogValueX64(subscriptionInfo.mNodeId), subscriptionInfo.mFabricIndex,
     790              :                                         subscriptionInfo.mSubscriptionId);
     791            0 :                         mpSubscriptionResumptionStorage->Delete(subscriptionInfo.mNodeId, subscriptionInfo.mFabricIndex,
     792              :                                                                 subscriptionInfo.mSubscriptionId);
     793              :                     }
     794              :                 }
     795            0 :                 iterator->Release();
     796              : 
     797              :                 // If we have no subscriptions to resume, we can cancel the timer, which might be armed
     798              :                 // if one of the subscriptions we deleted was about to be resumed.
     799            0 :                 if (!HasSubscriptionsToResume())
     800              :                 {
     801            0 :                     mpExchangeMgr->GetSessionManager()->SystemLayer()->CancelTimer(ResumeSubscriptionsTimerCallback, this);
     802            0 :                     mSubscriptionResumptionScheduled  = false;
     803            0 :                     mNumSubscriptionResumptionRetries = 0;
     804              :                 }
     805            0 :             }
     806              : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     807              :         }
     808              : 
     809              :         {
     810          309 :             size_t requestedAttributePathCount = 0;
     811          309 :             size_t requestedEventPathCount     = 0;
     812          309 :             AttributePathIBs::Parser attributePathListParser;
     813          309 :             bool hasValidAttributePath = false;
     814          309 :             bool mayHaveValidEventPath = false;
     815              : 
     816          309 :             CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser);
     817          309 :             if (err == CHIP_NO_ERROR)
     818              :             {
     819          302 :                 auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
     820          302 :                 err                    = ParseAttributePaths(subjectDescriptor, attributePathListParser, hasValidAttributePath,
     821              :                                                              requestedAttributePathCount);
     822          302 :                 if (err != CHIP_NO_ERROR)
     823              :                 {
     824            0 :                     return Status::InvalidAction;
     825              :                 }
     826              :             }
     827            7 :             else if (err != CHIP_ERROR_END_OF_TLV)
     828              :             {
     829            0 :                 return Status::InvalidAction;
     830              :             }
     831              : 
     832          309 :             EventPathIBs::Parser eventPathListParser;
     833          309 :             err = subscribeRequestParser.GetEventRequests(&eventPathListParser);
     834          309 :             if (err == CHIP_NO_ERROR)
     835              :             {
     836           18 :                 auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
     837           18 :                 err = ParseEventPaths(subjectDescriptor, eventPathListParser, mayHaveValidEventPath, requestedEventPathCount);
     838           18 :                 if (err != CHIP_NO_ERROR)
     839              :                 {
     840            0 :                     return Status::InvalidAction;
     841              :                 }
     842              :             }
     843          291 :             else if (err != CHIP_ERROR_END_OF_TLV)
     844              :             {
     845            0 :                 return Status::InvalidAction;
     846              :             }
     847              : 
     848          309 :             if (requestedAttributePathCount == 0 && requestedEventPathCount == 0)
     849              :             {
     850            1 :                 ChipLogError(InteractionModel,
     851              :                              "Subscription from [%u:" ChipLogFormatX64 "] has no attribute or event paths. Rejecting request.",
     852              :                              apExchangeContext->GetSessionHandle()->GetFabricIndex(),
     853              :                              ChipLogValueX64(apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId()));
     854            1 :                 return Status::InvalidAction;
     855              :             }
     856              : 
     857          308 :             if (!hasValidAttributePath && !mayHaveValidEventPath)
     858              :             {
     859            3 :                 ChipLogError(InteractionModel,
     860              :                              "Subscription from [%u:" ChipLogFormatX64 "] has no access at all. Rejecting request.",
     861              :                              apExchangeContext->GetSessionHandle()->GetFabricIndex(),
     862              :                              ChipLogValueX64(apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId()));
     863            3 :                 return Status::InvalidAction;
     864              :             }
     865              : 
     866              :             // The following cast is safe, since we can only hold a few tens of paths in one request.
     867          305 :             if (!EnsureResourceForSubscription(apExchangeContext->GetSessionHandle()->GetFabricIndex(), requestedAttributePathCount,
     868              :                                                requestedEventPathCount))
     869              :             {
     870            2 :                 return Status::PathsExhausted;
     871              :             }
     872              :         }
     873          311 :     }
     874              :     else
     875              :     {
     876          795 :         System::PacketBufferTLVReader reader;
     877          795 :         reader.Init(aPayload.Retain());
     878              : 
     879          795 :         ReadRequestMessage::Parser readRequestParser;
     880          795 :         VerifyOrReturnError(readRequestParser.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
     881              : 
     882              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     883          791 :         readRequestParser.PrettyPrint();
     884              : #endif
     885              :         {
     886          791 :             size_t requestedAttributePathCount = 0;
     887          791 :             size_t requestedEventPathCount     = 0;
     888          791 :             AttributePathIBs::Parser attributePathListParser;
     889          791 :             CHIP_ERROR err = readRequestParser.GetAttributeRequests(&attributePathListParser);
     890          791 :             if (err == CHIP_NO_ERROR)
     891              :             {
     892          670 :                 TLV::TLVReader pathReader;
     893          670 :                 attributePathListParser.GetReader(&pathReader);
     894          670 :                 VerifyOrReturnError(TLV::Utilities::Count(pathReader, requestedAttributePathCount, false) == CHIP_NO_ERROR,
     895              :                                     Status::InvalidAction);
     896              :             }
     897          121 :             else if (err != CHIP_ERROR_END_OF_TLV)
     898              :             {
     899            0 :                 return Status::InvalidAction;
     900              :             }
     901          791 :             EventPathIBs::Parser eventpathListParser;
     902          791 :             err = readRequestParser.GetEventRequests(&eventpathListParser);
     903          791 :             if (err == CHIP_NO_ERROR)
     904              :             {
     905          317 :                 TLV::TLVReader pathReader;
     906          317 :                 eventpathListParser.GetReader(&pathReader);
     907          317 :                 VerifyOrReturnError(TLV::Utilities::Count(pathReader, requestedEventPathCount, false) == CHIP_NO_ERROR,
     908              :                                     Status::InvalidAction);
     909              :             }
     910          474 :             else if (err != CHIP_ERROR_END_OF_TLV)
     911              :             {
     912            0 :                 return Status::InvalidAction;
     913              :             }
     914              : 
     915              :             // The following cast is safe, since we can only hold a few tens of paths in one request.
     916          791 :             Status checkResult = EnsureResourceForRead(apExchangeContext->GetSessionHandle()->GetFabricIndex(),
     917              :                                                        requestedAttributePathCount, requestedEventPathCount);
     918          791 :             if (checkResult != Status::Success)
     919              :             {
     920            7 :                 return checkResult;
     921              :             }
     922              :         }
     923          795 :     }
     924              : 
     925              :     // We have already reserved enough resources for read requests, and have granted enough resources for current subscriptions, so
     926              :     // we should be able to allocate resources requested by this request.
     927         1087 :     ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType, mReportScheduler);
     928         1087 :     if (handler == nullptr)
     929              :     {
     930            0 :         ChipLogProgress(InteractionModel, "no resource for %s interaction",
     931              :                         aInteractionType == ReadHandler::InteractionType::Subscribe ? "Subscribe" : "Read");
     932            0 :         return Status::ResourceExhausted;
     933              :     }
     934              : 
     935         1087 :     handler->OnInitialRequest(std::move(aPayload));
     936              : 
     937         1087 :     return Status::Success;
     938              : }
     939              : 
     940          956 : Protocols::InteractionModel::Status InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext,
     941              :                                                                            const PayloadHeader & aPayloadHeader,
     942              :                                                                            System::PacketBufferHandle && aPayload,
     943              :                                                                            bool aIsTimedWrite)
     944              : {
     945          956 :     ChipLogDetail(InteractionModel, "Received Write request");
     946              : 
     947          960 :     for (auto & writeHandler : mWriteHandlers)
     948              :     {
     949          960 :         if (writeHandler.IsFree())
     950              :         {
     951          956 :             VerifyOrReturnError(writeHandler.Init(GetDataModelProvider(), this) == CHIP_NO_ERROR, Status::Busy);
     952          955 :             return writeHandler.OnWriteRequest(apExchangeContext, std::move(aPayload), aIsTimedWrite);
     953              :         }
     954              :     }
     955            0 :     ChipLogProgress(InteractionModel, "no resource for write interaction");
     956            0 :     return Status::Busy;
     957              : }
     958              : 
     959            5 : CHIP_ERROR InteractionModelEngine::OnTimedRequest(Messaging::ExchangeContext * apExchangeContext,
     960              :                                                   const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
     961              :                                                   Protocols::InteractionModel::Status & aStatus)
     962              : {
     963            5 :     TimedHandler * handler = mTimedHandlers.CreateObject(this);
     964            5 :     if (handler == nullptr)
     965              :     {
     966            0 :         ChipLogProgress(InteractionModel, "no resource for Timed interaction");
     967            0 :         aStatus = Status::Busy;
     968            0 :         return CHIP_ERROR_NO_MEMORY;
     969              :     }
     970              : 
     971              :     // The timed handler takes over handling of this exchange and will do its
     972              :     // own status reporting as needed.
     973            5 :     aStatus = Status::Success;
     974            5 :     apExchangeContext->SetDelegate(handler);
     975            5 :     return handler->OnMessageReceived(apExchangeContext, aPayloadHeader, std::move(aPayload));
     976              : }
     977              : 
     978              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
     979           87 : Status InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext,
     980              :                                                        const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
     981              : {
     982           87 :     System::PacketBufferTLVReader reader;
     983           87 :     reader.Init(aPayload.Retain());
     984              : 
     985           87 :     ReportDataMessage::Parser report;
     986           87 :     VerifyOrReturnError(report.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
     987              : 
     988              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     989           85 :     report.PrettyPrint();
     990              : #endif
     991              : 
     992           85 :     SubscriptionId subscriptionId = 0;
     993           85 :     VerifyOrReturnError(report.GetSubscriptionId(&subscriptionId) == CHIP_NO_ERROR, Status::InvalidAction);
     994           84 :     VerifyOrReturnError(report.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
     995              : 
     996           84 :     ReadClient * foundSubscription = nullptr;
     997          312 :     for (auto * readClient = mpActiveReadClientList; readClient != nullptr; readClient = readClient->GetNextClient())
     998              :     {
     999          228 :         auto peer = apExchangeContext->GetSessionHandle()->GetPeer();
    1000          228 :         if (readClient->GetFabricIndex() != peer.GetFabricIndex() || readClient->GetPeerNodeId() != peer.GetNodeId())
    1001              :         {
    1002          146 :             continue;
    1003              :         }
    1004              : 
    1005              :         // Notify Subscriptions about incoming communication from node
    1006          195 :         readClient->OnUnsolicitedMessageFromPublisher();
    1007              : 
    1008          195 :         if (!readClient->IsSubscriptionActive())
    1009              :         {
    1010            0 :             continue;
    1011              :         }
    1012              : 
    1013          195 :         if (!readClient->IsMatchingSubscriptionId(subscriptionId))
    1014              :         {
    1015          113 :             continue;
    1016              :         }
    1017              : 
    1018           82 :         if (!foundSubscription)
    1019              :         {
    1020           82 :             foundSubscription = readClient;
    1021              :         }
    1022              :     }
    1023              : 
    1024           84 :     if (foundSubscription)
    1025              :     {
    1026           82 :         foundSubscription->OnUnsolicitedReportData(apExchangeContext, std::move(aPayload));
    1027           82 :         return Status::Success;
    1028              :     }
    1029              : 
    1030            2 :     ChipLogDetail(InteractionModel, "Received report with invalid subscriptionId %" PRIu32, subscriptionId);
    1031              : 
    1032            2 :     return Status::InvalidSubscription;
    1033           87 : }
    1034              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
    1035              : 
    1036         2194 : CHIP_ERROR InteractionModelEngine::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader,
    1037              :                                                                 ExchangeDelegate *& newDelegate)
    1038              : {
    1039              :     // TODO: Implement OnUnsolicitedMessageReceived, let messaging layer dispatch message to ReadHandler/ReadClient/TimedHandler
    1040              :     // directly.
    1041         2194 :     newDelegate = this;
    1042         2194 :     return CHIP_NO_ERROR;
    1043              : }
    1044              : 
    1045         2194 : CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext,
    1046              :                                                      const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
    1047              : {
    1048              :     using namespace Protocols::InteractionModel;
    1049              : 
    1050         2194 :     Protocols::InteractionModel::Status status = Status::Failure;
    1051              : 
    1052              :     // Ensure that DataModel::Provider has access to the exchange the message was received on.
    1053         2194 :     CurrentExchangeValueScope scopedExchangeContext(*this, apExchangeContext);
    1054              : 
    1055              :     // Group Message can only be an InvokeCommandRequest or WriteRequest
    1056         2194 :     if (apExchangeContext->IsGroupExchangeContext() &&
    1057         2194 :         !aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::InvokeCommandRequest) &&
    1058            0 :         !aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
    1059              :     {
    1060            0 :         ChipLogProgress(InteractionModel, "Msg type %d not supported for group message", aPayloadHeader.GetMessageType());
    1061            0 :         return CHIP_NO_ERROR;
    1062              :     }
    1063              : 
    1064         2194 :     if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::InvokeCommandRequest))
    1065              :     {
    1066           34 :         status = OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ false);
    1067              :     }
    1068         2160 :     else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReadRequest))
    1069              :     {
    1070          795 :         status = OnReadInitialRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), ReadHandler::InteractionType::Read);
    1071              :     }
    1072         1365 :     else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
    1073              :     {
    1074          955 :         status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ false);
    1075              :     }
    1076          410 :     else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::SubscribeRequest))
    1077              :     {
    1078          311 :         status =
    1079          311 :             OnReadInitialRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), ReadHandler::InteractionType::Subscribe);
    1080              :     }
    1081              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
    1082           99 :     else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReportData))
    1083              :     {
    1084           87 :         status = OnUnsolicitedReportData(apExchangeContext, aPayloadHeader, std::move(aPayload));
    1085              :     }
    1086              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
    1087           12 :     else if (aPayloadHeader.HasMessageType(MsgType::TimedRequest))
    1088              :     {
    1089            5 :         OnTimedRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), status);
    1090              :     }
    1091              :     else
    1092              :     {
    1093            7 :         ChipLogProgress(InteractionModel, "Msg type %d not supported", aPayloadHeader.GetMessageType());
    1094            7 :         status = Status::InvalidAction;
    1095              :     }
    1096              : 
    1097         2194 :     if (status != Status::Success && !apExchangeContext->IsGroupExchangeContext())
    1098              :     {
    1099           32 :         return StatusResponse::Send(status, apExchangeContext, false /*aExpectResponse*/);
    1100              :     }
    1101              : 
    1102         2162 :     return CHIP_NO_ERROR;
    1103         2194 : }
    1104              : 
    1105            0 : void InteractionModelEngine::OnResponseTimeout(Messaging::ExchangeContext * ec)
    1106              : {
    1107            0 :     ChipLogError(InteractionModel, "Time out! Failed to receive IM response from Exchange: " ChipLogFormatExchange,
    1108              :                  ChipLogValueExchange(ec));
    1109            0 : }
    1110              : 
    1111              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
    1112            9 : void InteractionModelEngine::OnActiveModeNotification(ScopedNodeId aPeer, uint64_t aMonitoredSubject)
    1113              : {
    1114           13 :     for (ReadClient * pListItem = mpActiveReadClientList; pListItem != nullptr;)
    1115              :     {
    1116            4 :         auto pNextItem = pListItem->GetNextClient();
    1117              :         // It is possible that pListItem is destroyed by the app in OnActiveModeNotification.
    1118              :         // Get the next item before invoking `OnActiveModeNotification`.
    1119            4 :         CATValues cats;
    1120              : 
    1121            4 :         mpFabricTable->FetchCATs(pListItem->GetFabricIndex(), cats);
    1122           12 :         if (ScopedNodeId(pListItem->GetPeerNodeId(), pListItem->GetFabricIndex()) == aPeer &&
    1123            8 :             (cats.CheckSubjectAgainstCATs(aMonitoredSubject) ||
    1124            4 :              aMonitoredSubject == mpFabricTable->FindFabricWithIndex(pListItem->GetFabricIndex())->GetNodeId()))
    1125              :         {
    1126            3 :             pListItem->OnActiveModeNotification();
    1127              :         }
    1128            4 :         pListItem = pNextItem;
    1129              :     }
    1130            9 : }
    1131              : 
    1132            5 : void InteractionModelEngine::OnPeerTypeChange(ScopedNodeId aPeer, ReadClient::PeerType aType)
    1133              : {
    1134              :     // TODO: Follow up to use a iterator function to avoid copy/paste here.
    1135            8 :     for (ReadClient * pListItem = mpActiveReadClientList; pListItem != nullptr;)
    1136              :     {
    1137              :         // It is possible that pListItem is destroyed by the app in OnPeerTypeChange.
    1138              :         // Get the next item before invoking `OnPeerTypeChange`.
    1139            3 :         auto pNextItem = pListItem->GetNextClient();
    1140            3 :         if (ScopedNodeId(pListItem->GetPeerNodeId(), pListItem->GetFabricIndex()) == aPeer)
    1141              :         {
    1142            3 :             pListItem->OnPeerTypeChange(aType);
    1143              :         }
    1144            3 :         pListItem = pNextItem;
    1145              :     }
    1146            5 : }
    1147              : 
    1148          317 : void InteractionModelEngine::AddReadClient(ReadClient * apReadClient)
    1149              : {
    1150          317 :     apReadClient->SetNextClient(mpActiveReadClientList);
    1151          317 :     mpActiveReadClientList = apReadClient;
    1152          317 : }
    1153              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
    1154              : 
    1155            6 : bool InteractionModelEngine::TrimFabricForSubscriptions(FabricIndex aFabricIndex, bool aForceEvict)
    1156              : {
    1157            6 :     const size_t pathPoolCapacity        = GetPathPoolCapacityForSubscriptions();
    1158            6 :     const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions();
    1159              : 
    1160            6 :     uint8_t fabricCount                            = mpFabricTable->FabricCount();
    1161            6 :     size_t attributePathsSubscribedByCurrentFabric = 0;
    1162            6 :     size_t eventPathsSubscribedByCurrentFabric     = 0;
    1163            6 :     size_t subscriptionsEstablishedByCurrentFabric = 0;
    1164              : 
    1165            6 :     if (fabricCount == 0)
    1166              :     {
    1167            0 :         return false;
    1168              :     }
    1169              : 
    1170              :     // Note: This is OK only when we have assumed the fabricCount is not zero. Should be revised when adding support to
    1171              :     // subscriptions on PASE sessions.
    1172            6 :     size_t perFabricPathCapacity         = pathPoolCapacity / static_cast<size_t>(fabricCount);
    1173            6 :     size_t perFabricSubscriptionCapacity = readHandlerPoolCapacity / static_cast<size_t>(fabricCount);
    1174              : 
    1175            6 :     ReadHandler * candidate            = nullptr;
    1176            6 :     size_t candidateAttributePathsUsed = 0;
    1177            6 :     size_t candidateEventPathsUsed     = 0;
    1178              : 
    1179              :     // It is safe to use & here since this function will be called on current stack.
    1180            6 :     mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
    1181           47 :         if (handler->GetAccessingFabricIndex() != aFabricIndex || !handler->IsType(ReadHandler::InteractionType::Subscribe))
    1182              :         {
    1183           13 :             return Loop::Continue;
    1184              :         }
    1185              : 
    1186           34 :         size_t attributePathsUsed = handler->GetAttributePathCount();
    1187           34 :         size_t eventPathsUsed     = handler->GetEventPathCount();
    1188              : 
    1189           34 :         attributePathsSubscribedByCurrentFabric += attributePathsUsed;
    1190           34 :         eventPathsSubscribedByCurrentFabric += eventPathsUsed;
    1191           34 :         subscriptionsEstablishedByCurrentFabric++;
    1192              : 
    1193           34 :         if (candidate == nullptr)
    1194              :         {
    1195            6 :             candidate = handler;
    1196              :         }
    1197              :         // This handler uses more resources than the one we picked before.
    1198           28 :         else if ((attributePathsUsed > perFabricPathCapacity || eventPathsUsed > perFabricPathCapacity) &&
    1199            0 :                  (candidateAttributePathsUsed <= perFabricPathCapacity && candidateEventPathsUsed <= perFabricPathCapacity))
    1200              :         {
    1201            0 :             candidate                   = handler;
    1202            0 :             candidateAttributePathsUsed = attributePathsUsed;
    1203            0 :             candidateEventPathsUsed     = eventPathsUsed;
    1204              :         }
    1205              :         // This handler is older than the one we picked before.
    1206           28 :         else if (handler->GetTransactionStartGeneration() < candidate->GetTransactionStartGeneration() &&
    1207              :                  // And the level of resource usage is the same (both exceed or neither exceed)
    1208            0 :                  ((attributePathsUsed > perFabricPathCapacity || eventPathsUsed > perFabricPathCapacity) ==
    1209            0 :                   (candidateAttributePathsUsed > perFabricPathCapacity || candidateEventPathsUsed > perFabricPathCapacity)))
    1210              :         {
    1211            0 :             candidate = handler;
    1212              :         }
    1213           34 :         return Loop::Continue;
    1214              :     });
    1215              : 
    1216            6 :     if (candidate != nullptr &&
    1217            6 :         (aForceEvict || attributePathsSubscribedByCurrentFabric > perFabricPathCapacity ||
    1218            0 :          eventPathsSubscribedByCurrentFabric > perFabricPathCapacity ||
    1219            0 :          subscriptionsEstablishedByCurrentFabric > perFabricSubscriptionCapacity))
    1220              :     {
    1221              :         SubscriptionId subId;
    1222            6 :         candidate->GetSubscriptionId(subId);
    1223            6 :         ChipLogProgress(DataManagement, "Evicting Subscription ID %u:0x%" PRIx32, candidate->GetSubjectDescriptor().fabricIndex,
    1224              :                         subId);
    1225            6 :         candidate->Close();
    1226            6 :         return true;
    1227              :     }
    1228            0 :     return false;
    1229              : }
    1230              : 
    1231          305 : bool InteractionModelEngine::EnsureResourceForSubscription(FabricIndex aFabricIndex, size_t aRequestedAttributePathCount,
    1232              :                                                            size_t aRequestedEventPathCount)
    1233              : {
    1234              : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
    1235              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
    1236          305 :     const bool allowUnlimited = !mForceHandlerQuota;
    1237              : #else  // CONFIG_BUILD_FOR_HOST_UNIT_TEST
    1238              :        // If the resources are allocated on the heap, we should be able to handle as many Read / Subscribe requests as possible.
    1239              :     const bool allowUnlimited = true;
    1240              : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
    1241              : #else  // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
    1242              :     const bool allowUnlimited = false;
    1243              : #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
    1244              : 
    1245              :     // Don't couple with read requests, always reserve enough resource for read requests.
    1246              : 
    1247          305 :     const size_t pathPoolCapacity        = GetPathPoolCapacityForSubscriptions();
    1248          305 :     const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions();
    1249              : 
    1250              :     // If we return early here, the compiler will complain about the unreachable code, so we add a always-true check.
    1251          305 :     const size_t attributePathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity;
    1252          305 :     const size_t eventPathCap     = allowUnlimited ? SIZE_MAX : pathPoolCapacity;
    1253          305 :     const size_t readHandlerCap   = allowUnlimited ? SIZE_MAX : readHandlerPoolCapacity;
    1254              : 
    1255          305 :     size_t usedAttributePaths = 0;
    1256          305 :     size_t usedEventPaths     = 0;
    1257          305 :     size_t usedReadHandlers   = 0;
    1258              : 
    1259          311 :     auto countResourceUsage = [&]() {
    1260          311 :         usedAttributePaths = 0;
    1261          311 :         usedEventPaths     = 0;
    1262          311 :         usedReadHandlers   = 0;
    1263          311 :         mReadHandlers.ForEachActiveObject([&](auto * handler) {
    1264         6388 :             if (!handler->IsType(ReadHandler::InteractionType::Subscribe))
    1265              :             {
    1266           34 :                 return Loop::Continue;
    1267              :             }
    1268         6354 :             usedAttributePaths += handler->GetAttributePathCount();
    1269         6354 :             usedEventPaths += handler->GetEventPathCount();
    1270         6354 :             usedReadHandlers++;
    1271         6354 :             return Loop::Continue;
    1272              :         });
    1273          616 :     };
    1274              : 
    1275          305 :     countResourceUsage();
    1276              : 
    1277          305 :     if (usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
    1278          298 :         usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap)
    1279              :     {
    1280              :         // We have enough resources, then we serve the requests in a best-effort manner.
    1281          298 :         return true;
    1282              :     }
    1283              : 
    1284            7 :     if ((aRequestedAttributePathCount > kMinSupportedPathsPerSubscription &&
    1285            7 :          usedAttributePaths + aRequestedAttributePathCount > attributePathCap) ||
    1286            0 :         (aRequestedEventPathCount > kMinSupportedPathsPerSubscription && usedEventPaths + aRequestedEventPathCount > eventPathCap))
    1287              :     {
    1288              :         // We cannot offer enough resources, and the subscription is requesting more than the spec limit.
    1289            2 :         return false;
    1290              :     }
    1291              : 
    1292            6 :     const auto evictAndUpdateResourceUsage = [&](FabricIndex fabricIndex, bool forceEvict) {
    1293            6 :         bool ret = TrimFabricForSubscriptions(fabricIndex, forceEvict);
    1294            6 :         countResourceUsage();
    1295            6 :         return ret;
    1296            5 :     };
    1297              : 
    1298              :     //
    1299              :     // At this point, we have an inbound request that respects minimas but we still don't have enough resources to handle it. Which
    1300              :     // means that we definitely have handlers on existing fabrics that are over limits and need to evict at least one of them to
    1301              :     // make space.
    1302              :     //
    1303              :     // There might be cases that one fabric has lots of subscriptions with one interested path, while the other fabrics are not
    1304              :     // using excess resources. So we need to do this multiple times until we have enough room or no fabrics are using excess
    1305              :     // resources.
    1306              :     //
    1307            5 :     bool didEvictHandler = true;
    1308           16 :     while (didEvictHandler)
    1309              :     {
    1310           11 :         didEvictHandler = false;
    1311           18 :         for (const auto & fabric : *mpFabricTable)
    1312              :         {
    1313              :             // The resources are enough to serve this request, do not evict anything.
    1314           17 :             if (usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
    1315           10 :                 usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap)
    1316              :             {
    1317           10 :                 break;
    1318              :             }
    1319            7 :             didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(fabric.GetFabricIndex(), false);
    1320              :         }
    1321              :     }
    1322              : 
    1323              :     // The above loop cannot guarantee the resources for the new subscriptions when the resource usage from all fabrics are exactly
    1324              :     // within the quota (which means we have exactly used all resources). Evict (from the large subscriptions first then from
    1325              :     // oldest) subscriptions from the current fabric until we have enough resource for the new subscription.
    1326            5 :     didEvictHandler = true;
    1327            5 :     while ((usedAttributePaths + aRequestedAttributePathCount > attributePathCap ||
    1328            5 :             usedEventPaths + aRequestedEventPathCount > eventPathCap || usedReadHandlers >= readHandlerCap) &&
    1329              :            // Avoid infinity loop
    1330              :            didEvictHandler)
    1331              :     {
    1332            0 :         didEvictHandler = evictAndUpdateResourceUsage(aFabricIndex, true);
    1333              :     }
    1334              : 
    1335              :     // If didEvictHandler is false, means the loop above evicted all subscriptions from the current fabric but we still don't have
    1336              :     // enough resources for the new subscription, this should never happen.
    1337              :     // This is safe as long as we have rejected subscriptions without a fabric associated (with a PASE session) before.
    1338              :     // Note: Spec#5141: should reject subscription requests on PASE sessions.
    1339            5 :     VerifyOrDieWithMsg(didEvictHandler, DataManagement, "Failed to get required resources by evicting existing subscriptions.");
    1340              : 
    1341              :     // We have ensured enough resources by the logic above.
    1342            5 :     return true;
    1343              : }
    1344              : 
    1345           31 : bool InteractionModelEngine::TrimFabricForRead(FabricIndex aFabricIndex)
    1346              : {
    1347           31 :     const size_t guaranteedReadRequestsPerFabric   = GetGuaranteedReadRequestsPerFabric();
    1348           31 :     const size_t minSupportedPathsPerFabricForRead = guaranteedReadRequestsPerFabric * kMinSupportedPathsPerReadRequest;
    1349              : 
    1350           31 :     size_t attributePathsUsedByCurrentFabric = 0;
    1351           31 :     size_t eventPathsUsedByCurrentFabric     = 0;
    1352           31 :     size_t readTransactionsOnCurrentFabric   = 0;
    1353              : 
    1354           31 :     ReadHandler * candidate            = nullptr;
    1355           31 :     size_t candidateAttributePathsUsed = 0;
    1356           31 :     size_t candidateEventPathsUsed     = 0;
    1357              : 
    1358              :     // It is safe to use & here since this function will be called on current stack.
    1359           31 :     mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
    1360           81 :         if (handler->GetAccessingFabricIndex() != aFabricIndex || !handler->IsType(ReadHandler::InteractionType::Read))
    1361              :         {
    1362           49 :             return Loop::Continue;
    1363              :         }
    1364              : 
    1365           32 :         size_t attributePathsUsed = handler->GetAttributePathCount();
    1366           32 :         size_t eventPathsUsed     = handler->GetEventPathCount();
    1367              : 
    1368           32 :         attributePathsUsedByCurrentFabric += attributePathsUsed;
    1369           32 :         eventPathsUsedByCurrentFabric += eventPathsUsed;
    1370           32 :         readTransactionsOnCurrentFabric++;
    1371              : 
    1372           32 :         if (candidate == nullptr)
    1373              :         {
    1374           18 :             candidate = handler;
    1375              :         }
    1376              :         // Oversized read handlers will be evicted first.
    1377           14 :         else if ((attributePathsUsed > kMinSupportedPathsPerReadRequest || eventPathsUsed > kMinSupportedPathsPerReadRequest) &&
    1378            3 :                  (candidateAttributePathsUsed <= kMinSupportedPathsPerReadRequest &&
    1379            1 :                   candidateEventPathsUsed <= kMinSupportedPathsPerReadRequest))
    1380              :         {
    1381            1 :             candidate = handler;
    1382              :         }
    1383              :         // Read Handlers are "first come first served", so we give eariler read transactions a higher priority.
    1384           16 :         else if (handler->GetTransactionStartGeneration() > candidate->GetTransactionStartGeneration() &&
    1385              :                  // And the level of resource usage is the same (both exceed or neither exceed)
    1386            3 :                  ((attributePathsUsed > kMinSupportedPathsPerReadRequest || eventPathsUsed > kMinSupportedPathsPerReadRequest) ==
    1387            4 :                   (candidateAttributePathsUsed > kMinSupportedPathsPerReadRequest ||
    1388            1 :                    candidateEventPathsUsed > kMinSupportedPathsPerReadRequest)))
    1389              :         {
    1390            1 :             candidate = handler;
    1391              :         }
    1392              : 
    1393           32 :         if (candidate == handler)
    1394              :         {
    1395           20 :             candidateAttributePathsUsed = attributePathsUsed;
    1396           20 :             candidateEventPathsUsed     = eventPathsUsed;
    1397              :         }
    1398           32 :         return Loop::Continue;
    1399              :     });
    1400              : 
    1401           49 :     if (candidate != nullptr &&
    1402           18 :         ((attributePathsUsedByCurrentFabric > minSupportedPathsPerFabricForRead ||
    1403           13 :           eventPathsUsedByCurrentFabric > minSupportedPathsPerFabricForRead ||
    1404           13 :           readTransactionsOnCurrentFabric > guaranteedReadRequestsPerFabric) ||
    1405              :          // Always evict the transactions on PASE sessions if the fabric table is full.
    1406            9 :          (aFabricIndex == kUndefinedFabricIndex && mpFabricTable->FabricCount() == GetConfigMaxFabrics())))
    1407              :     {
    1408           13 :         candidate->Close();
    1409           13 :         return true;
    1410              :     }
    1411           18 :     return false;
    1412              : }
    1413              : 
    1414          791 : Protocols::InteractionModel::Status InteractionModelEngine::EnsureResourceForRead(FabricIndex aFabricIndex,
    1415              :                                                                                   size_t aRequestedAttributePathCount,
    1416              :                                                                                   size_t aRequestedEventPathCount)
    1417              : {
    1418              : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
    1419              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
    1420          791 :     const bool allowUnlimited = !mForceHandlerQuota;
    1421              : #else  // CONFIG_BUILD_FOR_HOST_UNIT_TEST
    1422              :        // If the resources are allocated on the heap, we should be able to handle as many Read / Subscribe requests as possible.
    1423              :     const bool allowUnlimited = true;
    1424              : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
    1425              : #else  // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
    1426              :     const bool allowUnlimited = false;
    1427              : #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
    1428              : 
    1429              :     // If we return early here, the compiler will complain about the unreachable code, so we add a always-true check.
    1430          791 :     const size_t attributePathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads();
    1431          791 :     const size_t eventPathCap     = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads();
    1432          791 :     const size_t readHandlerCap   = allowUnlimited ? SIZE_MAX : GetReadHandlerPoolCapacityForReads();
    1433              : 
    1434          791 :     const size_t guaranteedReadRequestsPerFabric = GetGuaranteedReadRequestsPerFabric();
    1435          791 :     const size_t guaranteedPathsPerFabric        = kMinSupportedPathsPerReadRequest * guaranteedReadRequestsPerFabric;
    1436              : 
    1437          791 :     size_t usedAttributePaths = 0;
    1438          791 :     size_t usedEventPaths     = 0;
    1439          791 :     size_t usedReadHandlers   = 0;
    1440              : 
    1441          822 :     auto countResourceUsage = [&]() {
    1442          822 :         usedAttributePaths = 0;
    1443          822 :         usedEventPaths     = 0;
    1444          822 :         usedReadHandlers   = 0;
    1445          822 :         mReadHandlers.ForEachActiveObject([&](auto * handler) {
    1446          178 :             if (!handler->IsType(ReadHandler::InteractionType::Read))
    1447              :             {
    1448            7 :                 return Loop::Continue;
    1449              :             }
    1450          171 :             usedAttributePaths += handler->GetAttributePathCount();
    1451          171 :             usedEventPaths += handler->GetEventPathCount();
    1452          171 :             usedReadHandlers++;
    1453          171 :             return Loop::Continue;
    1454              :         });
    1455         1613 :     };
    1456              : 
    1457          835 :     auto haveEnoughResourcesForTheRequest = [&]() {
    1458         1652 :         return usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
    1459          835 :             usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap;
    1460          791 :     };
    1461              : 
    1462          791 :     countResourceUsage();
    1463              : 
    1464          791 :     if (haveEnoughResourcesForTheRequest())
    1465              :     {
    1466              :         // We have enough resources, then we serve the requests in a best-effort manner.
    1467          773 :         return Status::Success;
    1468              :     }
    1469              : 
    1470           18 :     if ((aRequestedAttributePathCount > kMinSupportedPathsPerReadRequest &&
    1471            4 :          usedAttributePaths + aRequestedAttributePathCount > attributePathCap) ||
    1472           15 :         (aRequestedEventPathCount > kMinSupportedPathsPerReadRequest && usedEventPaths + aRequestedEventPathCount > eventPathCap))
    1473              :     {
    1474              :         // We cannot offer enough resources, and the read transaction is requesting more than the spec limit.
    1475            3 :         return Status::PathsExhausted;
    1476              :     }
    1477              : 
    1478              :     // If we have commissioned CHIP_CONFIG_MAX_FABRICS already, and this transaction doesn't have an associated fabric index, reject
    1479              :     // the request if we don't have sufficient resources for this request.
    1480           15 :     if (mpFabricTable->FabricCount() == GetConfigMaxFabrics() && aFabricIndex == kUndefinedFabricIndex)
    1481              :     {
    1482            1 :         return Status::Busy;
    1483              :     }
    1484              : 
    1485           14 :     size_t usedAttributePathsInFabric = 0;
    1486           14 :     size_t usedEventPathsInFabric     = 0;
    1487           14 :     size_t usedReadHandlersInFabric   = 0;
    1488           14 :     mReadHandlers.ForEachActiveObject([&](auto * handler) {
    1489           35 :         if (!handler->IsType(ReadHandler::InteractionType::Read) || handler->GetAccessingFabricIndex() != aFabricIndex)
    1490              :         {
    1491           33 :             return Loop::Continue;
    1492              :         }
    1493            2 :         usedAttributePathsInFabric += handler->GetAttributePathCount();
    1494            2 :         usedEventPathsInFabric += handler->GetEventPathCount();
    1495            2 :         usedReadHandlersInFabric++;
    1496            2 :         return Loop::Continue;
    1497              :     });
    1498              : 
    1499              :     // Busy, since there are already some read requests ongoing on this fabric, please retry later.
    1500           14 :     if (usedAttributePathsInFabric + aRequestedAttributePathCount > guaranteedPathsPerFabric ||
    1501           11 :         usedEventPathsInFabric + aRequestedEventPathCount > guaranteedPathsPerFabric ||
    1502           11 :         usedReadHandlersInFabric >= guaranteedReadRequestsPerFabric)
    1503              :     {
    1504            3 :         return Status::Busy;
    1505              :     }
    1506              : 
    1507           31 :     const auto evictAndUpdateResourceUsage = [&](FabricIndex fabricIndex) {
    1508           31 :         bool ret = TrimFabricForRead(fabricIndex);
    1509           31 :         countResourceUsage();
    1510           31 :         return ret;
    1511           11 :     };
    1512              : 
    1513              :     //
    1514              :     // At this point, we have an inbound request that respects minimas but we still don't have enough resources to handle it. Which
    1515              :     // means that we definitely have handlers on existing fabrics that are over limits and need to evict at least one of them to
    1516              :     // make space.
    1517              :     //
    1518           11 :     bool didEvictHandler = true;
    1519           21 :     while (didEvictHandler)
    1520              :     {
    1521           21 :         didEvictHandler = false;
    1522           21 :         didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(kUndefinedFabricIndex);
    1523           21 :         if (haveEnoughResourcesForTheRequest())
    1524              :         {
    1525           11 :             break;
    1526              :         }
    1527              :         // If the fabric table is full, we won't evict read requests from normal fabrics before we have evicted all read requests
    1528              :         // from PASE sessions.
    1529           10 :         if (mpFabricTable->FabricCount() == GetConfigMaxFabrics() && didEvictHandler)
    1530              :         {
    1531            1 :             continue;
    1532              :         }
    1533           13 :         for (const auto & fabric : *mpFabricTable)
    1534              :         {
    1535           12 :             didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(fabric.GetFabricIndex());
    1536              :             // If we now have enough resources to serve this request, stop evicting things.
    1537           12 :             if (haveEnoughResourcesForTheRequest())
    1538              :             {
    1539            8 :                 break;
    1540              :             }
    1541              :         }
    1542              :     }
    1543              : 
    1544              :     // Now all fabrics are not oversized (since we have trimmed the oversized fabrics in the loop above), and the read handler is
    1545              :     // also not oversized, we should be able to handle this read transaction.
    1546           11 :     VerifyOrDie(haveEnoughResourcesForTheRequest());
    1547              : 
    1548           11 :     return Status::Success;
    1549              : }
    1550              : 
    1551              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
    1552          152 : void InteractionModelEngine::RemoveReadClient(ReadClient * apReadClient)
    1553              : {
    1554          152 :     ReadClient * pPrevListItem = nullptr;
    1555          152 :     ReadClient * pCurListItem  = mpActiveReadClientList;
    1556              : 
    1557          157 :     while (pCurListItem != apReadClient)
    1558              :     {
    1559              :         //
    1560              :         // Item must exist in this tracker list. If not, there's a bug somewhere.
    1561              :         //
    1562            5 :         VerifyOrDie(pCurListItem != nullptr);
    1563              : 
    1564            5 :         pPrevListItem = pCurListItem;
    1565            5 :         pCurListItem  = pCurListItem->GetNextClient();
    1566              :     }
    1567              : 
    1568          152 :     if (pPrevListItem)
    1569              :     {
    1570            3 :         pPrevListItem->SetNextClient(apReadClient->GetNextClient());
    1571              :     }
    1572              :     else
    1573              :     {
    1574          149 :         mpActiveReadClientList = apReadClient->GetNextClient();
    1575              :     }
    1576              : 
    1577          152 :     apReadClient->SetNextClient(nullptr);
    1578          152 : }
    1579              : 
    1580           95 : size_t InteractionModelEngine::GetNumActiveReadClients()
    1581              : {
    1582           95 :     ReadClient * pListItem = mpActiveReadClientList;
    1583           95 :     size_t count           = 0;
    1584              : 
    1585           95 :     while (pListItem)
    1586              :     {
    1587            0 :         pListItem = pListItem->GetNextClient();
    1588            0 :         count++;
    1589              :     }
    1590              : 
    1591           95 :     return count;
    1592              : }
    1593              : 
    1594           14 : bool InteractionModelEngine::InActiveReadClientList(ReadClient * apReadClient)
    1595              : {
    1596           14 :     ReadClient * pListItem = mpActiveReadClientList;
    1597              : 
    1598           14 :     while (pListItem)
    1599              :     {
    1600           14 :         if (pListItem == apReadClient)
    1601              :         {
    1602           14 :             return true;
    1603              :         }
    1604              : 
    1605            0 :         pListItem = pListItem->GetNextClient();
    1606              :     }
    1607              : 
    1608            0 :     return false;
    1609              : }
    1610              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
    1611              : 
    1612         5214 : bool InteractionModelEngine::HasConflictWriteRequests(const WriteHandler * apWriteHandler, const ConcreteAttributePath & aPath)
    1613              : {
    1614        26038 :     for (auto & writeHandler : mWriteHandlers)
    1615              :     {
    1616        20832 :         if (writeHandler.IsFree() || &writeHandler == apWriteHandler)
    1617              :         {
    1618        20810 :             continue;
    1619              :         }
    1620           22 :         if (writeHandler.IsCurrentlyProcessingWritePath(aPath))
    1621              :         {
    1622            8 :             return true;
    1623              :         }
    1624              :     }
    1625         5206 :     return false;
    1626              : }
    1627              : 
    1628         1142 : void InteractionModelEngine::ReleaseAttributePathList(SingleLinkedListNode<AttributePathParams> *& aAttributePathList)
    1629              : {
    1630         1142 :     ReleasePool(aAttributePathList, mAttributePathPool);
    1631         1142 : }
    1632              : 
    1633         1563 : CHIP_ERROR InteractionModelEngine::PushFrontAttributePathList(SingleLinkedListNode<AttributePathParams> *& aAttributePathList,
    1634              :                                                               AttributePathParams & aAttributePath)
    1635              : {
    1636         1563 :     CHIP_ERROR err = PushFront(aAttributePathList, aAttributePath, mAttributePathPool);
    1637         1563 :     if (err == CHIP_ERROR_NO_MEMORY)
    1638              :     {
    1639            0 :         ChipLogError(InteractionModel, "AttributePath pool full");
    1640            0 :         return CHIP_IM_GLOBAL_STATUS(PathsExhausted);
    1641              :     }
    1642         1563 :     return err;
    1643              : }
    1644              : 
    1645         1614 : std::optional<DataModel::AttributeEntry> InteractionModelEngine::FindAttributeEntry(const ConcreteAttributePath & path)
    1646              : {
    1647         1614 :     DataModel::AttributeFinder finder(mDataModelProvider);
    1648         1614 :     return finder.Find(path);
    1649         1614 : }
    1650              : 
    1651          976 : void InteractionModelEngine::RemoveDuplicateConcreteAttributePath(SingleLinkedListNode<AttributePathParams> *& aAttributePaths)
    1652              : {
    1653          976 :     SingleLinkedListNode<AttributePathParams> * prev = nullptr;
    1654          976 :     auto * path1                                     = aAttributePaths;
    1655              : 
    1656         2536 :     while (path1 != nullptr)
    1657              :     {
    1658         1560 :         bool duplicate = false;
    1659              : 
    1660              :         // skip all wildcard paths and invalid concrete attribute
    1661         2814 :         if (path1->mValue.IsWildcardPath() ||
    1662         2508 :             !FindAttributeEntry(
    1663         2814 :                  ConcreteAttributePath(path1->mValue.mEndpointId, path1->mValue.mClusterId, path1->mValue.mAttributeId))
    1664         1254 :                  .has_value())
    1665              :         {
    1666          576 :             prev  = path1;
    1667          576 :             path1 = path1->mpNext;
    1668          576 :             continue;
    1669              :         }
    1670              : 
    1671              :         // Check whether a wildcard path expands to something that includes this concrete path.
    1672         3344 :         for (auto * path2 = aAttributePaths; path2 != nullptr; path2 = path2->mpNext)
    1673              :         {
    1674         2369 :             if (path2 == path1)
    1675              :             {
    1676          980 :                 continue;
    1677              :             }
    1678              : 
    1679         1389 :             if (path2->mValue.IsWildcardPath() && path2->mValue.IsAttributePathSupersetOf(path1->mValue))
    1680              :             {
    1681            9 :                 duplicate = true;
    1682            9 :                 break;
    1683              :             }
    1684              :         }
    1685              : 
    1686              :         // if path1 duplicates something from wildcard expansion, discard path1
    1687          984 :         if (!duplicate)
    1688              :         {
    1689          975 :             prev  = path1;
    1690          975 :             path1 = path1->mpNext;
    1691          975 :             continue;
    1692              :         }
    1693              : 
    1694            9 :         if (path1 == aAttributePaths)
    1695              :         {
    1696            5 :             aAttributePaths = path1->mpNext;
    1697            5 :             mAttributePathPool.ReleaseObject(path1);
    1698            5 :             path1 = aAttributePaths;
    1699              :         }
    1700              :         else
    1701              :         {
    1702            4 :             prev->mpNext = path1->mpNext;
    1703            4 :             mAttributePathPool.ReleaseObject(path1);
    1704            4 :             path1 = prev->mpNext;
    1705              :         }
    1706              :     }
    1707          976 : }
    1708              : 
    1709         1134 : void InteractionModelEngine::ReleaseEventPathList(SingleLinkedListNode<EventPathParams> *& aEventPathList)
    1710              : {
    1711         1134 :     ReleasePool(aEventPathList, mEventPathPool);
    1712         1134 : }
    1713              : 
    1714          497 : CHIP_ERROR InteractionModelEngine::PushFrontEventPathParamsList(SingleLinkedListNode<EventPathParams> *& aEventPathList,
    1715              :                                                                 EventPathParams & aEventPath)
    1716              : {
    1717          497 :     CHIP_ERROR err = PushFront(aEventPathList, aEventPath, mEventPathPool);
    1718          497 :     if (err == CHIP_ERROR_NO_MEMORY)
    1719              :     {
    1720            0 :         ChipLogError(InteractionModel, "EventPath pool full");
    1721            0 :         return CHIP_IM_GLOBAL_STATUS(PathsExhausted);
    1722              :     }
    1723          497 :     return err;
    1724              : }
    1725              : 
    1726         2219 : void InteractionModelEngine::ReleaseDataVersionFilterList(SingleLinkedListNode<DataVersionFilter> *& aDataVersionFilterList)
    1727              : {
    1728         2219 :     ReleasePool(aDataVersionFilterList, mDataVersionFilterPool);
    1729         2219 : }
    1730              : 
    1731         2492 : CHIP_ERROR InteractionModelEngine::PushFrontDataVersionFilterList(SingleLinkedListNode<DataVersionFilter> *& aDataVersionFilterList,
    1732              :                                                                   DataVersionFilter & aDataVersionFilter)
    1733              : {
    1734         2492 :     CHIP_ERROR err = PushFront(aDataVersionFilterList, aDataVersionFilter, mDataVersionFilterPool);
    1735         2492 :     if (err == CHIP_ERROR_NO_MEMORY)
    1736              :     {
    1737            0 :         ChipLogError(InteractionModel, "DataVersionFilter pool full, ignore this filter");
    1738            0 :         err = CHIP_NO_ERROR;
    1739              :     }
    1740         2492 :     return err;
    1741              : }
    1742              : 
    1743              : template <typename T, size_t N>
    1744         4495 : void InteractionModelEngine::ReleasePool(SingleLinkedListNode<T> *& aObjectList,
    1745              :                                          ObjectPool<SingleLinkedListNode<T>, N> & aObjectPool)
    1746              : {
    1747         4495 :     SingleLinkedListNode<T> * current = aObjectList;
    1748         9038 :     while (current != nullptr)
    1749              :     {
    1750         4543 :         SingleLinkedListNode<T> * nextObject = current->mpNext;
    1751         4543 :         aObjectPool.ReleaseObject(current);
    1752         4543 :         current = nextObject;
    1753              :     }
    1754              : 
    1755         4495 :     aObjectList = nullptr;
    1756         4495 : }
    1757              : 
    1758              : template <typename T, size_t N>
    1759         4552 : CHIP_ERROR InteractionModelEngine::PushFront(SingleLinkedListNode<T> *& aObjectList, T & aData,
    1760              :                                              ObjectPool<SingleLinkedListNode<T>, N> & aObjectPool)
    1761              : {
    1762         4552 :     SingleLinkedListNode<T> * object = aObjectPool.CreateObject();
    1763         4552 :     if (object == nullptr)
    1764              :     {
    1765            0 :         return CHIP_ERROR_NO_MEMORY;
    1766              :     }
    1767         4552 :     object->mValue = aData;
    1768         4552 :     object->mpNext = aObjectList;
    1769         4552 :     aObjectList    = object;
    1770         4552 :     return CHIP_NO_ERROR;
    1771              : }
    1772              : 
    1773           25 : void InteractionModelEngine::DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
    1774              :                                              TLV::TLVReader & apPayload)
    1775              : {
    1776           25 :     Access::SubjectDescriptor subjectDescriptor = apCommandObj.GetSubjectDescriptor();
    1777              : 
    1778           25 :     DataModel::InvokeRequest request;
    1779           25 :     request.path = aCommandPath;
    1780           25 :     request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, apCommandObj.IsTimedInvoke());
    1781           25 :     request.subjectDescriptor = &subjectDescriptor;
    1782              : 
    1783           25 :     std::optional<DataModel::ActionReturnStatus> status = GetDataModelProvider()->InvokeCommand(request, apPayload, &apCommandObj);
    1784              : 
    1785              :     // Provider indicates that handler status or data was already set (or will be set asynchronously) by
    1786              :     // returning std::nullopt. If any other value is returned, it is requesting that a status is set. This
    1787              :     // includes CHIP_NO_ERROR: in this case CHIP_NO_ERROR would mean set a `status success on the command`
    1788           25 :     if (status.has_value())
    1789              :     {
    1790            0 :         apCommandObj.AddStatus(aCommandPath, status->GetStatusCode());
    1791              :     }
    1792           25 : }
    1793              : 
    1794           32 : Protocols::InteractionModel::Status InteractionModelEngine::ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request)
    1795              : {
    1796              : 
    1797           32 :     DataModel::AcceptedCommandEntry acceptedCommandEntry;
    1798              : 
    1799           32 :     Status status = CheckCommandExistence(request.path, acceptedCommandEntry);
    1800              : 
    1801           32 :     if (status != Status::Success)
    1802              :     {
    1803            7 :         ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint %u",
    1804              :                       ChipLogValueMEI(request.path.mCommandId), ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId);
    1805            7 :         return status;
    1806              :     }
    1807              : 
    1808           25 :     status = CheckCommandAccess(request, acceptedCommandEntry);
    1809           25 :     VerifyOrReturnValue(status == Status::Success, status);
    1810              : 
    1811           25 :     return CheckCommandFlags(request, acceptedCommandEntry);
    1812              : }
    1813              : 
    1814           25 : Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(const DataModel::InvokeRequest & aRequest,
    1815              :                                                                                const DataModel::AcceptedCommandEntry & entry)
    1816              : {
    1817           25 :     if (aRequest.subjectDescriptor == nullptr)
    1818              :     {
    1819            0 :         return Status::UnsupportedAccess; // we require a subject for invoke
    1820              :     }
    1821              : 
    1822           25 :     Access::RequestPath requestPath{ .cluster     = aRequest.path.mClusterId,
    1823           25 :                                      .endpoint    = aRequest.path.mEndpointId,
    1824              :                                      .requestType = Access::RequestType::kCommandInvokeRequest,
    1825           25 :                                      .entityId    = aRequest.path.mCommandId };
    1826              : 
    1827           25 :     CHIP_ERROR err = Access::GetAccessControl().Check(*aRequest.subjectDescriptor, requestPath, entry.GetInvokePrivilege());
    1828           25 :     if (err != CHIP_NO_ERROR)
    1829              :     {
    1830            0 :         if ((err != CHIP_ERROR_ACCESS_DENIED) && (err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL))
    1831              :         {
    1832            0 :             return Status::Failure;
    1833              :         }
    1834            0 :         return err == CHIP_ERROR_ACCESS_DENIED ? Status::UnsupportedAccess : Status::AccessRestricted;
    1835              :     }
    1836              : 
    1837           25 :     return Status::Success;
    1838              : }
    1839              : 
    1840           25 : Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandFlags(const DataModel::InvokeRequest & aRequest,
    1841              :                                                                               const DataModel::AcceptedCommandEntry & entry)
    1842              : {
    1843              :     // Command that is marked as having a large payload must be sent over a
    1844              :     // session that supports it.
    1845           25 :     if (entry.HasFlags(DataModel::CommandQualityFlags::kLargeMessage) &&
    1846           25 :         !CurrentExchange()->GetSessionHandle()->AllowsLargePayload())
    1847              :     {
    1848            0 :         return Status::InvalidTransportType;
    1849              :     }
    1850              : 
    1851           25 :     const bool commandIsFabricScoped = entry.HasFlags(DataModel::CommandQualityFlags::kFabricScoped);
    1852           25 :     if (commandIsFabricScoped)
    1853              :     {
    1854              :         // SPEC: Else if the command in the path is fabric-scoped and there is no accessing fabric,
    1855              :         // a CommandStatusIB SHALL be generated with the UNSUPPORTED_ACCESS Status Code.
    1856              : 
    1857              :         // Fabric-scoped commands are not allowed before a specific accessing fabric is available.
    1858              :         // This is mostly just during a PASE session before AddNOC.
    1859            0 :         if (aRequest.GetAccessingFabricIndex() == kUndefinedFabricIndex)
    1860              :         {
    1861            0 :             return Status::UnsupportedAccess;
    1862              :         }
    1863              :     }
    1864              : 
    1865           25 :     const bool commandNeedsTimedInvoke = entry.HasFlags(DataModel::CommandQualityFlags::kTimed);
    1866           25 :     if (commandNeedsTimedInvoke && !aRequest.invokeFlags.Has(DataModel::InvokeFlags::kTimed))
    1867              :     {
    1868            0 :         return Status::NeedsTimedInteraction;
    1869              :     }
    1870              : 
    1871           25 :     return Status::Success;
    1872              : }
    1873              : 
    1874           32 : Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistence(const ConcreteCommandPath & aCommandPath,
    1875              :                                                                                   DataModel::AcceptedCommandEntry & entry)
    1876              : {
    1877           32 :     auto provider = GetDataModelProvider();
    1878              : 
    1879           32 :     ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedCommands;
    1880           32 :     (void) provider->AcceptedCommands(aCommandPath, acceptedCommands);
    1881           58 :     for (auto & existing : acceptedCommands.TakeBuffer())
    1882              :     {
    1883           51 :         if (existing.commandId == aCommandPath.mCommandId)
    1884              :         {
    1885           25 :             entry = existing;
    1886           25 :             return Protocols::InteractionModel::Status::Success;
    1887              :         }
    1888           32 :     }
    1889              : 
    1890              :     // invalid command, return the right failure status
    1891            7 :     return DataModel::ValidateClusterPath(provider, aCommandPath, Protocols::InteractionModel::Status::UnsupportedCommand);
    1892           32 : }
    1893              : 
    1894          522 : DataModel::Provider * InteractionModelEngine::SetDataModelProvider(DataModel::Provider * model)
    1895              : {
    1896              :     // Altering data model should not be done while IM is actively handling requests.
    1897          522 :     VerifyOrDie(mReadHandlers.begin() == mReadHandlers.end());
    1898              : 
    1899          522 :     if (model == mDataModelProvider)
    1900              :     {
    1901              :         // no-op, just return
    1902           25 :         return model;
    1903              :     }
    1904              : 
    1905          497 :     DataModel::Provider * oldModel = mDataModelProvider;
    1906          497 :     if (oldModel != nullptr)
    1907              :     {
    1908          243 :         CHIP_ERROR err = oldModel->Shutdown();
    1909          243 :         if (err != CHIP_NO_ERROR)
    1910              :         {
    1911            0 :             ChipLogError(InteractionModel, "Failure on interaction model shutdown: %" CHIP_ERROR_FORMAT, err.Format());
    1912              :         }
    1913              :     }
    1914              : 
    1915          497 :     mDataModelProvider = model;
    1916          497 :     if (mDataModelProvider != nullptr)
    1917              :     {
    1918          254 :         DataModel::InteractionModelContext context;
    1919              : 
    1920          254 :         context.eventsGenerator         = &EventManagement::GetInstance();
    1921          254 :         context.dataModelChangeListener = &mReportingEngine;
    1922          254 :         context.actionContext           = this;
    1923              : 
    1924          254 :         CHIP_ERROR err = mDataModelProvider->Startup(context);
    1925          254 :         if (err != CHIP_NO_ERROR)
    1926              :         {
    1927            0 :             ChipLogError(InteractionModel, "Failure on interaction model startup: %" CHIP_ERROR_FORMAT, err.Format());
    1928              :         }
    1929              :     }
    1930              : 
    1931          497 :     return oldModel;
    1932              : }
    1933              : 
    1934        15194 : DataModel::Provider * InteractionModelEngine::GetDataModelProvider() const
    1935              : {
    1936              :     // These should be called within the CHIP processing loop.
    1937        15194 :     assertChipStackLockedByCurrentThread();
    1938              : 
    1939        15194 :     return mDataModelProvider;
    1940              : }
    1941              : 
    1942            2 : void InteractionModelEngine::OnTimedInteractionFailed(TimedHandler * apTimedHandler)
    1943              : {
    1944            2 :     mTimedHandlers.ReleaseObject(apTimedHandler);
    1945            2 : }
    1946              : 
    1947            1 : void InteractionModelEngine::OnTimedInvoke(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
    1948              :                                            const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
    1949              : {
    1950              :     using namespace Protocols::InteractionModel;
    1951              : 
    1952              :     // Reset the ourselves as the exchange delegate for now, to match what we'd
    1953              :     // do with an initial unsolicited invoke.
    1954            1 :     apExchangeContext->SetDelegate(this);
    1955            1 :     mTimedHandlers.ReleaseObject(apTimedHandler);
    1956              : 
    1957            1 :     VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::InvokeCommandRequest));
    1958            1 :     VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
    1959              : 
    1960              :     // Ensure that DataModel::Provider has access to the exchange the message was received on.
    1961            1 :     CurrentExchangeValueScope scopedExchangeContext(*this, apExchangeContext);
    1962              : 
    1963            1 :     Status status = OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ true);
    1964            1 :     if (status != Status::Success)
    1965              :     {
    1966            0 :         StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
    1967              :     }
    1968            1 : }
    1969              : 
    1970            1 : void InteractionModelEngine::OnTimedWrite(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
    1971              :                                           const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
    1972              : {
    1973              :     using namespace Protocols::InteractionModel;
    1974              : 
    1975              :     // Reset the ourselves as the exchange delegate for now, to match what we'd
    1976              :     // do with an initial unsolicited write.
    1977            1 :     apExchangeContext->SetDelegate(this);
    1978            1 :     mTimedHandlers.ReleaseObject(apTimedHandler);
    1979              : 
    1980            1 :     VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::WriteRequest));
    1981            1 :     VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
    1982              : 
    1983              :     // Ensure that DataModel::Provider has access to the exchange the message was received on.
    1984            1 :     CurrentExchangeValueScope scopedExchangeContext(*this, apExchangeContext);
    1985              : 
    1986            1 :     Status status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ true);
    1987            1 :     if (status != Status::Success)
    1988              :     {
    1989            1 :         StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
    1990              :     }
    1991            1 : }
    1992              : 
    1993            0 : bool InteractionModelEngine::HasActiveRead()
    1994              : {
    1995            0 :     return ((mReadHandlers.ForEachActiveObject([](ReadHandler * handler) {
    1996            0 :         if (handler->IsType(ReadHandler::InteractionType::Read))
    1997              :         {
    1998            0 :             return Loop::Break;
    1999              :         }
    2000              : 
    2001            0 :         return Loop::Continue;
    2002            0 :     }) == Loop::Break));
    2003              : }
    2004              : 
    2005            0 : uint16_t InteractionModelEngine::GetMinGuaranteedSubscriptionsPerFabric() const
    2006              : {
    2007              : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
    2008            0 :     return UINT16_MAX;
    2009              : #else
    2010              :     return static_cast<uint16_t>(
    2011              :         std::min(GetReadHandlerPoolCapacityForSubscriptions() / GetConfigMaxFabrics(), static_cast<size_t>(UINT16_MAX)));
    2012              : #endif
    2013              : }
    2014              : 
    2015            5 : size_t InteractionModelEngine::GetNumDirtySubscriptions() const
    2016              : {
    2017            5 :     size_t numDirtySubscriptions = 0;
    2018            5 :     mReadHandlers.ForEachActiveObject([&](const auto readHandler) {
    2019           33 :         if (readHandler->IsType(ReadHandler::InteractionType::Subscribe) && readHandler->IsDirty())
    2020              :         {
    2021            8 :             numDirtySubscriptions++;
    2022              :         }
    2023           33 :         return Loop::Continue;
    2024              :     });
    2025            5 :     return numDirtySubscriptions;
    2026              : }
    2027              : 
    2028            7 : void InteractionModelEngine::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
    2029              : {
    2030            7 :     mReadHandlers.ForEachActiveObject([fabricIndex](ReadHandler * handler) {
    2031           67 :         if (handler->GetAccessingFabricIndex() == fabricIndex)
    2032              :         {
    2033           67 :             ChipLogProgress(InteractionModel, "Deleting expired ReadHandler for NodeId: " ChipLogFormatX64 ", FabricIndex: %u",
    2034              :                             ChipLogValueX64(handler->GetInitiatorNodeId()), fabricIndex);
    2035           67 :             handler->Close();
    2036              :         }
    2037              : 
    2038           67 :         return Loop::Continue;
    2039              :     });
    2040              : 
    2041              : #if CHIP_CONFIG_ENABLE_READ_CLIENT
    2042          141 :     for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
    2043              :     {
    2044              :         // ReadClient::Close may delete the read client so that readClient->GetNextClient() will be use-after-free.
    2045              :         // We need save readClient as nextReadClient before closing.
    2046          134 :         if (readClient->GetFabricIndex() == fabricIndex)
    2047              :         {
    2048           67 :             ChipLogProgress(InteractionModel, "Fabric removed, deleting obsolete read client with FabricIndex: %u", fabricIndex);
    2049           67 :             auto * nextReadClient = readClient->GetNextClient();
    2050           67 :             readClient->Close(CHIP_ERROR_IM_FABRIC_DELETED, false);
    2051           67 :             readClient = nextReadClient;
    2052              :         }
    2053              :         else
    2054              :         {
    2055           67 :             readClient = readClient->GetNextClient();
    2056              :         }
    2057              :     }
    2058              : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
    2059              : 
    2060           35 :     for (auto & handler : mWriteHandlers)
    2061              :     {
    2062           28 :         if (!(handler.IsFree()) && handler.GetAccessingFabricIndex() == fabricIndex)
    2063              :         {
    2064            1 :             ChipLogProgress(InteractionModel, "Fabric removed, deleting obsolete write handler with FabricIndex: %u", fabricIndex);
    2065            1 :             handler.Close();
    2066              :         }
    2067              :     }
    2068              : 
    2069              :     // Applications may hold references to CommandHandlerImpl instances for async command processing.
    2070              :     // Therefore we can't forcible destroy CommandHandlers here.  Their exchanges will get closed by
    2071              :     // the fabric removal, though, so they will fail when they try to actually send their command response
    2072              :     // and will close at that point.
    2073            7 : }
    2074              : 
    2075            1 : CHIP_ERROR InteractionModelEngine::ResumeSubscriptions()
    2076              : {
    2077              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
    2078            1 :     VerifyOrReturnError(mpSubscriptionResumptionStorage, CHIP_NO_ERROR);
    2079              : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2080            1 :     VerifyOrReturnError(!mSubscriptionResumptionScheduled, CHIP_NO_ERROR);
    2081              : #endif
    2082              : 
    2083              :     // To avoid the case of a reboot loop causing rapid traffic generation / power consumption, subscription resumption should make
    2084              :     // use of the persisted min-interval values, and wait before resumption. Ideally, each persisted subscription should wait their
    2085              :     // own min-interval value before resumption, but that both A) potentially runs into a timer resource issue, and B) having a
    2086              :     // low-powered device wake many times also has energy use implications. The logic below waits the largest of the persisted
    2087              :     // min-interval values before resuming subscriptions.
    2088              : 
    2089              :     // Even though this causes subscription-to-subscription interaction by linking the min-interval values, this is the right thing
    2090              :     // to do for now because it's both simple and avoids the timer resource and multiple-wake problems. This issue is to track
    2091              :     // future improvements: https://github.com/project-chip/connectedhomeip/issues/25439
    2092              : 
    2093            1 :     SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
    2094            1 :     auto * iterator             = mpSubscriptionResumptionStorage->IterateSubscriptions();
    2095            1 :     mNumOfSubscriptionsToResume = 0;
    2096            1 :     uint16_t minInterval        = 0;
    2097            1 :     while (iterator->Next(subscriptionInfo))
    2098              :     {
    2099            0 :         mNumOfSubscriptionsToResume++;
    2100            0 :         minInterval = std::max(minInterval, subscriptionInfo.mMinInterval);
    2101              :     }
    2102            1 :     iterator->Release();
    2103              : 
    2104            1 :     if (mNumOfSubscriptionsToResume)
    2105              :     {
    2106              : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2107            0 :         mSubscriptionResumptionScheduled = true;
    2108              : #endif
    2109            0 :         ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", mNumOfSubscriptionsToResume, minInterval);
    2110            0 :         ReturnErrorOnFailure(mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds16(minInterval),
    2111              :                                                                                            ResumeSubscriptionsTimerCallback, this));
    2112              :     }
    2113              :     else
    2114              :     {
    2115            1 :         ChipLogProgress(InteractionModel, "No subscriptions to resume");
    2116              :     }
    2117              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
    2118              : 
    2119            1 :     return CHIP_NO_ERROR;
    2120            1 : }
    2121              : 
    2122            0 : void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * apSystemLayer, void * apAppState)
    2123              : {
    2124              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
    2125            0 :     VerifyOrReturn(apAppState != nullptr);
    2126            0 :     InteractionModelEngine * imEngine = static_cast<InteractionModelEngine *>(apAppState);
    2127              : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2128            0 :     imEngine->mSubscriptionResumptionScheduled = false;
    2129            0 :     bool resumedSubscriptions                  = false;
    2130              : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2131            0 :     SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
    2132            0 :     AutoReleaseSubscriptionInfoIterator iterator(imEngine->mpSubscriptionResumptionStorage->IterateSubscriptions());
    2133            0 :     while (iterator->Next(subscriptionInfo))
    2134              :     {
    2135              :         // If subscription happens between reboot and this timer callback, it's already live and should skip resumption
    2136            0 :         if (Loop::Break == imEngine->mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
    2137              :                 SubscriptionId subscriptionId;
    2138            0 :                 handler->GetSubscriptionId(subscriptionId);
    2139            0 :                 if (subscriptionId == subscriptionInfo.mSubscriptionId)
    2140              :                 {
    2141            0 :                     return Loop::Break;
    2142              :                 }
    2143            0 :                 return Loop::Continue;
    2144              :             }))
    2145              :         {
    2146            0 :             ChipLogProgress(InteractionModel, "Skip resuming live subscriptionId %" PRIu32, subscriptionInfo.mSubscriptionId);
    2147            0 :             continue;
    2148              :         }
    2149              : 
    2150            0 :         auto subscriptionResumptionSessionEstablisher = Platform::MakeUnique<SubscriptionResumptionSessionEstablisher>();
    2151            0 :         if (subscriptionResumptionSessionEstablisher == nullptr)
    2152              :         {
    2153            0 :             ChipLogProgress(InteractionModel, "Failed to create SubscriptionResumptionSessionEstablisher");
    2154            0 :             return;
    2155              :         }
    2156              : 
    2157            0 :         if (subscriptionResumptionSessionEstablisher->ResumeSubscription(*imEngine->mpCASESessionMgr, subscriptionInfo) !=
    2158            0 :             CHIP_NO_ERROR)
    2159              :         {
    2160            0 :             ChipLogProgress(InteractionModel, "Failed to ResumeSubscription 0x%" PRIx32, subscriptionInfo.mSubscriptionId);
    2161            0 :             return;
    2162              :         }
    2163            0 :         subscriptionResumptionSessionEstablisher.release();
    2164              : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2165            0 :         resumedSubscriptions = true;
    2166              : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2167            0 :     }
    2168              : 
    2169              : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2170              :     // If no persisted subscriptions needed resumption then all resumption retries are done
    2171            0 :     if (!resumedSubscriptions)
    2172              :     {
    2173            0 :         imEngine->mNumSubscriptionResumptionRetries = 0;
    2174              :     }
    2175              : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2176              : 
    2177              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
    2178            0 : }
    2179              : 
    2180              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2181           12 : uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption()
    2182              : {
    2183              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
    2184           12 :     if (mSubscriptionResumptionRetrySecondsOverride > 0)
    2185              :     {
    2186            0 :         return static_cast<uint32_t>(mSubscriptionResumptionRetrySecondsOverride);
    2187              :     }
    2188              : #endif
    2189           12 :     if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX)
    2190              :     {
    2191            1 :         return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS;
    2192              :     }
    2193              : 
    2194              :     return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS +
    2195           11 :         GetFibonacciForIndex(mNumSubscriptionResumptionRetries) *
    2196           11 :         CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS;
    2197              : }
    2198              : 
    2199          866 : bool InteractionModelEngine::HasSubscriptionsToResume()
    2200              : {
    2201          866 :     VerifyOrReturnValue(mpSubscriptionResumptionStorage != nullptr, false);
    2202              : 
    2203              :     // Look through persisted subscriptions and see if any aren't already in mReadHandlers pool
    2204            0 :     SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
    2205            0 :     auto * iterator                = mpSubscriptionResumptionStorage->IterateSubscriptions();
    2206            0 :     bool foundSubscriptionToResume = false;
    2207            0 :     while (iterator->Next(subscriptionInfo))
    2208              :     {
    2209            0 :         if (Loop::Break == mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
    2210              :                 SubscriptionId subscriptionId;
    2211            0 :                 handler->GetSubscriptionId(subscriptionId);
    2212            0 :                 if (subscriptionId == subscriptionInfo.mSubscriptionId)
    2213              :                 {
    2214            0 :                     return Loop::Break;
    2215              :                 }
    2216            0 :                 return Loop::Continue;
    2217              :             }))
    2218              :         {
    2219            0 :             continue;
    2220              :         }
    2221              : 
    2222            0 :         foundSubscriptionToResume = true;
    2223            0 :         break;
    2224              :     }
    2225            0 :     iterator->Release();
    2226              : 
    2227            0 :     return foundSubscriptionToResume;
    2228            0 : }
    2229              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2230              : 
    2231              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
    2232            6 : void InteractionModelEngine::DecrementNumSubscriptionsToResume()
    2233              : {
    2234            6 :     VerifyOrReturn(mNumOfSubscriptionsToResume > 0);
    2235            5 :     mNumOfSubscriptionsToResume--;
    2236              : 
    2237              : #if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2238              :     if (mICDManager && !mNumOfSubscriptionsToResume)
    2239              :     {
    2240              :         mICDManager->SetBootUpResumeSubscriptionExecuted();
    2241              :     }
    2242              : #endif // CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2243              : }
    2244              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
    2245              : 
    2246              : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2247            0 : void InteractionModelEngine::ResetNumSubscriptionsRetries()
    2248              : {
    2249              :     // Check if there are any subscriptions to resume, if not the retry counter can be reset.
    2250            0 :     if (!HasSubscriptionsToResume())
    2251              :     {
    2252            0 :         mNumSubscriptionResumptionRetries = 0;
    2253              :     }
    2254            0 : }
    2255              : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    2256              : } // namespace app
    2257              : } // namespace chip
        

Generated by: LCOV version 2.0-1