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