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