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