Matter SDK Coverage Report
Current view: top level - app - InteractionModelEngine.cpp (source / functions) Coverage Total Hit
Test: SHA:f1767a8b0a3778fdf31b1d979afbdf544892fd94 Lines: 82.4 % 1031 850
Test Date: 2026-06-03 07:35:21 Functions: 85.6 % 111 95

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

Generated by: LCOV version 2.0-1