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 370 : bool IsAccessibleAttributeEntry(const ConcreteAttributePath & path, const Access::SubjectDescriptor & subjectDescriptor,
148 : const std::optional<DataModel::AttributeEntry> & entry)
149 : {
150 370 : 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 366 : Access::RequestPath requestPath{ .cluster = path.mClusterId,
157 366 : .endpoint = path.mEndpointId,
158 : .requestType = Access::RequestType::kAttributeReadRequest,
159 366 : .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 366 : const Access::Privilege privilege = *entry->GetReadPrivilege(); // NOLINT(bugprone-unchecked-optional-access)
164 :
165 732 : 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 835 : InteractionModelEngine::InteractionModelEngine() : mReportingEngine(this) {}
194 :
195 9266 : InteractionModelEngine * InteractionModelEngine::GetInstance()
196 : {
197 9266 : return &sInteractionModelEngine.get();
198 : }
199 :
200 501 : CHIP_ERROR InteractionModelEngine::Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable,
201 : reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr,
202 : SubscriptionResumptionStorage * subscriptionResumptionStorage,
203 : EventManagement * eventManagement)
204 : {
205 501 : VerifyOrReturnError(apFabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
206 501 : VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
207 501 : VerifyOrReturnError(reportScheduler != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
208 :
209 501 : mState = State::kInitializing;
210 501 : mpExchangeMgr = apExchangeMgr;
211 501 : mpFabricTable = apFabricTable;
212 501 : mpCASESessionMgr = apCASESessionMgr;
213 501 : mpSubscriptionResumptionStorage = subscriptionResumptionStorage;
214 501 : mReportScheduler = reportScheduler;
215 :
216 501 : ReturnErrorOnFailure(mpFabricTable->AddFabricDelegate(this));
217 501 : ReturnErrorOnFailure(mpExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id, this));
218 :
219 1002 : TEMPORARY_RETURN_IGNORED mReportingEngine.Init((eventManagement != nullptr) ? eventManagement
220 501 : : &EventManagement::GetInstance());
221 :
222 501 : StatusIB::RegisterErrorFormatter();
223 :
224 501 : mState = State::kInitialized;
225 501 : return CHIP_NO_ERROR;
226 : }
227 :
228 485 : void InteractionModelEngine::Shutdown()
229 : {
230 485 : VerifyOrReturn(State::kUninitialized != mState);
231 :
232 369 : 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 369 : CommandHandlerInterfaceRegistry::Instance().UnregisterAllHandlers();
240 369 : mCommandResponderObjs.ReleaseAll();
241 :
242 369 : mTimedHandlers.ForEachActiveObject([this](TimedHandler * obj) -> Loop {
243 1 : mpExchangeMgr->CloseAllContextsForDelegate(obj);
244 1 : return Loop::Continue;
245 : });
246 :
247 369 : mTimedHandlers.ReleaseAll();
248 :
249 369 : 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 369 : 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 373 : 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 369 : mpActiveReadClientList = nullptr;
278 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
279 :
280 1845 : for (auto & writeHandler : mWriteHandlers)
281 : {
282 1476 : if (!writeHandler.IsFree())
283 : {
284 0 : writeHandler.Close();
285 : }
286 : }
287 369 : mReportingEngine.Shutdown();
288 369 : mAttributePathPool.ReleaseAll();
289 369 : mEventPathPool.ReleaseAll();
290 369 : mDataVersionFilterPool.ReleaseAll();
291 369 : TEMPORARY_RETURN_IGNORED mpExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id);
292 :
293 369 : 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 369 : if (mDataModelProvider != nullptr)
308 : {
309 153 : ChipLogProgress(InteractionModel, "Shutting down data model provider %p", mDataModelProvider);
310 306 : LogErrorOnFailure(mDataModelProvider->Shutdown());
311 153 : mDataModelProviderNeedsStartup = true;
312 : }
313 :
314 369 : mState = State::kUninitialized;
315 : }
316 :
317 69 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers() const
318 : {
319 69 : 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 2304 : ReadHandler * InteractionModelEngine::ActiveHandlerAt(unsigned int aIndex)
355 : {
356 2304 : if (aIndex >= mReadHandlers.Allocated())
357 : {
358 0 : return nullptr;
359 : }
360 :
361 2304 : unsigned int i = 0;
362 2304 : ReadHandler * ret = nullptr;
363 :
364 2304 : mReadHandlers.ForEachActiveObject([aIndex, &i, &ret](ReadHandler * handler) {
365 14990 : if (i == aIndex)
366 : {
367 2304 : ret = handler;
368 2304 : return Loop::Break;
369 : }
370 :
371 12686 : i++;
372 12686 : return Loop::Continue;
373 : });
374 :
375 2304 : 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 14 : uint32_t InteractionModelEngine::GetNumActiveWriteHandlers() const
397 : {
398 14 : uint32_t numActive = 0;
399 :
400 70 : for (auto & writeHandler : mWriteHandlers)
401 : {
402 56 : if (!writeHandler.IsFree())
403 : {
404 2 : numActive++;
405 : }
406 : }
407 :
408 14 : 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 369 : void InteractionModelEngine::ShutdownAllSubscriptions()
443 : {
444 369 : assertChipStackLockedByCurrentThread();
445 369 : ShutdownMatchingSubscriptions();
446 369 : }
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 369 : 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 373 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
466 : {
467 : // Grab the next client now, because we might be about to delete readClient.
468 4 : auto * nextClient = readClient->GetNextClient();
469 4 : if (readClient->IsSubscriptionType())
470 : {
471 4 : bool fabricMatches = !aFabricIndex.HasValue() || (aFabricIndex.Value() == readClient->GetFabricIndex());
472 4 : bool nodeIdMatches = !aPeerNodeId.HasValue() || (aPeerNodeId.Value() == readClient->GetPeerNodeId());
473 4 : if (fabricMatches && nodeIdMatches)
474 : {
475 4 : readClient->Close(CHIP_NO_ERROR);
476 : }
477 : }
478 4 : readClient = nextClient;
479 : }
480 369 : }
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 54 : void InteractionModelEngine::OnDone(CommandResponseSender & apResponderObj)
561 : {
562 54 : mCommandResponderObjs.ReleaseObject(&apResponderObj);
563 54 : }
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 867 : 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 867 : mReportingEngine.ResetReadHandlerTracker(&apReadObj);
581 :
582 867 : mReadHandlers.ReleaseObject(&apReadObj);
583 867 : TryToResumeSubscriptions();
584 867 : }
585 :
586 867 : void InteractionModelEngine::TryToResumeSubscriptions()
587 : {
588 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
589 867 : 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 867 : }
601 :
602 54 : 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 54 : CommandResponseSender * commandResponder = mCommandResponderObjs.CreateObject(this, this);
608 54 : if (commandResponder == nullptr)
609 : {
610 0 : ChipLogProgress(InteractionModel, "no resource for Invoke interaction");
611 0 : return Status::Busy;
612 : }
613 54 : 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 54 : 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 54 : 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 54 : commandResponder->OnInvokeCommandRequest(apExchangeContext, std::move(aPayload), aIsTimedInvoke);
629 54 : return Status::Success;
630 : }
631 :
632 302 : CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDescriptor & aSubjectDescriptor,
633 : AttributePathIBs::Parser & aAttributePathListParser,
634 : bool & aHasValidAttributePath, size_t & aRequestedAttributePathCount)
635 : {
636 302 : TLV::TLVReader pathReader;
637 302 : aAttributePathListParser.GetReader(&pathReader);
638 302 : CHIP_ERROR err = CHIP_NO_ERROR;
639 :
640 302 : aHasValidAttributePath = false;
641 302 : aRequestedAttributePathCount = 0;
642 :
643 1344 : while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
644 : {
645 370 : 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 370 : SingleLinkedListNode<AttributePathParams> paramsList;
652 :
653 370 : ReturnErrorOnFailure(path.Init(pathReader));
654 370 : ReturnErrorOnFailure(path.ParsePath(paramsList.mValue));
655 :
656 370 : 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 362 : ConcreteAttributePath concretePath(paramsList.mValue.mEndpointId, paramsList.mValue.mClusterId,
682 362 : paramsList.mValue.mAttributeId);
683 :
684 362 : std::optional<DataModel::AttributeEntry> entry = FindAttributeEntry(concretePath);
685 :
686 362 : if (IsAccessibleAttributeEntry(concretePath, aSubjectDescriptor, entry))
687 : {
688 356 : aHasValidAttributePath = true;
689 : }
690 : }
691 :
692 370 : aRequestedAttributePathCount++;
693 : }
694 :
695 604 : if (err == CHIP_ERROR_END_OF_TLV)
696 : {
697 302 : err = CHIP_NO_ERROR;
698 : }
699 :
700 302 : 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 1108 : Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest(Messaging::ExchangeContext * apExchangeContext,
744 : const PayloadHeader & aPayloadHeader,
745 : System::PacketBufferHandle && aPayload,
746 : ReadHandler::InteractionType aInteractionType)
747 : {
748 1108 : 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 1108 : if (aInteractionType == ReadHandler::InteractionType::Subscribe)
756 : {
757 311 : System::PacketBufferTLVReader reader;
758 311 : bool keepExistingSubscriptions = true;
759 :
760 311 : if (apExchangeContext->GetSessionHandle()->GetFabricIndex() == kUndefinedFabricIndex)
761 : {
762 : // Subscriptions must be associated to a fabric.
763 0 : return Status::UnsupportedAccess;
764 : }
765 :
766 311 : reader.Init(aPayload.Retain());
767 :
768 311 : SubscribeRequestMessage::Parser subscribeRequestParser;
769 622 : VerifyOrReturnError(subscribeRequestParser.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
770 :
771 : #if CHIP_CONFIG_IM_PRETTY_PRINT
772 309 : TEMPORARY_RETURN_IGNORED subscribeRequestParser.PrettyPrint();
773 : #endif
774 :
775 618 : VerifyOrReturnError(subscribeRequestParser.GetKeepSubscriptions(&keepExistingSubscriptions) == CHIP_NO_ERROR,
776 : Status::InvalidAction);
777 309 : 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 83 : 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 83 : 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 309 : size_t requestedAttributePathCount = 0;
833 309 : size_t requestedEventPathCount = 0;
834 309 : AttributePathIBs::Parser attributePathListParser;
835 309 : bool hasValidAttributePath = false;
836 309 : bool mayHaveValidEventPath = false;
837 :
838 309 : CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser);
839 618 : if (err == CHIP_NO_ERROR)
840 : {
841 302 : auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
842 302 : err = ParseAttributePaths(subjectDescriptor, attributePathListParser, hasValidAttributePath,
843 : requestedAttributePathCount);
844 604 : 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 309 : EventPathIBs::Parser eventPathListParser;
855 309 : err = subscribeRequestParser.GetEventRequests(&eventPathListParser);
856 618 : 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 582 : else if (err != CHIP_ERROR_END_OF_TLV)
866 : {
867 0 : return Status::InvalidAction;
868 : }
869 :
870 309 : 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 308 : 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 305 : if (!EnsureResourceForSubscription(apExchangeContext->GetSessionHandle()->GetFabricIndex(), requestedAttributePathCount,
890 : requestedEventPathCount))
891 : {
892 2 : return Status::PathsExhausted;
893 : }
894 : }
895 311 : }
896 : else
897 : {
898 797 : System::PacketBufferTLVReader reader;
899 797 : reader.Init(aPayload.Retain());
900 :
901 797 : ReadRequestMessage::Parser readRequestParser;
902 1594 : VerifyOrReturnError(readRequestParser.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
903 :
904 : #if CHIP_CONFIG_IM_PRETTY_PRINT
905 793 : TEMPORARY_RETURN_IGNORED readRequestParser.PrettyPrint();
906 : #endif
907 : {
908 793 : size_t requestedAttributePathCount = 0;
909 793 : size_t requestedEventPathCount = 0;
910 793 : AttributePathIBs::Parser attributePathListParser;
911 793 : CHIP_ERROR err = readRequestParser.GetAttributeRequests(&attributePathListParser);
912 1586 : 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 244 : else if (err != CHIP_ERROR_END_OF_TLV)
920 : {
921 0 : return Status::InvalidAction;
922 : }
923 793 : EventPathIBs::Parser eventpathListParser;
924 793 : err = readRequestParser.GetEventRequests(&eventpathListParser);
925 1586 : 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 950 : 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 793 : Status checkResult = EnsureResourceForRead(apExchangeContext->GetSessionHandle()->GetFabricIndex(),
939 : requestedAttributePathCount, requestedEventPathCount);
940 793 : if (checkResult != Status::Success)
941 : {
942 7 : return checkResult;
943 : }
944 : }
945 797 : }
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 1089 : ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType, mReportScheduler);
950 1089 : 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 1089 : handler->OnInitialRequest(std::move(aPayload));
958 :
959 1089 : return Status::Success;
960 : }
961 :
962 965 : Protocols::InteractionModel::Status InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext,
963 : const PayloadHeader & aPayloadHeader,
964 : System::PacketBufferHandle && aPayload,
965 : bool aIsTimedWrite)
966 : {
967 965 : ChipLogDetail(InteractionModel, "Received Write request");
968 :
969 969 : for (auto & writeHandler : mWriteHandlers)
970 : {
971 969 : if (writeHandler.IsFree())
972 : {
973 1930 : VerifyOrReturnError(writeHandler.Init(GetDataModelProvider(), this) == CHIP_NO_ERROR, Status::Busy);
974 964 : 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 87 : Status InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext,
1002 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
1003 : {
1004 87 : System::PacketBufferTLVReader reader;
1005 87 : reader.Init(aPayload.Retain());
1006 :
1007 87 : ReportDataMessage::Parser report;
1008 174 : VerifyOrReturnError(report.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
1009 :
1010 : #if CHIP_CONFIG_IM_PRETTY_PRINT
1011 85 : TEMPORARY_RETURN_IGNORED report.PrettyPrint();
1012 : #endif
1013 :
1014 85 : SubscriptionId subscriptionId = 0;
1015 170 : VerifyOrReturnError(report.GetSubscriptionId(&subscriptionId) == CHIP_NO_ERROR, Status::InvalidAction);
1016 168 : VerifyOrReturnError(report.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
1017 :
1018 84 : ReadClient * foundSubscription = nullptr;
1019 312 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr; readClient = readClient->GetNextClient())
1020 : {
1021 228 : auto peer = apExchangeContext->GetSessionHandle()->GetPeer();
1022 228 : if (readClient->GetFabricIndex() != peer.GetFabricIndex() || readClient->GetPeerNodeId() != peer.GetNodeId())
1023 : {
1024 146 : continue;
1025 : }
1026 :
1027 : // Notify Subscriptions about incoming communication from node
1028 195 : readClient->OnUnsolicitedMessageFromPublisher();
1029 :
1030 195 : if (!readClient->IsSubscriptionActive())
1031 : {
1032 0 : continue;
1033 : }
1034 :
1035 195 : if (!readClient->IsMatchingSubscriptionId(subscriptionId))
1036 : {
1037 113 : continue;
1038 : }
1039 :
1040 82 : if (!foundSubscription)
1041 : {
1042 82 : foundSubscription = readClient;
1043 : }
1044 : }
1045 :
1046 84 : if (foundSubscription)
1047 : {
1048 82 : foundSubscription->OnUnsolicitedReportData(apExchangeContext, std::move(aPayload));
1049 82 : return Status::Success;
1050 : }
1051 :
1052 2 : ChipLogDetail(InteractionModel, "Received report with invalid subscriptionId %" PRIu32, subscriptionId);
1053 :
1054 2 : return Status::InvalidSubscription;
1055 87 : }
1056 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
1057 :
1058 2224 : 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 2224 : newDelegate = this;
1064 2224 : return CHIP_NO_ERROR;
1065 : }
1066 :
1067 2224 : CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext,
1068 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
1069 : {
1070 : using namespace Protocols::InteractionModel;
1071 :
1072 2224 : Protocols::InteractionModel::Status status = Status::Failure;
1073 :
1074 : // Ensure that DataModel::Provider has access to the exchange the message was received on.
1075 2224 : CurrentExchangeValueScope scopedExchangeContext(*this, apExchangeContext);
1076 :
1077 : // Group Message can only be an InvokeCommandRequest or WriteRequest
1078 2224 : if (apExchangeContext->IsGroupExchangeContext() &&
1079 2226 : !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 2224 : if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::InvokeCommandRequest))
1087 : {
1088 53 : status = OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ false);
1089 : }
1090 2171 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReadRequest))
1091 : {
1092 797 : status = OnReadInitialRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), ReadHandler::InteractionType::Read);
1093 : }
1094 1374 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
1095 : {
1096 964 : status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ false);
1097 : }
1098 410 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::SubscribeRequest))
1099 : {
1100 311 : status =
1101 311 : OnReadInitialRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), ReadHandler::InteractionType::Subscribe);
1102 : }
1103 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
1104 99 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReportData))
1105 : {
1106 87 : 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 2224 : 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 2224 : if (status != Status::Success && !apExchangeContext->IsGroupExchangeContext())
1134 : {
1135 32 : return StatusResponse::Send(status, apExchangeContext, false /*aExpectResponse*/);
1136 : }
1137 :
1138 2192 : return CHIP_NO_ERROR;
1139 2224 : }
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 317 : void InteractionModelEngine::AddReadClient(ReadClient * apReadClient)
1185 : {
1186 317 : apReadClient->SetNextClient(mpActiveReadClientList);
1187 317 : mpActiveReadClientList = apReadClient;
1188 317 : }
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 305 : 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 305 : 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 305 : const size_t pathPoolCapacity = GetPathPoolCapacityForSubscriptions();
1284 305 : 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 305 : const size_t attributePathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity;
1288 305 : const size_t eventPathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity;
1289 305 : const size_t readHandlerCap = allowUnlimited ? SIZE_MAX : readHandlerPoolCapacity;
1290 :
1291 305 : size_t usedAttributePaths = 0;
1292 305 : size_t usedEventPaths = 0;
1293 305 : size_t usedReadHandlers = 0;
1294 :
1295 311 : auto countResourceUsage = [&]() {
1296 311 : usedAttributePaths = 0;
1297 311 : usedEventPaths = 0;
1298 311 : usedReadHandlers = 0;
1299 311 : 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 616 : };
1310 :
1311 305 : countResourceUsage();
1312 :
1313 305 : if (usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
1314 298 : usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap)
1315 : {
1316 : // We have enough resources, then we serve the requests in a best-effort manner.
1317 298 : 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 793 : 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 793 : 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 793 : const size_t attributePathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads();
1467 793 : const size_t eventPathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads();
1468 793 : const size_t readHandlerCap = allowUnlimited ? SIZE_MAX : GetReadHandlerPoolCapacityForReads();
1469 :
1470 793 : const size_t guaranteedReadRequestsPerFabric = GetGuaranteedReadRequestsPerFabric();
1471 793 : const size_t guaranteedPathsPerFabric = kMinSupportedPathsPerReadRequest * guaranteedReadRequestsPerFabric;
1472 :
1473 793 : size_t usedAttributePaths = 0;
1474 793 : size_t usedEventPaths = 0;
1475 793 : size_t usedReadHandlers = 0;
1476 :
1477 824 : auto countResourceUsage = [&]() {
1478 824 : usedAttributePaths = 0;
1479 824 : usedEventPaths = 0;
1480 824 : usedReadHandlers = 0;
1481 824 : 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 1617 : };
1492 :
1493 837 : auto haveEnoughResourcesForTheRequest = [&]() {
1494 1656 : return usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
1495 837 : usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap;
1496 793 : };
1497 :
1498 793 : countResourceUsage();
1499 :
1500 793 : if (haveEnoughResourcesForTheRequest())
1501 : {
1502 : // We have enough resources, then we serve the requests in a best-effort manner.
1503 775 : 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 152 : void InteractionModelEngine::RemoveReadClient(ReadClient * apReadClient)
1589 : {
1590 152 : ReadClient * pPrevListItem = nullptr;
1591 152 : ReadClient * pCurListItem = mpActiveReadClientList;
1592 :
1593 157 : 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 152 : if (pPrevListItem)
1605 : {
1606 3 : pPrevListItem->SetNextClient(apReadClient->GetNextClient());
1607 : }
1608 : else
1609 : {
1610 149 : mpActiveReadClientList = apReadClient->GetNextClient();
1611 : }
1612 :
1613 152 : apReadClient->SetNextClient(nullptr);
1614 152 : }
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 5224 : bool InteractionModelEngine::HasConflictWriteRequests(const WriteHandler * apWriteHandler, const ConcreteAttributePath & aPath)
1649 : {
1650 26088 : for (auto & writeHandler : mWriteHandlers)
1651 : {
1652 20872 : if (writeHandler.IsFree() || &writeHandler == apWriteHandler)
1653 : {
1654 20850 : continue;
1655 : }
1656 22 : if (writeHandler.IsCurrentlyProcessingWritePath(aPath))
1657 : {
1658 8 : return true;
1659 : }
1660 : }
1661 5216 : return false;
1662 : }
1663 :
1664 1146 : void InteractionModelEngine::ReleaseAttributePathList(SingleLinkedListNode<AttributePathParams> *& aAttributePathList)
1665 : {
1666 1146 : ReleasePool(aAttributePathList, mAttributePathPool);
1667 1146 : }
1668 :
1669 1564 : CHIP_ERROR InteractionModelEngine::PushFrontAttributePathList(SingleLinkedListNode<AttributePathParams> *& aAttributePathList,
1670 : AttributePathParams & aAttributePath)
1671 : {
1672 1564 : CHIP_ERROR err = PushFront(aAttributePathList, aAttributePath, mAttributePathPool);
1673 3128 : if (err == CHIP_ERROR_NO_MEMORY)
1674 : {
1675 0 : ChipLogError(InteractionModel, "AttributePath pool full");
1676 0 : return CHIP_IM_GLOBAL_STATUS(PathsExhausted);
1677 : }
1678 1564 : return err;
1679 : }
1680 :
1681 1617 : std::optional<DataModel::AttributeEntry> InteractionModelEngine::FindAttributeEntry(const ConcreteAttributePath & path)
1682 : {
1683 1617 : DataModel::AttributeFinder finder(mDataModelProvider);
1684 1617 : return finder.Find(path);
1685 1617 : }
1686 :
1687 977 : void InteractionModelEngine::RemoveDuplicateConcreteAttributePath(SingleLinkedListNode<AttributePathParams> *& aAttributePaths)
1688 : {
1689 977 : SingleLinkedListNode<AttributePathParams> * prev = nullptr;
1690 977 : auto * path1 = aAttributePaths;
1691 :
1692 2538 : while (path1 != nullptr)
1693 : {
1694 1561 : bool duplicate = false;
1695 :
1696 : // skip all wildcard paths and invalid concrete attribute
1697 2816 : if (path1->mValue.IsWildcardPath() ||
1698 2510 : !FindAttributeEntry(
1699 2816 : ConcreteAttributePath(path1->mValue.mEndpointId, path1->mValue.mClusterId, path1->mValue.mAttributeId))
1700 1255 : .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 5840 : for (auto * path2 = aAttributePaths; path2 != nullptr; path2 = path2->mpNext)
1709 : {
1710 4603 : if (path2 == path1)
1711 : {
1712 1242 : 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 1246 : if (!duplicate)
1724 : {
1725 1237 : prev = path1;
1726 1237 : path1 = path1->mpNext;
1727 1237 : 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 977 : }
1744 :
1745 1138 : void InteractionModelEngine::ReleaseEventPathList(SingleLinkedListNode<EventPathParams> *& aEventPathList)
1746 : {
1747 1138 : ReleasePool(aEventPathList, mEventPathPool);
1748 1138 : }
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 2219 : void InteractionModelEngine::ReleaseDataVersionFilterList(SingleLinkedListNode<DataVersionFilter> *& aDataVersionFilterList)
1763 : {
1764 2219 : ReleasePool(aDataVersionFilterList, mDataVersionFilterPool);
1765 2219 : }
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 4503 : void InteractionModelEngine::ReleasePool(SingleLinkedListNode<T> *& aObjectList,
1781 : ObjectPool<SingleLinkedListNode<T>, N> & aObjectPool)
1782 : {
1783 4503 : SingleLinkedListNode<T> * current = aObjectList;
1784 9049 : while (current != nullptr)
1785 : {
1786 4546 : SingleLinkedListNode<T> * nextObject = current->mpNext;
1787 4546 : aObjectPool.ReleaseObject(current);
1788 4546 : current = nextObject;
1789 : }
1790 :
1791 4503 : aObjectList = nullptr;
1792 4503 : }
1793 :
1794 : template <typename T, size_t N>
1795 4555 : CHIP_ERROR InteractionModelEngine::PushFront(SingleLinkedListNode<T> *& aObjectList, T & aData,
1796 : ObjectPool<SingleLinkedListNode<T>, N> & aObjectPool)
1797 : {
1798 4555 : SingleLinkedListNode<T> * object = aObjectPool.CreateObject();
1799 4555 : if (object == nullptr)
1800 : {
1801 0 : return CHIP_ERROR_NO_MEMORY;
1802 : }
1803 4555 : object->mValue = aData;
1804 4555 : object->mpNext = aObjectList;
1805 4555 : aObjectList = object;
1806 4555 : return CHIP_NO_ERROR;
1807 : }
1808 :
1809 38 : void InteractionModelEngine::DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
1810 : TLV::TLVReader & apPayload)
1811 : {
1812 38 : Access::SubjectDescriptor subjectDescriptor = apCommandObj.GetSubjectDescriptor();
1813 :
1814 38 : DataModel::InvokeRequest request(aCommandPath, subjectDescriptor);
1815 38 : request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, apCommandObj.IsTimedInvoke());
1816 :
1817 38 : 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 38 : if (status.has_value())
1823 : {
1824 0 : apCommandObj.AddStatus(aCommandPath, status->GetStatusCode());
1825 : }
1826 38 : }
1827 :
1828 51 : Protocols::InteractionModel::Status InteractionModelEngine::ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request)
1829 : {
1830 51 : DataModel::AcceptedCommandEntry acceptedCommandEntry;
1831 :
1832 51 : const Status existenceStatus = CheckCommandExistence(request.path, acceptedCommandEntry);
1833 51 : 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 51 : Access::Privilege privilegeToCheck = commandExists ? acceptedCommandEntry.GetInvokePrivilege() : Access::Privilege::kOperate;
1851 :
1852 51 : Status accessStatus = CheckCommandAccess(request, privilegeToCheck);
1853 : // Groupcast Testing
1854 51 : auto & testing = Groupcast::GetTesting();
1855 51 : if (testing.IsEnabled() && testing.IsFabricUnderTest(request.GetAccessingFabricIndex()))
1856 : {
1857 0 : testing.SetAccessAllowed(Status::Success == accessStatus);
1858 0 : if (!testing.GetAccessAllowed().ValueOr(false))
1859 : {
1860 0 : testing.SetTestResult(Groupcast::Testing::Result::kFailedAuth);
1861 : }
1862 : }
1863 51 : VerifyOrReturnValue(accessStatus == Status::Success, accessStatus);
1864 :
1865 48 : if (!commandExists)
1866 : {
1867 10 : ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint %u",
1868 : ChipLogValueMEI(request.path.mCommandId), ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId);
1869 10 : return existenceStatus;
1870 : }
1871 :
1872 38 : return CheckCommandFlags(request, acceptedCommandEntry);
1873 : }
1874 :
1875 51 : Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(const DataModel::InvokeRequest & aRequest,
1876 : const Access::Privilege aRequiredPrivilege)
1877 : {
1878 51 : Access::RequestPath requestPath{ .cluster = aRequest.path.mClusterId,
1879 51 : .endpoint = aRequest.path.mEndpointId,
1880 : .requestType = Access::RequestType::kCommandInvokeRequest,
1881 51 : .entityId = aRequest.path.mCommandId };
1882 :
1883 51 : CHIP_ERROR err = Access::GetAccessControl().Check(aRequest.subjectDescriptor, requestPath, aRequiredPrivilege);
1884 102 : if (err != CHIP_NO_ERROR)
1885 : {
1886 6 : if ((err != CHIP_ERROR_ACCESS_DENIED) && (err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL))
1887 : {
1888 0 : return Status::Failure;
1889 : }
1890 6 : return err == CHIP_ERROR_ACCESS_DENIED ? Status::UnsupportedAccess : Status::AccessRestricted;
1891 : }
1892 :
1893 48 : return Status::Success;
1894 : }
1895 :
1896 38 : Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandFlags(const DataModel::InvokeRequest & aRequest,
1897 : const DataModel::AcceptedCommandEntry & entry)
1898 : {
1899 : // Command that is marked as having a large payload must be sent over a
1900 : // session that supports it.
1901 38 : if (entry.HasFlags(DataModel::CommandQualityFlags::kLargeMessage) &&
1902 38 : !CurrentExchange()->GetSessionHandle()->AllowsLargePayload())
1903 : {
1904 0 : return Status::InvalidTransportType;
1905 : }
1906 :
1907 38 : const bool commandIsFabricScoped = entry.HasFlags(DataModel::CommandQualityFlags::kFabricScoped);
1908 38 : if (commandIsFabricScoped)
1909 : {
1910 : // SPEC: Else if the command in the path is fabric-scoped and there is no accessing fabric,
1911 : // a CommandStatusIB SHALL be generated with the UNSUPPORTED_ACCESS Status Code.
1912 :
1913 : // Fabric-scoped commands are not allowed before a specific accessing fabric is available.
1914 : // This is mostly just during a PASE session before AddNOC.
1915 0 : if (aRequest.GetAccessingFabricIndex() == kUndefinedFabricIndex)
1916 : {
1917 0 : return Status::UnsupportedAccess;
1918 : }
1919 : }
1920 :
1921 38 : const bool commandNeedsTimedInvoke = entry.HasFlags(DataModel::CommandQualityFlags::kTimed);
1922 38 : if (commandNeedsTimedInvoke && !aRequest.invokeFlags.Has(DataModel::InvokeFlags::kTimed))
1923 : {
1924 0 : return Status::NeedsTimedInteraction;
1925 : }
1926 :
1927 38 : return Status::Success;
1928 : }
1929 :
1930 51 : Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistence(const ConcreteCommandPath & aCommandPath,
1931 : DataModel::AcceptedCommandEntry & entry)
1932 : {
1933 51 : auto provider = GetDataModelProvider();
1934 :
1935 51 : ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedCommands;
1936 51 : (void) provider->AcceptedCommands(aCommandPath, acceptedCommands);
1937 77 : for (auto & existing : acceptedCommands.TakeBuffer())
1938 : {
1939 64 : if (existing.commandId == aCommandPath.mCommandId)
1940 : {
1941 38 : entry = existing;
1942 38 : return Protocols::InteractionModel::Status::Success;
1943 : }
1944 51 : }
1945 :
1946 : // invalid command, return the right failure status
1947 13 : return DataModel::ValidateClusterPath(provider, aCommandPath, Protocols::InteractionModel::Status::UnsupportedCommand);
1948 51 : }
1949 :
1950 592 : DataModel::Provider * InteractionModelEngine::SetDataModelProvider(DataModel::Provider * model)
1951 : {
1952 : // Altering data model should not be done while IM is actively handling requests.
1953 592 : VerifyOrDie(mReadHandlers.begin() == mReadHandlers.end());
1954 :
1955 592 : DataModel::Provider * oldModel = mDataModelProvider;
1956 :
1957 592 : if (model == mDataModelProvider)
1958 : {
1959 18 : if (!mDataModelProviderNeedsStartup)
1960 : {
1961 : // Same provider, already running — no-op.
1962 0 : return model;
1963 : }
1964 : // Same provider but it was shut down (e.g. Server restart cycle).
1965 : // Don't call Shutdown() again — just proceed to re-call Startup() below.
1966 18 : ChipLogProgress(InteractionModel, "Re-starting data model provider %p (server restart cycle)", model);
1967 : }
1968 574 : else if (oldModel != nullptr)
1969 : {
1970 : // Different provider replacing the current one — shut down the old one first.
1971 282 : oldModel->UnregisterAttributeChangeListener(mReportingEngine);
1972 282 : if (!mDataModelProviderNeedsStartup)
1973 : {
1974 : // Only shut down if not already shut down by InteractionModelEngine::Shutdown().
1975 164 : CHIP_ERROR err = oldModel->Shutdown();
1976 328 : if (err != CHIP_NO_ERROR)
1977 : {
1978 0 : ChipLogError(InteractionModel, "Failure on interaction model shutdown: %" CHIP_ERROR_FORMAT, err.Format());
1979 : }
1980 : }
1981 : }
1982 :
1983 592 : mDataModelProvider = model;
1984 592 : if (mDataModelProvider != nullptr)
1985 : {
1986 312 : CHIP_ERROR err = mDataModelProvider->Startup({
1987 312 : .eventsGenerator = EventManagement::GetInstance(),
1988 : .actionContext = *this,
1989 : });
1990 :
1991 624 : if (err != CHIP_NO_ERROR)
1992 : {
1993 117 : ChipLogError(InteractionModel, "Failure on interaction model startup: %" CHIP_ERROR_FORMAT, err.Format());
1994 : }
1995 312 : mDataModelProviderNeedsStartup = false;
1996 : // Register to the new model (skip if same provider — already registered)
1997 312 : if (model != oldModel)
1998 : {
1999 294 : mDataModelProvider->RegisterAttributeChangeListener(mReportingEngine);
2000 : }
2001 : }
2002 :
2003 592 : return oldModel;
2004 : }
2005 :
2006 15901 : DataModel::Provider * InteractionModelEngine::GetDataModelProvider() const
2007 : {
2008 : // These should be called within the CHIP processing loop.
2009 15901 : assertChipStackLockedByCurrentThread();
2010 :
2011 15901 : return mDataModelProvider;
2012 : }
2013 :
2014 2 : void InteractionModelEngine::OnTimedInteractionFailed(TimedHandler * apTimedHandler)
2015 : {
2016 2 : mTimedHandlers.ReleaseObject(apTimedHandler);
2017 2 : }
2018 :
2019 1 : void InteractionModelEngine::OnTimedInvoke(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
2020 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
2021 : {
2022 : using namespace Protocols::InteractionModel;
2023 :
2024 : // Reset the ourselves as the exchange delegate for now, to match what we'd
2025 : // do with an initial unsolicited invoke.
2026 1 : apExchangeContext->SetDelegate(this);
2027 1 : mTimedHandlers.ReleaseObject(apTimedHandler);
2028 :
2029 1 : VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::InvokeCommandRequest));
2030 1 : VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
2031 :
2032 : // Ensure that DataModel::Provider has access to the exchange the message was received on.
2033 1 : CurrentExchangeValueScope scopedExchangeContext(*this, apExchangeContext);
2034 :
2035 1 : Status status = OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ true);
2036 1 : if (status != Status::Success)
2037 : {
2038 0 : TEMPORARY_RETURN_IGNORED StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
2039 : }
2040 1 : }
2041 :
2042 1 : void InteractionModelEngine::OnTimedWrite(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
2043 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
2044 : {
2045 : using namespace Protocols::InteractionModel;
2046 :
2047 : // Reset the ourselves as the exchange delegate for now, to match what we'd
2048 : // do with an initial unsolicited write.
2049 1 : apExchangeContext->SetDelegate(this);
2050 1 : mTimedHandlers.ReleaseObject(apTimedHandler);
2051 :
2052 1 : VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::WriteRequest));
2053 1 : VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
2054 :
2055 : // Ensure that DataModel::Provider has access to the exchange the message was received on.
2056 1 : CurrentExchangeValueScope scopedExchangeContext(*this, apExchangeContext);
2057 :
2058 1 : Status status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ true);
2059 1 : if (status != Status::Success)
2060 : {
2061 1 : TEMPORARY_RETURN_IGNORED StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
2062 : }
2063 1 : }
2064 :
2065 0 : bool InteractionModelEngine::HasActiveRead()
2066 : {
2067 0 : return ((mReadHandlers.ForEachActiveObject([](ReadHandler * handler) {
2068 0 : if (handler->IsType(ReadHandler::InteractionType::Read))
2069 : {
2070 0 : return Loop::Break;
2071 : }
2072 :
2073 0 : return Loop::Continue;
2074 0 : }) == Loop::Break));
2075 : }
2076 :
2077 8 : uint16_t InteractionModelEngine::GetMinGuaranteedSubscriptionsPerFabric() const
2078 : {
2079 : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
2080 8 : return kMaxNumSubscriptionsPerFabric;
2081 : #else
2082 : return static_cast<uint16_t>(std::min(GetReadHandlerPoolCapacityForSubscriptions() / GetConfigMaxFabrics(),
2083 : static_cast<size_t>(kMaxNumSubscriptionsPerFabric)));
2084 : #endif
2085 : }
2086 :
2087 5 : size_t InteractionModelEngine::GetNumDirtySubscriptions() const
2088 : {
2089 5 : size_t numDirtySubscriptions = 0;
2090 5 : mReadHandlers.ForEachActiveObject([&](const auto readHandler) {
2091 33 : if (readHandler->IsType(ReadHandler::InteractionType::Subscribe) && readHandler->IsDirty())
2092 : {
2093 8 : numDirtySubscriptions++;
2094 : }
2095 33 : return Loop::Continue;
2096 : });
2097 5 : return numDirtySubscriptions;
2098 : }
2099 :
2100 7 : void InteractionModelEngine::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
2101 : {
2102 7 : mReadHandlers.ForEachActiveObject([fabricIndex](ReadHandler * handler) {
2103 67 : if (handler->GetAccessingFabricIndex() == fabricIndex)
2104 : {
2105 67 : ChipLogProgress(InteractionModel, "Deleting expired ReadHandler for NodeId: " ChipLogFormatX64 ", FabricIndex: %u",
2106 : ChipLogValueX64(handler->GetInitiatorNodeId()), fabricIndex);
2107 67 : handler->Close();
2108 : }
2109 :
2110 67 : return Loop::Continue;
2111 : });
2112 :
2113 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
2114 141 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
2115 : {
2116 : // ReadClient::Close may delete the read client so that readClient->GetNextClient() will be use-after-free.
2117 : // We need save readClient as nextReadClient before closing.
2118 134 : if (readClient->GetFabricIndex() == fabricIndex)
2119 : {
2120 67 : ChipLogProgress(InteractionModel, "Fabric removed, deleting obsolete read client with FabricIndex: %u", fabricIndex);
2121 67 : auto * nextReadClient = readClient->GetNextClient();
2122 67 : readClient->Close(CHIP_ERROR_IM_FABRIC_DELETED, false);
2123 67 : readClient = nextReadClient;
2124 : }
2125 : else
2126 : {
2127 67 : readClient = readClient->GetNextClient();
2128 : }
2129 : }
2130 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
2131 :
2132 35 : for (auto & handler : mWriteHandlers)
2133 : {
2134 28 : if (!(handler.IsFree()) && handler.GetAccessingFabricIndex() == fabricIndex)
2135 : {
2136 1 : ChipLogProgress(InteractionModel, "Fabric removed, deleting obsolete write handler with FabricIndex: %u", fabricIndex);
2137 1 : handler.Close();
2138 : }
2139 : }
2140 :
2141 : // Applications may hold references to CommandHandlerImpl instances for async command processing.
2142 : // Therefore we can't forcible destroy CommandHandlers here. Their exchanges will get closed by
2143 : // the fabric removal, though, so they will fail when they try to actually send their command response
2144 : // and will close at that point.
2145 7 : }
2146 :
2147 2 : CHIP_ERROR InteractionModelEngine::ResumeSubscriptions()
2148 : {
2149 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
2150 2 : VerifyOrReturnError(mpSubscriptionResumptionStorage, CHIP_NO_ERROR);
2151 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2152 2 : VerifyOrReturnError(!mSubscriptionResumptionScheduled, CHIP_NO_ERROR);
2153 : #endif
2154 :
2155 : // To avoid the case of a reboot loop causing rapid traffic generation / power consumption, subscription resumption should make
2156 : // use of the persisted min-interval values, and wait before resumption. Ideally, each persisted subscription should wait their
2157 : // own min-interval value before resumption, but that both A) potentially runs into a timer resource issue, and B) having a
2158 : // low-powered device wake many times also has energy use implications. The logic below waits the largest of the persisted
2159 : // min-interval values before resuming subscriptions.
2160 :
2161 : // Even though this causes subscription-to-subscription interaction by linking the min-interval values, this is the right thing
2162 : // to do for now because it's both simple and avoids the timer resource and multiple-wake problems. This issue is to track
2163 : // future improvements: https://github.com/project-chip/connectedhomeip/issues/25439
2164 :
2165 2 : SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
2166 2 : mNumOfSubscriptionsToResume = 0;
2167 2 : uint16_t minInterval = 0;
2168 :
2169 2 : auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions();
2170 2 : VerifyOrReturnError(nullptr != iterator, CHIP_ERROR_NO_MEMORY);
2171 1 : while (iterator->Next(subscriptionInfo))
2172 : {
2173 0 : mNumOfSubscriptionsToResume++;
2174 0 : minInterval = std::max(minInterval, subscriptionInfo.mMinInterval);
2175 : }
2176 1 : iterator->Release();
2177 :
2178 1 : if (mNumOfSubscriptionsToResume)
2179 : {
2180 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2181 0 : mSubscriptionResumptionScheduled = true;
2182 : #endif
2183 0 : ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", mNumOfSubscriptionsToResume, minInterval);
2184 0 : ReturnErrorOnFailure(mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds16(minInterval),
2185 : ResumeSubscriptionsTimerCallback, this));
2186 : }
2187 : else
2188 : {
2189 1 : ChipLogProgress(InteractionModel, "No subscriptions to resume");
2190 : }
2191 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
2192 :
2193 1 : return CHIP_NO_ERROR;
2194 2 : }
2195 :
2196 0 : void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * apSystemLayer, void * apAppState)
2197 : {
2198 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
2199 0 : VerifyOrReturn(apAppState != nullptr);
2200 0 : InteractionModelEngine * imEngine = static_cast<InteractionModelEngine *>(apAppState);
2201 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2202 0 : imEngine->mSubscriptionResumptionScheduled = false;
2203 0 : bool resumedSubscriptions = false;
2204 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2205 0 : SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
2206 0 : AutoReleaseSubscriptionInfoIterator iterator(imEngine->mpSubscriptionResumptionStorage->IterateSubscriptions());
2207 0 : VerifyOrReturn(iterator, ChipLogError(InteractionModel, "Failed to allocate subscription resumption iterator"));
2208 0 : while (iterator->Next(subscriptionInfo))
2209 : {
2210 : // If subscription happens between reboot and this timer callback, it's already live and should skip resumption
2211 0 : if (Loop::Break == imEngine->mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
2212 : SubscriptionId subscriptionId;
2213 0 : handler->GetSubscriptionId(subscriptionId);
2214 0 : if (subscriptionId == subscriptionInfo.mSubscriptionId)
2215 : {
2216 0 : return Loop::Break;
2217 : }
2218 0 : return Loop::Continue;
2219 : }))
2220 : {
2221 0 : ChipLogProgress(InteractionModel, "Skip resuming live subscriptionId %" PRIu32, subscriptionInfo.mSubscriptionId);
2222 0 : continue;
2223 : }
2224 :
2225 0 : auto subscriptionResumptionSessionEstablisher = Platform::MakeUnique<SubscriptionResumptionSessionEstablisher>();
2226 0 : if (subscriptionResumptionSessionEstablisher == nullptr)
2227 : {
2228 0 : ChipLogProgress(InteractionModel, "Failed to create SubscriptionResumptionSessionEstablisher");
2229 0 : return;
2230 : }
2231 :
2232 0 : if (subscriptionResumptionSessionEstablisher->ResumeSubscription(*imEngine->mpCASESessionMgr, subscriptionInfo) !=
2233 0 : CHIP_NO_ERROR)
2234 : {
2235 0 : ChipLogProgress(InteractionModel, "Failed to ResumeSubscription 0x%" PRIx32, subscriptionInfo.mSubscriptionId);
2236 0 : return;
2237 : }
2238 0 : subscriptionResumptionSessionEstablisher.release();
2239 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2240 0 : resumedSubscriptions = true;
2241 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2242 0 : }
2243 :
2244 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2245 : // If no persisted subscriptions needed resumption then all resumption retries are done
2246 0 : if (!resumedSubscriptions)
2247 : {
2248 0 : imEngine->mNumSubscriptionResumptionRetries = 0;
2249 : }
2250 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2251 :
2252 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
2253 0 : }
2254 :
2255 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2256 12 : uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption()
2257 : {
2258 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
2259 12 : if (mSubscriptionResumptionRetrySecondsOverride > 0)
2260 : {
2261 0 : return static_cast<uint32_t>(mSubscriptionResumptionRetrySecondsOverride);
2262 : }
2263 : #endif
2264 12 : if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX)
2265 : {
2266 1 : return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS;
2267 : }
2268 :
2269 : return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS +
2270 11 : GetFibonacciForIndex(mNumSubscriptionResumptionRetries) *
2271 11 : CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS;
2272 : }
2273 :
2274 868 : bool InteractionModelEngine::HasSubscriptionsToResume()
2275 : {
2276 868 : VerifyOrReturnValue(mpSubscriptionResumptionStorage != nullptr, false);
2277 :
2278 : // Look through persisted subscriptions and see if any aren't already in mReadHandlers pool
2279 1 : SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
2280 1 : auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions();
2281 : // Conservatively assume there may be subscriptions to resume if we cannot get an iterator.
2282 1 : VerifyOrReturnValue(iterator != nullptr, true);
2283 0 : bool foundSubscriptionToResume = false;
2284 0 : while (iterator->Next(subscriptionInfo))
2285 : {
2286 0 : if (Loop::Break == mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
2287 : SubscriptionId subscriptionId;
2288 0 : handler->GetSubscriptionId(subscriptionId);
2289 0 : if (subscriptionId == subscriptionInfo.mSubscriptionId)
2290 : {
2291 0 : return Loop::Break;
2292 : }
2293 0 : return Loop::Continue;
2294 : }))
2295 : {
2296 0 : continue;
2297 : }
2298 :
2299 0 : foundSubscriptionToResume = true;
2300 0 : break;
2301 : }
2302 0 : iterator->Release();
2303 :
2304 0 : return foundSubscriptionToResume;
2305 1 : }
2306 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2307 :
2308 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
2309 6 : void InteractionModelEngine::DecrementNumSubscriptionsToResume()
2310 : {
2311 6 : VerifyOrReturn(mNumOfSubscriptionsToResume > 0);
2312 5 : mNumOfSubscriptionsToResume--;
2313 :
2314 : #if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2315 : if (mICDManager && !mNumOfSubscriptionsToResume)
2316 : {
2317 : mICDManager->SetBootUpResumeSubscriptionExecuted();
2318 : }
2319 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2320 : }
2321 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
2322 :
2323 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2324 0 : void InteractionModelEngine::ResetNumSubscriptionsRetries()
2325 : {
2326 : // Check if there are any subscriptions to resume, if not the retry counter can be reset.
2327 0 : if (!HasSubscriptionsToResume())
2328 : {
2329 0 : mNumSubscriptionResumptionRetries = 0;
2330 : }
2331 0 : }
2332 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2333 :
2334 0 : MessageStats InteractionModelEngine::GetMessageStats()
2335 : {
2336 0 : return GetExchangeManager()->GetSessionManager()->GetMessageStats();
2337 : }
2338 :
2339 0 : SubscriptionStats InteractionModelEngine::GetSubscriptionStats(FabricIndex fabric)
2340 : {
2341 0 : return SubscriptionStats{ .numTotalSubscriptions = GetReportScheduler()->GetTotalSubscriptionsEstablished(),
2342 : .numCurrentSubscriptions =
2343 0 : static_cast<uint16_t>(GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe)),
2344 : .numCurrentSubscriptionsForFabric = static_cast<uint16_t>(
2345 0 : GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe, fabric)) };
2346 : }
2347 :
2348 : } // namespace app
2349 : } // namespace chip
|