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