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