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

Generated by: LCOV version 2.0-1