LCOV - code coverage report
Current view: top level - app - InteractionModelEngine.cpp (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 707 888 79.6 %
Date: 2024-02-15 08:20:41 Functions: 82 98 83.7 %

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

Generated by: LCOV version 1.14