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