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