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