Matter SDK Coverage Report
Current view: top level - app - InteractionModelEngine.cpp (source / functions) Coverage Total Hit
Test: SHA:4cbce7f768f16e614f5a8ccb8cd93c92cbeae70d Lines: 82.8 % 988 818
Test Date: 2025-04-26 07:09:35 Functions: 86.9 % 107 93

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

Generated by: LCOV version 2.0-1