Matter SDK Coverage Report
Current view: top level - app - InteractionModelEngine.cpp (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 83.9 % 950 797
Test Date: 2025-01-17 19:00:11 Functions: 87.5 % 104 91

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

Generated by: LCOV version 2.0-1