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 <cinttypes>
29 :
30 : #include "access/RequestPath.h"
31 : #include "access/SubjectDescriptor.h"
32 : #include <app/AppConfig.h>
33 : #include <app/RequiredPrivilege.h>
34 : #include <app/util/af-types.h>
35 : #include <app/util/endpoint-config-api.h>
36 : #include <lib/core/Global.h>
37 : #include <lib/core/TLVUtilities.h>
38 : #include <lib/support/CodeUtils.h>
39 : #include <lib/support/FibonacciUtils.h>
40 :
41 : namespace chip {
42 : namespace app {
43 :
44 : class AutoReleaseSubscriptionInfoIterator
45 : {
46 : public:
47 0 : AutoReleaseSubscriptionInfoIterator(SubscriptionResumptionStorage::SubscriptionInfoIterator * iterator) : mIterator(iterator){};
48 0 : ~AutoReleaseSubscriptionInfoIterator() { mIterator->Release(); }
49 :
50 0 : SubscriptionResumptionStorage::SubscriptionInfoIterator * operator->() const { return mIterator; }
51 :
52 : private:
53 : SubscriptionResumptionStorage::SubscriptionInfoIterator * mIterator;
54 : };
55 :
56 : using Protocols::InteractionModel::Status;
57 :
58 : Global<InteractionModelEngine> sInteractionModelEngine;
59 :
60 130 : InteractionModelEngine::InteractionModelEngine() : mReportingEngine(this) {}
61 :
62 11464 : InteractionModelEngine * InteractionModelEngine::GetInstance()
63 : {
64 11464 : return &sInteractionModelEngine.get();
65 : }
66 :
67 367 : CHIP_ERROR InteractionModelEngine::Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable,
68 : reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr,
69 : SubscriptionResumptionStorage * subscriptionResumptionStorage)
70 : {
71 367 : VerifyOrReturnError(apFabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
72 367 : VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
73 367 : VerifyOrReturnError(reportScheduler != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
74 :
75 367 : mpExchangeMgr = apExchangeMgr;
76 367 : mpFabricTable = apFabricTable;
77 367 : mpCASESessionMgr = apCASESessionMgr;
78 367 : mpSubscriptionResumptionStorage = subscriptionResumptionStorage;
79 367 : mReportScheduler = reportScheduler;
80 :
81 367 : ReturnErrorOnFailure(mpFabricTable->AddFabricDelegate(this));
82 367 : ReturnErrorOnFailure(mpExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id, this));
83 :
84 367 : mReportingEngine.Init();
85 367 : mMagic++;
86 :
87 367 : StatusIB::RegisterErrorFormatter();
88 :
89 367 : return CHIP_NO_ERROR;
90 : }
91 :
92 363 : void InteractionModelEngine::Shutdown()
93 : {
94 363 : mpExchangeMgr->GetSessionManager()->SystemLayer()->CancelTimer(ResumeSubscriptionsTimerCallback, this);
95 :
96 363 : CommandHandlerInterface * handlerIter = mCommandHandlerList;
97 :
98 : //
99 : // Walk our list of command handlers and de-register them, before finally
100 : // nulling out the list entirely.
101 : //
102 363 : while (handlerIter)
103 : {
104 0 : CommandHandlerInterface * nextHandler = handlerIter->GetNext();
105 0 : handlerIter->SetNext(nullptr);
106 0 : handlerIter = nextHandler;
107 : }
108 :
109 363 : mCommandHandlerList = nullptr;
110 :
111 : // Increase magic number to invalidate all Handle-s.
112 363 : mMagic++;
113 :
114 363 : mCommandHandlerObjs.ReleaseAll();
115 :
116 363 : mTimedHandlers.ForEachActiveObject([this](TimedHandler * obj) -> Loop {
117 1 : mpExchangeMgr->CloseAllContextsForDelegate(obj);
118 1 : return Loop::Continue;
119 : });
120 :
121 363 : mTimedHandlers.ReleaseAll();
122 :
123 363 : mReadHandlers.ReleaseAll();
124 :
125 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
126 : // Shut down any subscription clients that are still around. They won't be
127 : // able to work after this point anyway, since we're about to drop our refs
128 : // to them.
129 363 : ShutdownAllSubscriptions();
130 :
131 : //
132 : // We hold weak references to ReadClient objects. The application ultimately
133 : // actually owns them, so it's on them to eventually shut them down and free them
134 : // up.
135 : //
136 : // However, we should null out their pointers back to us at the very least so that
137 : // at destruction time, they won't attempt to reach back here to remove themselves
138 : // from this list.
139 : //
140 367 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
141 : {
142 4 : readClient->mpImEngine = nullptr;
143 4 : auto * tmpClient = readClient->GetNextClient();
144 4 : readClient->SetNextClient(nullptr);
145 4 : readClient = tmpClient;
146 : }
147 :
148 : //
149 : // After that, we just null out our tracker.
150 : //
151 363 : mpActiveReadClientList = nullptr;
152 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
153 :
154 1815 : for (auto & writeHandler : mWriteHandlers)
155 : {
156 1452 : if (!writeHandler.IsFree())
157 : {
158 0 : writeHandler.Close();
159 : }
160 : }
161 :
162 363 : mReportingEngine.Shutdown();
163 363 : mAttributePathPool.ReleaseAll();
164 363 : mEventPathPool.ReleaseAll();
165 363 : mDataVersionFilterPool.ReleaseAll();
166 363 : mpExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id);
167 :
168 363 : mpCASESessionMgr = nullptr;
169 :
170 : //
171 : // We _should_ be clearing these out, but doing so invites a world
172 : // of trouble. #21233 tracks fixing the underlying assumptions to make
173 : // this possible.
174 : //
175 : // mpFabricTable = nullptr;
176 : // mpExchangeMgr = nullptr;
177 363 : }
178 :
179 69 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers() const
180 : {
181 69 : return static_cast<uint32_t>(mReadHandlers.Allocated());
182 : }
183 :
184 51 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers(ReadHandler::InteractionType aType) const
185 : {
186 51 : uint32_t count = 0;
187 :
188 51 : mReadHandlers.ForEachActiveObject([aType, &count](const ReadHandler * handler) {
189 73 : if (handler->IsType(aType))
190 : {
191 61 : count++;
192 : }
193 :
194 73 : return Loop::Continue;
195 : });
196 :
197 51 : return count;
198 : }
199 :
200 19 : uint32_t InteractionModelEngine::GetNumActiveReadHandlers(ReadHandler::InteractionType aType, FabricIndex aFabricIndex) const
201 : {
202 19 : uint32_t count = 0;
203 :
204 19 : mReadHandlers.ForEachActiveObject([aType, aFabricIndex, &count](const ReadHandler * handler) {
205 66 : if (handler->IsType(aType) && handler->GetAccessingFabricIndex() == aFabricIndex)
206 : {
207 31 : count++;
208 : }
209 :
210 66 : return Loop::Continue;
211 : });
212 :
213 19 : return count;
214 : }
215 :
216 2095 : ReadHandler * InteractionModelEngine::ActiveHandlerAt(unsigned int aIndex)
217 : {
218 2095 : if (aIndex >= mReadHandlers.Allocated())
219 : {
220 0 : return nullptr;
221 : }
222 :
223 2095 : unsigned int i = 0;
224 2095 : ReadHandler * ret = nullptr;
225 :
226 2095 : mReadHandlers.ForEachActiveObject([aIndex, &i, &ret](ReadHandler * handler) {
227 10685 : if (i == aIndex)
228 : {
229 2095 : ret = handler;
230 2095 : return Loop::Break;
231 : }
232 :
233 8590 : i++;
234 8590 : return Loop::Continue;
235 : });
236 :
237 2095 : return ret;
238 : }
239 :
240 1 : WriteHandler * InteractionModelEngine::ActiveWriteHandlerAt(unsigned int aIndex)
241 : {
242 1 : unsigned int i = 0;
243 :
244 1 : for (auto & writeHandler : mWriteHandlers)
245 : {
246 1 : if (!writeHandler.IsFree())
247 : {
248 1 : if (i == aIndex)
249 : {
250 1 : return &writeHandler;
251 : }
252 0 : i++;
253 : }
254 : }
255 0 : return nullptr;
256 : }
257 :
258 11 : uint32_t InteractionModelEngine::GetNumActiveWriteHandlers() const
259 : {
260 11 : uint32_t numActive = 0;
261 :
262 55 : for (auto & writeHandler : mWriteHandlers)
263 : {
264 44 : if (!writeHandler.IsFree())
265 : {
266 2 : numActive++;
267 : }
268 : }
269 :
270 11 : return numActive;
271 : }
272 :
273 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
274 2 : CHIP_ERROR InteractionModelEngine::ShutdownSubscription(const ScopedNodeId & aPeerNodeId, SubscriptionId aSubscriptionId)
275 : {
276 2 : assertChipStackLockedByCurrentThread();
277 2 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
278 : {
279 : // Grab the next client now, because we might be about to delete readClient.
280 2 : auto * nextClient = readClient->GetNextClient();
281 6 : if (readClient->IsSubscriptionType() && readClient->IsMatchingSubscriptionId(aSubscriptionId) &&
282 6 : readClient->GetFabricIndex() == aPeerNodeId.GetFabricIndex() && readClient->GetPeerNodeId() == aPeerNodeId.GetNodeId())
283 : {
284 2 : readClient->Close(CHIP_NO_ERROR);
285 2 : return CHIP_NO_ERROR;
286 : }
287 0 : readClient = nextClient;
288 : }
289 :
290 0 : return CHIP_ERROR_KEY_NOT_FOUND;
291 : }
292 :
293 0 : void InteractionModelEngine::ShutdownSubscriptions(FabricIndex aFabricIndex, NodeId aPeerNodeId)
294 : {
295 0 : assertChipStackLockedByCurrentThread();
296 0 : ShutdownMatchingSubscriptions(MakeOptional(aFabricIndex), MakeOptional(aPeerNodeId));
297 0 : }
298 0 : void InteractionModelEngine::ShutdownSubscriptions(FabricIndex aFabricIndex)
299 : {
300 0 : assertChipStackLockedByCurrentThread();
301 0 : ShutdownMatchingSubscriptions(MakeOptional(aFabricIndex));
302 0 : }
303 :
304 363 : void InteractionModelEngine::ShutdownAllSubscriptions()
305 : {
306 363 : assertChipStackLockedByCurrentThread();
307 363 : ShutdownMatchingSubscriptions();
308 363 : }
309 :
310 363 : void InteractionModelEngine::ShutdownMatchingSubscriptions(const Optional<FabricIndex> & aFabricIndex,
311 : const Optional<NodeId> & aPeerNodeId)
312 : {
313 : // This is assuming that ReadClient::Close will not affect any other
314 : // ReadClients in the list.
315 367 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
316 : {
317 : // Grab the next client now, because we might be about to delete readClient.
318 4 : auto * nextClient = readClient->GetNextClient();
319 4 : if (readClient->IsSubscriptionType())
320 : {
321 4 : bool fabricMatches = !aFabricIndex.HasValue() || (aFabricIndex.Value() == readClient->GetFabricIndex());
322 4 : bool nodeIdMatches = !aPeerNodeId.HasValue() || (aPeerNodeId.Value() == readClient->GetPeerNodeId());
323 4 : if (fabricMatches && nodeIdMatches)
324 : {
325 4 : readClient->Close(CHIP_NO_ERROR);
326 : }
327 : }
328 4 : readClient = nextClient;
329 : }
330 363 : }
331 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
332 :
333 0 : bool InteractionModelEngine::SubjectHasActiveSubscription(FabricIndex aFabricIndex, NodeId subjectID)
334 : {
335 0 : bool isActive = false;
336 0 : mReadHandlers.ForEachActiveObject([aFabricIndex, subjectID, &isActive](ReadHandler * handler) {
337 0 : if (!handler->IsType(ReadHandler::InteractionType::Subscribe))
338 : {
339 0 : return Loop::Continue;
340 : }
341 :
342 0 : Access::SubjectDescriptor subject = handler->GetSubjectDescriptor();
343 0 : if (subject.fabricIndex != aFabricIndex)
344 : {
345 0 : return Loop::Continue;
346 : }
347 :
348 0 : if (subject.authMode == Access::AuthMode::kCase)
349 : {
350 0 : if (subject.cats.CheckSubjectAgainstCATs(subjectID) || subjectID == subject.subject)
351 : {
352 0 : isActive = handler->IsActiveSubscription();
353 :
354 : // Exit loop only if isActive is set to true
355 : // Otherwise keep looking for another subscription that could
356 : // match the subject
357 0 : if (isActive)
358 : {
359 0 : return Loop::Break;
360 : }
361 : }
362 : }
363 :
364 0 : return Loop::Continue;
365 : });
366 :
367 0 : return isActive;
368 : }
369 :
370 0 : bool InteractionModelEngine::SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subjectID)
371 : {
372 : // TODO(#30281) : Implement persisted sub check and verify how persistent subscriptions affects this at ICDManager::Init
373 0 : return false;
374 : }
375 :
376 31 : void InteractionModelEngine::OnDone(CommandHandler & apCommandObj)
377 : {
378 31 : mCommandHandlerObjs.ReleaseObject(&apCommandObj);
379 31 : }
380 :
381 727 : void InteractionModelEngine::OnDone(ReadHandler & apReadObj)
382 : {
383 : //
384 : // Deleting an item can shift down the contents of the underlying pool storage,
385 : // rendering any tracker using positional indexes invalid. Let's reset it,
386 : // based on which readHandler we are getting rid of.
387 : //
388 727 : mReportingEngine.ResetReadHandlerTracker(&apReadObj);
389 :
390 727 : mReadHandlers.ReleaseObject(&apReadObj);
391 :
392 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
393 727 : if (!mSubscriptionResumptionScheduled && HasSubscriptionsToResume())
394 : {
395 0 : mSubscriptionResumptionScheduled = true;
396 0 : auto timeTillNextSubscriptionResumptionSecs = ComputeTimeSecondsTillNextSubscriptionResumption();
397 0 : mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(
398 0 : System::Clock::Seconds32(timeTillNextSubscriptionResumptionSecs), ResumeSubscriptionsTimerCallback, this);
399 0 : mNumSubscriptionResumptionRetries++;
400 : }
401 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
402 727 : }
403 :
404 31 : Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext,
405 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
406 : bool aIsTimedInvoke)
407 : {
408 31 : CommandHandler * commandHandler = mCommandHandlerObjs.CreateObject(this);
409 31 : if (commandHandler == nullptr)
410 : {
411 0 : ChipLogProgress(InteractionModel, "no resource for Invoke interaction");
412 0 : return Status::Busy;
413 : }
414 31 : commandHandler->OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), aIsTimedInvoke);
415 31 : return Status::Success;
416 : }
417 :
418 228 : CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDescriptor & aSubjectDescriptor,
419 : AttributePathIBs::Parser & aAttributePathListParser,
420 : bool & aHasValidAttributePath, size_t & aRequestedAttributePathCount)
421 : {
422 : TLV::TLVReader pathReader;
423 228 : aAttributePathListParser.GetReader(&pathReader);
424 228 : CHIP_ERROR err = CHIP_NO_ERROR;
425 :
426 228 : aHasValidAttributePath = false;
427 228 : aRequestedAttributePathCount = 0;
428 :
429 524 : while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
430 : {
431 296 : AttributePathIB::Parser path;
432 : //
433 : // We create an iterator to point to a single item object list that tracks the path we just parsed.
434 : // This avoids the 'parse all paths' approach that is employed in ReadHandler since we want to
435 : // avoid allocating out of the path store during this minimal initial processing stage.
436 : //
437 296 : ObjectList<AttributePathParams> paramsList;
438 :
439 296 : ReturnErrorOnFailure(path.Init(pathReader));
440 296 : ReturnErrorOnFailure(path.ParsePath(paramsList.mValue));
441 :
442 296 : if (paramsList.mValue.IsWildcardPath())
443 : {
444 13 : AttributePathExpandIterator pathIterator(¶msList);
445 13 : ConcreteAttributePath readPath;
446 :
447 : // The definition of "valid path" is "path exists and ACL allows access". The "path exists" part is handled by
448 : // AttributePathExpandIterator. So we just need to check the ACL bits.
449 13 : for (; pathIterator.Get(readPath); pathIterator.Next())
450 : {
451 10 : Access::RequestPath requestPath{ .cluster = readPath.mClusterId, .endpoint = readPath.mEndpointId };
452 10 : err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath,
453 : RequiredPrivilege::ForReadAttribute(readPath));
454 10 : if (err == CHIP_NO_ERROR)
455 : {
456 10 : aHasValidAttributePath = true;
457 10 : break;
458 : }
459 : }
460 : }
461 : else
462 : {
463 283 : ConcreteAttributePath concretePath(paramsList.mValue.mEndpointId, paramsList.mValue.mClusterId,
464 283 : paramsList.mValue.mAttributeId);
465 283 : if (ConcreteAttributePathExists(concretePath))
466 : {
467 280 : Access::RequestPath requestPath{ .cluster = concretePath.mClusterId, .endpoint = concretePath.mEndpointId };
468 :
469 280 : err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath,
470 : RequiredPrivilege::ForReadAttribute(concretePath));
471 280 : if (err == CHIP_NO_ERROR)
472 : {
473 280 : aHasValidAttributePath = true;
474 : }
475 : }
476 : }
477 :
478 296 : aRequestedAttributePathCount++;
479 : }
480 :
481 228 : if (err == CHIP_ERROR_END_OF_TLV)
482 : {
483 228 : err = CHIP_NO_ERROR;
484 : }
485 :
486 228 : return err;
487 : }
488 :
489 18 : static bool CanAccess(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteClusterPath & aPath,
490 : Access::Privilege aNeededPrivilege)
491 : {
492 18 : Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId };
493 18 : CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, aNeededPrivilege);
494 18 : return (err == CHIP_NO_ERROR);
495 : }
496 :
497 12 : static bool CanAccess(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteEventPath & aPath)
498 : {
499 12 : return CanAccess(aSubjectDescriptor, aPath, RequiredPrivilege::ForReadEvent(aPath));
500 : }
501 :
502 : /**
503 : * Helper to handle wildcard events in the event path.
504 : */
505 18 : static bool HasValidEventPathForEndpointAndCluster(EndpointId aEndpoint, const EmberAfCluster * aCluster,
506 : const EventPathParams & aEventPath,
507 : const Access::SubjectDescriptor & aSubjectDescriptor)
508 : {
509 18 : if (aEventPath.HasWildcardEventId())
510 : {
511 : #if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE
512 : for (decltype(aCluster->eventCount) idx = 0; idx < aCluster->eventCount; ++idx)
513 : {
514 : ConcreteEventPath path(aEndpoint, aCluster->clusterId, aCluster->eventList[idx]);
515 : // If we get here, the path exists. We just have to do an ACL check for it.
516 : bool isValid = CanAccess(aSubjectDescriptor, path);
517 : if (isValid)
518 : {
519 : return true;
520 : }
521 : }
522 :
523 : return false;
524 : #else
525 : // We have no way to expand wildcards. Just assume that we would need
526 : // View permissions for whatever events are involved.
527 6 : ConcreteClusterPath clusterPath(aEndpoint, aCluster->clusterId);
528 6 : return CanAccess(aSubjectDescriptor, clusterPath, Access::Privilege::kView);
529 : #endif
530 : }
531 :
532 12 : ConcreteEventPath path(aEndpoint, aCluster->clusterId, aEventPath.mEventId);
533 12 : if (CheckEventSupportStatus(path) != Status::Success)
534 : {
535 : // Not an existing event path.
536 0 : return false;
537 : }
538 12 : return CanAccess(aSubjectDescriptor, path);
539 : }
540 :
541 : /**
542 : * Helper to handle wildcard clusters in the event path.
543 : */
544 18 : static bool HasValidEventPathForEndpoint(EndpointId aEndpoint, const EventPathParams & aEventPath,
545 : const Access::SubjectDescriptor & aSubjectDescriptor)
546 : {
547 18 : if (aEventPath.HasWildcardClusterId())
548 : {
549 0 : auto * endpointType = emberAfFindEndpointType(aEndpoint);
550 0 : if (endpointType == nullptr)
551 : {
552 : // Not going to have any valid paths in here.
553 0 : return false;
554 : }
555 :
556 0 : for (decltype(endpointType->clusterCount) idx = 0; idx < endpointType->clusterCount; ++idx)
557 : {
558 : bool hasValidPath =
559 0 : HasValidEventPathForEndpointAndCluster(aEndpoint, &endpointType->cluster[idx], aEventPath, aSubjectDescriptor);
560 0 : if (hasValidPath)
561 : {
562 0 : return true;
563 : }
564 : }
565 :
566 0 : return false;
567 : }
568 :
569 18 : auto * cluster = emberAfFindServerCluster(aEndpoint, aEventPath.mClusterId);
570 18 : if (cluster == nullptr)
571 : {
572 : // Nothing valid here.
573 0 : return false;
574 : }
575 18 : return HasValidEventPathForEndpointAndCluster(aEndpoint, cluster, aEventPath, aSubjectDescriptor);
576 : }
577 :
578 18 : CHIP_ERROR InteractionModelEngine::ParseEventPaths(const Access::SubjectDescriptor & aSubjectDescriptor,
579 : EventPathIBs::Parser & aEventPathListParser, bool & aHasValidEventPath,
580 : size_t & aRequestedEventPathCount)
581 : {
582 : TLV::TLVReader pathReader;
583 18 : aEventPathListParser.GetReader(&pathReader);
584 18 : CHIP_ERROR err = CHIP_NO_ERROR;
585 :
586 18 : aHasValidEventPath = false;
587 18 : aRequestedEventPathCount = 0;
588 :
589 52 : while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
590 : {
591 34 : EventPathIB::Parser path;
592 34 : ReturnErrorOnFailure(path.Init(pathReader));
593 :
594 34 : EventPathParams eventPath;
595 34 : ReturnErrorOnFailure(path.ParsePath(eventPath));
596 :
597 34 : ++aRequestedEventPathCount;
598 :
599 34 : if (aHasValidEventPath)
600 : {
601 : // Can skip all the rest of the checking.
602 16 : continue;
603 : }
604 :
605 : // The definition of "valid path" is "path exists and ACL allows
606 : // access". We need to do some expansion of wildcards to handle that.
607 18 : if (eventPath.HasWildcardEndpointId())
608 : {
609 0 : for (uint16_t endpointIndex = 0; !aHasValidEventPath && endpointIndex < emberAfEndpointCount(); ++endpointIndex)
610 : {
611 0 : if (!emberAfEndpointIndexIsEnabled(endpointIndex))
612 : {
613 0 : continue;
614 : }
615 0 : aHasValidEventPath =
616 0 : HasValidEventPathForEndpoint(emberAfEndpointFromIndex(endpointIndex), eventPath, aSubjectDescriptor);
617 : }
618 : }
619 : else
620 : {
621 : // No need to check whether the endpoint is enabled, because
622 : // emberAfFindEndpointType returns null for disabled endpoints.
623 18 : aHasValidEventPath = HasValidEventPathForEndpoint(eventPath.mEndpointId, eventPath, aSubjectDescriptor);
624 : }
625 : }
626 :
627 18 : if (err == CHIP_ERROR_END_OF_TLV)
628 : {
629 18 : err = CHIP_NO_ERROR;
630 : }
631 :
632 18 : return err;
633 : }
634 :
635 963 : Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest(Messaging::ExchangeContext * apExchangeContext,
636 : const PayloadHeader & aPayloadHeader,
637 : System::PacketBufferHandle && aPayload,
638 : ReadHandler::InteractionType aInteractionType)
639 : {
640 963 : ChipLogDetail(InteractionModel, "Received %s request",
641 : aInteractionType == ReadHandler::InteractionType::Subscribe ? "Subscribe" : "Read");
642 :
643 : //
644 : // Let's first figure out if the client has sent us a subscribe request and requested we keep any existing
645 : // subscriptions from that source.
646 : //
647 963 : if (aInteractionType == ReadHandler::InteractionType::Subscribe)
648 : {
649 237 : System::PacketBufferTLVReader reader;
650 237 : bool keepExistingSubscriptions = true;
651 :
652 237 : if (apExchangeContext->GetSessionHandle()->GetFabricIndex() == kUndefinedFabricIndex)
653 : {
654 : // Subscriptions must be associated to a fabric.
655 0 : return Status::UnsupportedAccess;
656 : }
657 :
658 237 : reader.Init(aPayload.Retain());
659 :
660 237 : SubscribeRequestMessage::Parser subscribeRequestParser;
661 237 : VerifyOrReturnError(subscribeRequestParser.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
662 :
663 : #if CHIP_CONFIG_IM_PRETTY_PRINT
664 235 : subscribeRequestParser.PrettyPrint();
665 : #endif
666 :
667 235 : VerifyOrReturnError(subscribeRequestParser.GetKeepSubscriptions(&keepExistingSubscriptions) == CHIP_NO_ERROR,
668 : Status::InvalidAction);
669 235 : if (!keepExistingSubscriptions)
670 : {
671 : //
672 : // Walk through all existing subscriptions and shut down those whose subscriber matches
673 : // that which just came in.
674 : //
675 74 : mReadHandlers.ForEachActiveObject([apExchangeContext](ReadHandler * handler) {
676 7 : if (handler->IsFromSubscriber(*apExchangeContext))
677 : {
678 7 : ChipLogProgress(InteractionModel,
679 : "Deleting previous subscription from NodeId: " ChipLogFormatX64 ", FabricIndex: %u",
680 : ChipLogValueX64(apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId()),
681 : apExchangeContext->GetSessionHandle()->GetFabricIndex());
682 7 : handler->Close();
683 : }
684 :
685 7 : return Loop::Continue;
686 : });
687 : }
688 :
689 : {
690 235 : size_t requestedAttributePathCount = 0;
691 235 : size_t requestedEventPathCount = 0;
692 235 : AttributePathIBs::Parser attributePathListParser;
693 235 : bool hasValidAttributePath = false;
694 235 : bool hasValidEventPath = false;
695 :
696 235 : CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser);
697 235 : if (err == CHIP_NO_ERROR)
698 : {
699 228 : auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
700 228 : err = ParseAttributePaths(subjectDescriptor, attributePathListParser, hasValidAttributePath,
701 : requestedAttributePathCount);
702 228 : if (err != CHIP_NO_ERROR)
703 : {
704 0 : return Status::InvalidAction;
705 : }
706 : }
707 7 : else if (err != CHIP_ERROR_END_OF_TLV)
708 : {
709 0 : return Status::InvalidAction;
710 : }
711 :
712 235 : EventPathIBs::Parser eventPathListParser;
713 235 : err = subscribeRequestParser.GetEventRequests(&eventPathListParser);
714 235 : if (err == CHIP_NO_ERROR)
715 : {
716 18 : auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
717 18 : err = ParseEventPaths(subjectDescriptor, eventPathListParser, hasValidEventPath, requestedEventPathCount);
718 18 : if (err != CHIP_NO_ERROR)
719 : {
720 0 : return Status::InvalidAction;
721 : }
722 : }
723 217 : else if (err != CHIP_ERROR_END_OF_TLV)
724 : {
725 0 : return Status::InvalidAction;
726 : }
727 :
728 235 : if (requestedAttributePathCount == 0 && requestedEventPathCount == 0)
729 : {
730 1 : ChipLogError(InteractionModel,
731 : "Subscription from [%u:" ChipLogFormatX64 "] has no attribute or event paths. Rejecting request.",
732 : apExchangeContext->GetSessionHandle()->GetFabricIndex(),
733 : ChipLogValueX64(apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId()));
734 1 : return Status::InvalidAction;
735 : }
736 :
737 234 : if (!hasValidAttributePath && !hasValidEventPath)
738 : {
739 3 : ChipLogError(InteractionModel,
740 : "Subscription from [%u:" ChipLogFormatX64 "] has no access at all. Rejecting request.",
741 : apExchangeContext->GetSessionHandle()->GetFabricIndex(),
742 : ChipLogValueX64(apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId()));
743 3 : return Status::InvalidAction;
744 : }
745 :
746 : // The following cast is safe, since we can only hold a few tens of paths in one request.
747 231 : if (!EnsureResourceForSubscription(apExchangeContext->GetSessionHandle()->GetFabricIndex(), requestedAttributePathCount,
748 : requestedEventPathCount))
749 : {
750 2 : return Status::PathsExhausted;
751 : }
752 : }
753 237 : }
754 : else
755 : {
756 726 : System::PacketBufferTLVReader reader;
757 726 : reader.Init(aPayload.Retain());
758 :
759 726 : ReadRequestMessage::Parser readRequestParser;
760 726 : VerifyOrReturnError(readRequestParser.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
761 :
762 : #if CHIP_CONFIG_IM_PRETTY_PRINT
763 722 : readRequestParser.PrettyPrint();
764 : #endif
765 : {
766 722 : size_t requestedAttributePathCount = 0;
767 722 : size_t requestedEventPathCount = 0;
768 722 : AttributePathIBs::Parser attributePathListParser;
769 722 : CHIP_ERROR err = readRequestParser.GetAttributeRequests(&attributePathListParser);
770 722 : if (err == CHIP_NO_ERROR)
771 : {
772 : TLV::TLVReader pathReader;
773 601 : attributePathListParser.GetReader(&pathReader);
774 601 : ReturnErrorCodeIf(TLV::Utilities::Count(pathReader, requestedAttributePathCount, false) != CHIP_NO_ERROR,
775 : Status::InvalidAction);
776 : }
777 121 : else if (err != CHIP_ERROR_END_OF_TLV)
778 : {
779 0 : return Status::InvalidAction;
780 : }
781 722 : EventPathIBs::Parser eventpathListParser;
782 722 : err = readRequestParser.GetEventRequests(&eventpathListParser);
783 722 : if (err == CHIP_NO_ERROR)
784 : {
785 : TLV::TLVReader pathReader;
786 317 : eventpathListParser.GetReader(&pathReader);
787 317 : ReturnErrorCodeIf(TLV::Utilities::Count(pathReader, requestedEventPathCount, false) != CHIP_NO_ERROR,
788 : Status::InvalidAction);
789 : }
790 405 : else if (err != CHIP_ERROR_END_OF_TLV)
791 : {
792 0 : return Status::InvalidAction;
793 : }
794 :
795 : // The following cast is safe, since we can only hold a few tens of paths in one request.
796 722 : Status checkResult = EnsureResourceForRead(apExchangeContext->GetSessionHandle()->GetFabricIndex(),
797 : requestedAttributePathCount, requestedEventPathCount);
798 722 : if (checkResult != Status::Success)
799 : {
800 7 : return checkResult;
801 : }
802 : }
803 726 : }
804 :
805 : // We have already reserved enough resources for read requests, and have granted enough resources for current subscriptions, so
806 : // we should be able to allocate resources requested by this request.
807 944 : ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType, mReportScheduler);
808 944 : if (handler == nullptr)
809 : {
810 0 : ChipLogProgress(InteractionModel, "no resource for %s interaction",
811 : aInteractionType == ReadHandler::InteractionType::Subscribe ? "Subscribe" : "Read");
812 0 : return Status::ResourceExhausted;
813 : }
814 :
815 944 : handler->OnInitialRequest(std::move(aPayload));
816 :
817 944 : return Status::Success;
818 : }
819 :
820 423 : Protocols::InteractionModel::Status InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext,
821 : const PayloadHeader & aPayloadHeader,
822 : System::PacketBufferHandle && aPayload,
823 : bool aIsTimedWrite)
824 : {
825 423 : ChipLogDetail(InteractionModel, "Received Write request");
826 :
827 425 : for (auto & writeHandler : mWriteHandlers)
828 : {
829 425 : if (writeHandler.IsFree())
830 : {
831 423 : VerifyOrReturnError(writeHandler.Init() == CHIP_NO_ERROR, Status::Busy);
832 423 : return writeHandler.OnWriteRequest(apExchangeContext, std::move(aPayload), aIsTimedWrite);
833 : }
834 : }
835 0 : ChipLogProgress(InteractionModel, "no resource for write interaction");
836 0 : return Status::Busy;
837 : }
838 :
839 3 : CHIP_ERROR InteractionModelEngine::OnTimedRequest(Messaging::ExchangeContext * apExchangeContext,
840 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
841 : Protocols::InteractionModel::Status & aStatus)
842 : {
843 3 : TimedHandler * handler = mTimedHandlers.CreateObject();
844 3 : if (handler == nullptr)
845 : {
846 0 : ChipLogProgress(InteractionModel, "no resource for Timed interaction");
847 0 : aStatus = Status::Busy;
848 0 : return CHIP_ERROR_NO_MEMORY;
849 : }
850 :
851 : // The timed handler takes over handling of this exchange and will do its
852 : // own status reporting as needed.
853 3 : aStatus = Status::Success;
854 3 : apExchangeContext->SetDelegate(handler);
855 3 : return handler->OnMessageReceived(apExchangeContext, aPayloadHeader, std::move(aPayload));
856 : }
857 :
858 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
859 87 : Status InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext,
860 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
861 : {
862 87 : System::PacketBufferTLVReader reader;
863 87 : reader.Init(aPayload.Retain());
864 :
865 87 : ReportDataMessage::Parser report;
866 87 : VerifyOrReturnError(report.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
867 :
868 : #if CHIP_CONFIG_IM_PRETTY_PRINT
869 85 : report.PrettyPrint();
870 : #endif
871 :
872 85 : SubscriptionId subscriptionId = 0;
873 85 : VerifyOrReturnError(report.GetSubscriptionId(&subscriptionId) == CHIP_NO_ERROR, Status::InvalidAction);
874 84 : VerifyOrReturnError(report.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
875 :
876 84 : ReadClient * foundSubscription = nullptr;
877 312 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr; readClient = readClient->GetNextClient())
878 : {
879 228 : auto peer = apExchangeContext->GetSessionHandle()->GetPeer();
880 228 : if (readClient->GetFabricIndex() != peer.GetFabricIndex() || readClient->GetPeerNodeId() != peer.GetNodeId())
881 : {
882 146 : continue;
883 : }
884 :
885 : // Notify Subscriptions about incoming communication from node
886 195 : readClient->OnUnsolicitedMessageFromPublisher();
887 :
888 195 : if (!readClient->IsSubscriptionActive())
889 : {
890 0 : continue;
891 : }
892 :
893 195 : if (!readClient->IsMatchingSubscriptionId(subscriptionId))
894 : {
895 113 : continue;
896 : }
897 :
898 82 : if (!foundSubscription)
899 : {
900 82 : foundSubscription = readClient;
901 : }
902 : }
903 :
904 84 : if (foundSubscription)
905 : {
906 82 : foundSubscription->OnUnsolicitedReportData(apExchangeContext, std::move(aPayload));
907 82 : return Status::Success;
908 : }
909 :
910 2 : ChipLogDetail(InteractionModel, "Received report with invalid subscriptionId %" PRIu32, subscriptionId);
911 :
912 2 : return Status::InvalidSubscription;
913 87 : }
914 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
915 :
916 1513 : CHIP_ERROR InteractionModelEngine::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader,
917 : ExchangeDelegate *& newDelegate)
918 : {
919 : // TODO: Implement OnUnsolicitedMessageReceived, let messaging layer dispatch message to ReadHandler/ReadClient/TimedHandler
920 : // directly.
921 1513 : newDelegate = this;
922 1513 : return CHIP_NO_ERROR;
923 : }
924 :
925 1513 : CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext,
926 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
927 : {
928 : using namespace Protocols::InteractionModel;
929 :
930 1513 : Protocols::InteractionModel::Status status = Status::Failure;
931 :
932 : // Group Message can only be an InvokeCommandRequest or WriteRequest
933 1513 : if (apExchangeContext->IsGroupExchangeContext() &&
934 1513 : !aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::InvokeCommandRequest) &&
935 0 : !aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
936 : {
937 0 : ChipLogProgress(InteractionModel, "Msg type %d not supported for group message", aPayloadHeader.GetMessageType());
938 0 : return CHIP_NO_ERROR;
939 : }
940 :
941 1513 : if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::InvokeCommandRequest))
942 : {
943 30 : status = OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ false);
944 : }
945 1483 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReadRequest))
946 : {
947 726 : status = OnReadInitialRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), ReadHandler::InteractionType::Read);
948 : }
949 757 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
950 : {
951 423 : status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ false);
952 : }
953 334 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::SubscribeRequest))
954 : {
955 237 : status =
956 237 : OnReadInitialRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), ReadHandler::InteractionType::Subscribe);
957 : }
958 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
959 97 : else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReportData))
960 : {
961 87 : status = OnUnsolicitedReportData(apExchangeContext, aPayloadHeader, std::move(aPayload));
962 : }
963 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
964 10 : else if (aPayloadHeader.HasMessageType(MsgType::TimedRequest))
965 : {
966 3 : OnTimedRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), status);
967 : }
968 : else
969 : {
970 7 : ChipLogProgress(InteractionModel, "Msg type %d not supported", aPayloadHeader.GetMessageType());
971 7 : status = Status::InvalidAction;
972 : }
973 :
974 1513 : if (status != Status::Success && !apExchangeContext->IsGroupExchangeContext())
975 : {
976 31 : return StatusResponse::Send(status, apExchangeContext, false /*aExpectResponse*/);
977 : }
978 :
979 1482 : return CHIP_NO_ERROR;
980 : }
981 :
982 0 : void InteractionModelEngine::OnResponseTimeout(Messaging::ExchangeContext * ec)
983 : {
984 0 : ChipLogError(InteractionModel, "Time out! Failed to receive IM response from Exchange: " ChipLogFormatExchange,
985 : ChipLogValueExchange(ec));
986 0 : }
987 :
988 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
989 1 : void InteractionModelEngine::OnActiveModeNotification(ScopedNodeId aPeer)
990 : {
991 2 : for (ReadClient * pListItem = mpActiveReadClientList; pListItem != nullptr;)
992 : {
993 1 : auto pNextItem = pListItem->GetNextClient();
994 : // It is possible that pListItem is destroyed by the app in OnActiveModeNotification.
995 : // Get the next item before invoking `OnActiveModeNotification`.
996 1 : if (ScopedNodeId(pListItem->GetPeerNodeId(), pListItem->GetFabricIndex()) == aPeer)
997 : {
998 1 : pListItem->OnActiveModeNotification();
999 : }
1000 1 : pListItem = pNextItem;
1001 : }
1002 1 : }
1003 :
1004 3 : void InteractionModelEngine::OnPeerTypeChange(ScopedNodeId aPeer, ReadClient::PeerType aType)
1005 : {
1006 : // TODO: Follow up to use a iterator function to avoid copy/paste here.
1007 6 : for (ReadClient * pListItem = mpActiveReadClientList; pListItem != nullptr;)
1008 : {
1009 : // It is possible that pListItem is destroyed by the app in OnPeerTypeChange.
1010 : // Get the next item before invoking `OnPeerTypeChange`.
1011 3 : auto pNextItem = pListItem->GetNextClient();
1012 3 : if (ScopedNodeId(pListItem->GetPeerNodeId(), pListItem->GetFabricIndex()) == aPeer)
1013 : {
1014 3 : pListItem->OnPeerTypeChange(aType);
1015 : }
1016 3 : pListItem = pNextItem;
1017 : }
1018 3 : }
1019 :
1020 247 : void InteractionModelEngine::AddReadClient(ReadClient * apReadClient)
1021 : {
1022 247 : apReadClient->SetNextClient(mpActiveReadClientList);
1023 247 : mpActiveReadClientList = apReadClient;
1024 247 : }
1025 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
1026 :
1027 6 : bool InteractionModelEngine::TrimFabricForSubscriptions(FabricIndex aFabricIndex, bool aForceEvict)
1028 : {
1029 6 : const size_t pathPoolCapacity = GetPathPoolCapacityForSubscriptions();
1030 6 : const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions();
1031 :
1032 6 : uint8_t fabricCount = mpFabricTable->FabricCount();
1033 6 : size_t attributePathsSubscribedByCurrentFabric = 0;
1034 6 : size_t eventPathsSubscribedByCurrentFabric = 0;
1035 6 : size_t subscriptionsEstablishedByCurrentFabric = 0;
1036 :
1037 6 : if (fabricCount == 0)
1038 : {
1039 0 : return false;
1040 : }
1041 :
1042 : // Note: This is OK only when we have assumed the fabricCount is not zero. Should be revised when adding support to
1043 : // subscriptions on PASE sessions.
1044 6 : size_t perFabricPathCapacity = pathPoolCapacity / static_cast<size_t>(fabricCount);
1045 6 : size_t perFabricSubscriptionCapacity = readHandlerPoolCapacity / static_cast<size_t>(fabricCount);
1046 :
1047 6 : ReadHandler * candidate = nullptr;
1048 6 : size_t candidateAttributePathsUsed = 0;
1049 6 : size_t candidateEventPathsUsed = 0;
1050 :
1051 : // It is safe to use & here since this function will be called on current stack.
1052 6 : mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
1053 47 : if (handler->GetAccessingFabricIndex() != aFabricIndex || !handler->IsType(ReadHandler::InteractionType::Subscribe))
1054 : {
1055 13 : return Loop::Continue;
1056 : }
1057 :
1058 34 : size_t attributePathsUsed = handler->GetAttributePathCount();
1059 34 : size_t eventPathsUsed = handler->GetEventPathCount();
1060 :
1061 34 : attributePathsSubscribedByCurrentFabric += attributePathsUsed;
1062 34 : eventPathsSubscribedByCurrentFabric += eventPathsUsed;
1063 34 : subscriptionsEstablishedByCurrentFabric++;
1064 :
1065 34 : if (candidate == nullptr)
1066 : {
1067 6 : candidate = handler;
1068 : }
1069 : // This handler uses more resources than the one we picked before.
1070 28 : else if ((attributePathsUsed > perFabricPathCapacity || eventPathsUsed > perFabricPathCapacity) &&
1071 0 : (candidateAttributePathsUsed <= perFabricPathCapacity && candidateEventPathsUsed <= perFabricPathCapacity))
1072 : {
1073 0 : candidate = handler;
1074 0 : candidateAttributePathsUsed = attributePathsUsed;
1075 0 : candidateEventPathsUsed = eventPathsUsed;
1076 : }
1077 : // This handler is older than the one we picked before.
1078 28 : else if (handler->GetTransactionStartGeneration() < candidate->GetTransactionStartGeneration() &&
1079 : // And the level of resource usage is the same (both exceed or neither exceed)
1080 0 : ((attributePathsUsed > perFabricPathCapacity || eventPathsUsed > perFabricPathCapacity) ==
1081 0 : (candidateAttributePathsUsed > perFabricPathCapacity || candidateEventPathsUsed > perFabricPathCapacity)))
1082 : {
1083 0 : candidate = handler;
1084 : }
1085 34 : return Loop::Continue;
1086 : });
1087 :
1088 6 : if (candidate != nullptr &&
1089 6 : (aForceEvict || attributePathsSubscribedByCurrentFabric > perFabricPathCapacity ||
1090 0 : eventPathsSubscribedByCurrentFabric > perFabricPathCapacity ||
1091 0 : subscriptionsEstablishedByCurrentFabric > perFabricSubscriptionCapacity))
1092 : {
1093 : SubscriptionId subId;
1094 6 : candidate->GetSubscriptionId(subId);
1095 6 : ChipLogProgress(DataManagement, "Evicting Subscription ID %u:0x%" PRIx32, candidate->GetSubjectDescriptor().fabricIndex,
1096 : subId);
1097 6 : candidate->Close();
1098 6 : return true;
1099 : }
1100 0 : return false;
1101 : }
1102 :
1103 231 : bool InteractionModelEngine::EnsureResourceForSubscription(FabricIndex aFabricIndex, size_t aRequestedAttributePathCount,
1104 : size_t aRequestedEventPathCount)
1105 : {
1106 : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
1107 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
1108 231 : const bool allowUnlimited = !mForceHandlerQuota;
1109 : #else // CONFIG_BUILD_FOR_HOST_UNIT_TEST
1110 : // If the resources are allocated on the heap, we should be able to handle as many Read / Subscribe requests as possible.
1111 : const bool allowUnlimited = true;
1112 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
1113 : #else // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
1114 : const bool allowUnlimited = false;
1115 : #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
1116 :
1117 : // Don't couple with read requests, always reserve enough resource for read requests.
1118 :
1119 231 : const size_t pathPoolCapacity = GetPathPoolCapacityForSubscriptions();
1120 231 : const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions();
1121 :
1122 : // If we return early here, the compiler will complain about the unreachable code, so we add a always-true check.
1123 231 : const size_t attributePathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity;
1124 231 : const size_t eventPathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity;
1125 231 : const size_t readHandlerCap = allowUnlimited ? SIZE_MAX : readHandlerPoolCapacity;
1126 :
1127 231 : size_t usedAttributePaths = 0;
1128 231 : size_t usedEventPaths = 0;
1129 231 : size_t usedReadHandlers = 0;
1130 :
1131 237 : auto countResourceUsage = [&]() {
1132 237 : usedAttributePaths = 0;
1133 237 : usedEventPaths = 0;
1134 237 : usedReadHandlers = 0;
1135 237 : mReadHandlers.ForEachActiveObject([&](auto * handler) {
1136 4308 : if (!handler->IsType(ReadHandler::InteractionType::Subscribe))
1137 : {
1138 34 : return Loop::Continue;
1139 : }
1140 4274 : usedAttributePaths += handler->GetAttributePathCount();
1141 4274 : usedEventPaths += handler->GetEventPathCount();
1142 4274 : usedReadHandlers++;
1143 4274 : return Loop::Continue;
1144 : });
1145 468 : };
1146 :
1147 231 : countResourceUsage();
1148 :
1149 231 : if (usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
1150 224 : usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap)
1151 : {
1152 : // We have enough resources, then we serve the requests in a best-effort manner.
1153 224 : return true;
1154 : }
1155 :
1156 7 : if ((aRequestedAttributePathCount > kMinSupportedPathsPerSubscription &&
1157 7 : usedAttributePaths + aRequestedAttributePathCount > attributePathCap) ||
1158 0 : (aRequestedEventPathCount > kMinSupportedPathsPerSubscription && usedEventPaths + aRequestedEventPathCount > eventPathCap))
1159 : {
1160 : // We cannot offer enough resources, and the subscription is requesting more than the spec limit.
1161 2 : return false;
1162 : }
1163 :
1164 6 : const auto evictAndUpdateResourceUsage = [&](FabricIndex fabricIndex, bool forceEvict) {
1165 6 : bool ret = TrimFabricForSubscriptions(fabricIndex, forceEvict);
1166 6 : countResourceUsage();
1167 6 : return ret;
1168 5 : };
1169 :
1170 : //
1171 : // At this point, we have an inbound request that respects minimas but we still don't have enough resources to handle it. Which
1172 : // means that we definitely have handlers on existing fabrics that are over limits and need to evict at least one of them to
1173 : // make space.
1174 : //
1175 : // There might be cases that one fabric has lots of subscriptions with one interested path, while the other fabrics are not
1176 : // using excess resources. So we need to do this multiple times until we have enough room or no fabrics are using excess
1177 : // resources.
1178 : //
1179 5 : bool didEvictHandler = true;
1180 16 : while (didEvictHandler)
1181 : {
1182 11 : didEvictHandler = false;
1183 18 : for (const auto & fabric : *mpFabricTable)
1184 : {
1185 : // The resources are enough to serve this request, do not evict anything.
1186 17 : if (usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
1187 10 : usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap)
1188 : {
1189 10 : break;
1190 : }
1191 7 : didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(fabric.GetFabricIndex(), false);
1192 : }
1193 : }
1194 :
1195 : // The above loop cannot guarantee the resources for the new subscriptions when the resource usage from all fabrics are exactly
1196 : // within the quota (which means we have exactly used all resources). Evict (from the large subscriptions first then from
1197 : // oldest) subscriptions from the current fabric until we have enough resource for the new subscription.
1198 5 : didEvictHandler = true;
1199 5 : while ((usedAttributePaths + aRequestedAttributePathCount > attributePathCap ||
1200 5 : usedEventPaths + aRequestedEventPathCount > eventPathCap || usedReadHandlers >= readHandlerCap) &&
1201 : // Avoid infinity loop
1202 : didEvictHandler)
1203 : {
1204 0 : didEvictHandler = evictAndUpdateResourceUsage(aFabricIndex, true);
1205 : }
1206 :
1207 : // If didEvictHandler is false, means the loop above evicted all subscriptions from the current fabric but we still don't have
1208 : // enough resources for the new subscription, this should never happen.
1209 : // This is safe as long as we have rejected subscriptions without a fabric associated (with a PASE session) before.
1210 : // Note: Spec#5141: should reject subscription requests on PASE sessions.
1211 5 : VerifyOrDieWithMsg(didEvictHandler, DataManagement, "Failed to get required resources by evicting existing subscriptions.");
1212 :
1213 : // We have ensured enough resources by the logic above.
1214 5 : return true;
1215 : }
1216 :
1217 31 : bool InteractionModelEngine::TrimFabricForRead(FabricIndex aFabricIndex)
1218 : {
1219 31 : const size_t guaranteedReadRequestsPerFabric = GetGuaranteedReadRequestsPerFabric();
1220 31 : const size_t minSupportedPathsPerFabricForRead = guaranteedReadRequestsPerFabric * kMinSupportedPathsPerReadRequest;
1221 :
1222 31 : size_t attributePathsUsedByCurrentFabric = 0;
1223 31 : size_t eventPathsUsedByCurrentFabric = 0;
1224 31 : size_t readTransactionsOnCurrentFabric = 0;
1225 :
1226 31 : ReadHandler * candidate = nullptr;
1227 31 : size_t candidateAttributePathsUsed = 0;
1228 31 : size_t candidateEventPathsUsed = 0;
1229 :
1230 : // It is safe to use & here since this function will be called on current stack.
1231 31 : mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
1232 81 : if (handler->GetAccessingFabricIndex() != aFabricIndex || !handler->IsType(ReadHandler::InteractionType::Read))
1233 : {
1234 49 : return Loop::Continue;
1235 : }
1236 :
1237 32 : size_t attributePathsUsed = handler->GetAttributePathCount();
1238 32 : size_t eventPathsUsed = handler->GetEventPathCount();
1239 :
1240 32 : attributePathsUsedByCurrentFabric += attributePathsUsed;
1241 32 : eventPathsUsedByCurrentFabric += eventPathsUsed;
1242 32 : readTransactionsOnCurrentFabric++;
1243 :
1244 32 : if (candidate == nullptr)
1245 : {
1246 18 : candidate = handler;
1247 : }
1248 : // Oversized read handlers will be evicted first.
1249 14 : else if ((attributePathsUsed > kMinSupportedPathsPerReadRequest || eventPathsUsed > kMinSupportedPathsPerReadRequest) &&
1250 3 : (candidateAttributePathsUsed <= kMinSupportedPathsPerReadRequest &&
1251 1 : candidateEventPathsUsed <= kMinSupportedPathsPerReadRequest))
1252 : {
1253 1 : candidate = handler;
1254 : }
1255 : // Read Handlers are "first come first served", so we give eariler read transactions a higher priority.
1256 16 : else if (handler->GetTransactionStartGeneration() > candidate->GetTransactionStartGeneration() &&
1257 : // And the level of resource usage is the same (both exceed or neither exceed)
1258 3 : ((attributePathsUsed > kMinSupportedPathsPerReadRequest || eventPathsUsed > kMinSupportedPathsPerReadRequest) ==
1259 4 : (candidateAttributePathsUsed > kMinSupportedPathsPerReadRequest ||
1260 1 : candidateEventPathsUsed > kMinSupportedPathsPerReadRequest)))
1261 : {
1262 1 : candidate = handler;
1263 : }
1264 :
1265 32 : if (candidate == handler)
1266 : {
1267 20 : candidateAttributePathsUsed = attributePathsUsed;
1268 20 : candidateEventPathsUsed = eventPathsUsed;
1269 : }
1270 32 : return Loop::Continue;
1271 : });
1272 :
1273 49 : if (candidate != nullptr &&
1274 18 : ((attributePathsUsedByCurrentFabric > minSupportedPathsPerFabricForRead ||
1275 13 : eventPathsUsedByCurrentFabric > minSupportedPathsPerFabricForRead ||
1276 13 : readTransactionsOnCurrentFabric > guaranteedReadRequestsPerFabric) ||
1277 : // Always evict the transactions on PASE sessions if the fabric table is full.
1278 9 : (aFabricIndex == kUndefinedFabricIndex && mpFabricTable->FabricCount() == GetConfigMaxFabrics())))
1279 : {
1280 13 : candidate->Close();
1281 13 : return true;
1282 : }
1283 18 : return false;
1284 : }
1285 :
1286 722 : Protocols::InteractionModel::Status InteractionModelEngine::EnsureResourceForRead(FabricIndex aFabricIndex,
1287 : size_t aRequestedAttributePathCount,
1288 : size_t aRequestedEventPathCount)
1289 : {
1290 : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
1291 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
1292 722 : const bool allowUnlimited = !mForceHandlerQuota;
1293 : #else // CONFIG_BUILD_FOR_HOST_UNIT_TEST
1294 : // If the resources are allocated on the heap, we should be able to handle as many Read / Subscribe requests as possible.
1295 : const bool allowUnlimited = true;
1296 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
1297 : #else // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
1298 : const bool allowUnlimited = false;
1299 : #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK
1300 :
1301 : // If we return early here, the compiler will complain about the unreachable code, so we add a always-true check.
1302 722 : const size_t attributePathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads();
1303 722 : const size_t eventPathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads();
1304 722 : const size_t readHandlerCap = allowUnlimited ? SIZE_MAX : GetReadHandlerPoolCapacityForReads();
1305 :
1306 722 : const size_t guaranteedReadRequestsPerFabric = GetGuaranteedReadRequestsPerFabric();
1307 722 : const size_t guaranteedPathsPerFabric = kMinSupportedPathsPerReadRequest * guaranteedReadRequestsPerFabric;
1308 :
1309 722 : size_t usedAttributePaths = 0;
1310 722 : size_t usedEventPaths = 0;
1311 722 : size_t usedReadHandlers = 0;
1312 :
1313 753 : auto countResourceUsage = [&]() {
1314 753 : usedAttributePaths = 0;
1315 753 : usedEventPaths = 0;
1316 753 : usedReadHandlers = 0;
1317 753 : mReadHandlers.ForEachActiveObject([&](auto * handler) {
1318 178 : if (!handler->IsType(ReadHandler::InteractionType::Read))
1319 : {
1320 7 : return Loop::Continue;
1321 : }
1322 171 : usedAttributePaths += handler->GetAttributePathCount();
1323 171 : usedEventPaths += handler->GetEventPathCount();
1324 171 : usedReadHandlers++;
1325 171 : return Loop::Continue;
1326 : });
1327 1475 : };
1328 :
1329 766 : auto haveEnoughResourcesForTheRequest = [&]() {
1330 1514 : return usedAttributePaths + aRequestedAttributePathCount <= attributePathCap &&
1331 766 : usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap;
1332 722 : };
1333 :
1334 722 : countResourceUsage();
1335 :
1336 722 : if (haveEnoughResourcesForTheRequest())
1337 : {
1338 : // We have enough resources, then we serve the requests in a best-effort manner.
1339 704 : return Status::Success;
1340 : }
1341 :
1342 18 : if ((aRequestedAttributePathCount > kMinSupportedPathsPerReadRequest &&
1343 4 : usedAttributePaths + aRequestedAttributePathCount > attributePathCap) ||
1344 15 : (aRequestedEventPathCount > kMinSupportedPathsPerReadRequest && usedEventPaths + aRequestedEventPathCount > eventPathCap))
1345 : {
1346 : // We cannot offer enough resources, and the read transaction is requesting more than the spec limit.
1347 3 : return Status::PathsExhausted;
1348 : }
1349 :
1350 : // If we have commissioned CHIP_CONFIG_MAX_FABRICS already, and this transaction doesn't have an associated fabric index, reject
1351 : // the request if we don't have sufficient resources for this request.
1352 15 : if (mpFabricTable->FabricCount() == GetConfigMaxFabrics() && aFabricIndex == kUndefinedFabricIndex)
1353 : {
1354 1 : return Status::Busy;
1355 : }
1356 :
1357 14 : size_t usedAttributePathsInFabric = 0;
1358 14 : size_t usedEventPathsInFabric = 0;
1359 14 : size_t usedReadHandlersInFabric = 0;
1360 14 : mReadHandlers.ForEachActiveObject([&](auto * handler) {
1361 35 : if (!handler->IsType(ReadHandler::InteractionType::Read) || handler->GetAccessingFabricIndex() != aFabricIndex)
1362 : {
1363 33 : return Loop::Continue;
1364 : }
1365 2 : usedAttributePathsInFabric += handler->GetAttributePathCount();
1366 2 : usedEventPathsInFabric += handler->GetEventPathCount();
1367 2 : usedReadHandlersInFabric++;
1368 2 : return Loop::Continue;
1369 : });
1370 :
1371 : // Busy, since there are already some read requests ongoing on this fabric, please retry later.
1372 14 : if (usedAttributePathsInFabric + aRequestedAttributePathCount > guaranteedPathsPerFabric ||
1373 11 : usedEventPathsInFabric + aRequestedEventPathCount > guaranteedPathsPerFabric ||
1374 11 : usedReadHandlersInFabric >= guaranteedReadRequestsPerFabric)
1375 : {
1376 3 : return Status::Busy;
1377 : }
1378 :
1379 31 : const auto evictAndUpdateResourceUsage = [&](FabricIndex fabricIndex) {
1380 31 : bool ret = TrimFabricForRead(fabricIndex);
1381 31 : countResourceUsage();
1382 31 : return ret;
1383 11 : };
1384 :
1385 : //
1386 : // At this point, we have an inbound request that respects minimas but we still don't have enough resources to handle it. Which
1387 : // means that we definitely have handlers on existing fabrics that are over limits and need to evict at least one of them to
1388 : // make space.
1389 : //
1390 11 : bool didEvictHandler = true;
1391 21 : while (didEvictHandler)
1392 : {
1393 21 : didEvictHandler = false;
1394 21 : didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(kUndefinedFabricIndex);
1395 21 : if (haveEnoughResourcesForTheRequest())
1396 : {
1397 11 : break;
1398 : }
1399 : // If the fabric table is full, we won't evict read requests from normal fabrics before we have evicted all read requests
1400 : // from PASE sessions.
1401 10 : if (mpFabricTable->FabricCount() == GetConfigMaxFabrics() && didEvictHandler)
1402 : {
1403 1 : continue;
1404 : }
1405 13 : for (const auto & fabric : *mpFabricTable)
1406 : {
1407 12 : didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(fabric.GetFabricIndex());
1408 : // If we now have enough resources to serve this request, stop evicting things.
1409 12 : if (haveEnoughResourcesForTheRequest())
1410 : {
1411 8 : break;
1412 : }
1413 : }
1414 : }
1415 :
1416 : // Now all fabrics are not oversized (since we have trimmed the oversized fabrics in the loop above), and the read handler is
1417 : // also not oversized, we should be able to handle this read transaction.
1418 11 : VerifyOrDie(haveEnoughResourcesForTheRequest());
1419 :
1420 11 : return Status::Success;
1421 : }
1422 :
1423 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
1424 82 : void InteractionModelEngine::RemoveReadClient(ReadClient * apReadClient)
1425 : {
1426 82 : ReadClient * pPrevListItem = nullptr;
1427 82 : ReadClient * pCurListItem = mpActiveReadClientList;
1428 :
1429 87 : while (pCurListItem != apReadClient)
1430 : {
1431 : //
1432 : // Item must exist in this tracker list. If not, there's a bug somewhere.
1433 : //
1434 5 : VerifyOrDie(pCurListItem != nullptr);
1435 :
1436 5 : pPrevListItem = pCurListItem;
1437 5 : pCurListItem = pCurListItem->GetNextClient();
1438 : }
1439 :
1440 82 : if (pPrevListItem)
1441 : {
1442 3 : pPrevListItem->SetNextClient(apReadClient->GetNextClient());
1443 : }
1444 : else
1445 : {
1446 79 : mpActiveReadClientList = apReadClient->GetNextClient();
1447 : }
1448 :
1449 82 : apReadClient->SetNextClient(nullptr);
1450 82 : }
1451 :
1452 94 : size_t InteractionModelEngine::GetNumActiveReadClients()
1453 : {
1454 94 : ReadClient * pListItem = mpActiveReadClientList;
1455 94 : size_t count = 0;
1456 :
1457 94 : while (pListItem)
1458 : {
1459 0 : pListItem = pListItem->GetNextClient();
1460 0 : count++;
1461 : }
1462 :
1463 94 : return count;
1464 : }
1465 :
1466 12 : bool InteractionModelEngine::InActiveReadClientList(ReadClient * apReadClient)
1467 : {
1468 12 : ReadClient * pListItem = mpActiveReadClientList;
1469 :
1470 12 : while (pListItem)
1471 : {
1472 12 : if (pListItem == apReadClient)
1473 : {
1474 12 : return true;
1475 : }
1476 :
1477 0 : pListItem = pListItem->GetNextClient();
1478 : }
1479 :
1480 0 : return false;
1481 : }
1482 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
1483 :
1484 2488 : bool InteractionModelEngine::HasConflictWriteRequests(const WriteHandler * apWriteHandler, const ConcreteAttributePath & aPath)
1485 : {
1486 12424 : for (auto & writeHandler : mWriteHandlers)
1487 : {
1488 9940 : if (writeHandler.IsFree() || &writeHandler == apWriteHandler)
1489 : {
1490 9928 : continue;
1491 : }
1492 12 : if (writeHandler.IsCurrentlyProcessingWritePath(aPath))
1493 : {
1494 4 : return true;
1495 : }
1496 : }
1497 2484 : return false;
1498 : }
1499 :
1500 983 : void InteractionModelEngine::ReleaseAttributePathList(ObjectList<AttributePathParams> *& aAttributePathList)
1501 : {
1502 983 : ReleasePool(aAttributePathList, mAttributePathPool);
1503 983 : }
1504 :
1505 1418 : CHIP_ERROR InteractionModelEngine::PushFrontAttributePathList(ObjectList<AttributePathParams> *& aAttributePathList,
1506 : AttributePathParams & aAttributePath)
1507 : {
1508 1418 : CHIP_ERROR err = PushFront(aAttributePathList, aAttributePath, mAttributePathPool);
1509 1418 : if (err == CHIP_ERROR_NO_MEMORY)
1510 : {
1511 0 : ChipLogError(InteractionModel, "AttributePath pool full");
1512 0 : return CHIP_IM_GLOBAL_STATUS(PathsExhausted);
1513 : }
1514 1418 : return err;
1515 : }
1516 :
1517 831 : void InteractionModelEngine::RemoveDuplicateConcreteAttributePath(ObjectList<AttributePathParams> *& aAttributePaths)
1518 : {
1519 831 : ObjectList<AttributePathParams> * prev = nullptr;
1520 831 : auto * path1 = aAttributePaths;
1521 :
1522 2246 : while (path1 != nullptr)
1523 : {
1524 1415 : bool duplicate = false;
1525 : // skip all wildcard paths and invalid concrete attribute
1526 2591 : if (path1->mValue.IsWildcardPath() ||
1527 1176 : !emberAfContainsAttribute(path1->mValue.mEndpointId, path1->mValue.mClusterId, path1->mValue.mAttributeId))
1528 : {
1529 915 : prev = path1;
1530 915 : path1 = path1->mpNext;
1531 915 : continue;
1532 : }
1533 :
1534 : // Check whether a wildcard path expands to something that includes this concrete path.
1535 1385 : for (auto * path2 = aAttributePaths; path2 != nullptr; path2 = path2->mpNext)
1536 : {
1537 894 : if (path2 == path1)
1538 : {
1539 496 : continue;
1540 : }
1541 :
1542 398 : if (path2->mValue.IsWildcardPath() && path2->mValue.IsAttributePathSupersetOf(path1->mValue))
1543 : {
1544 9 : duplicate = true;
1545 9 : break;
1546 : }
1547 : }
1548 :
1549 : // if path1 duplicates something from wildcard expansion, discard path1
1550 500 : if (!duplicate)
1551 : {
1552 491 : prev = path1;
1553 491 : path1 = path1->mpNext;
1554 491 : continue;
1555 : }
1556 :
1557 9 : if (path1 == aAttributePaths)
1558 : {
1559 5 : aAttributePaths = path1->mpNext;
1560 5 : mAttributePathPool.ReleaseObject(path1);
1561 5 : path1 = aAttributePaths;
1562 : }
1563 : else
1564 : {
1565 4 : prev->mpNext = path1->mpNext;
1566 4 : mAttributePathPool.ReleaseObject(path1);
1567 4 : path1 = prev->mpNext;
1568 : }
1569 : }
1570 831 : }
1571 :
1572 975 : void InteractionModelEngine::ReleaseEventPathList(ObjectList<EventPathParams> *& aEventPathList)
1573 : {
1574 975 : ReleasePool(aEventPathList, mEventPathPool);
1575 975 : }
1576 :
1577 497 : CHIP_ERROR InteractionModelEngine::PushFrontEventPathParamsList(ObjectList<EventPathParams> *& aEventPathList,
1578 : EventPathParams & aEventPath)
1579 : {
1580 497 : CHIP_ERROR err = PushFront(aEventPathList, aEventPath, mEventPathPool);
1581 497 : if (err == CHIP_ERROR_NO_MEMORY)
1582 : {
1583 0 : ChipLogError(InteractionModel, "EventPath pool full");
1584 0 : return CHIP_IM_GLOBAL_STATUS(PathsExhausted);
1585 : }
1586 497 : return err;
1587 : }
1588 :
1589 1916 : void InteractionModelEngine::ReleaseDataVersionFilterList(ObjectList<DataVersionFilter> *& aDataVersionFilterList)
1590 : {
1591 1916 : ReleasePool(aDataVersionFilterList, mDataVersionFilterPool);
1592 1916 : }
1593 :
1594 78 : CHIP_ERROR InteractionModelEngine::PushFrontDataVersionFilterList(ObjectList<DataVersionFilter> *& aDataVersionFilterList,
1595 : DataVersionFilter & aDataVersionFilter)
1596 : {
1597 78 : CHIP_ERROR err = PushFront(aDataVersionFilterList, aDataVersionFilter, mDataVersionFilterPool);
1598 78 : if (err == CHIP_ERROR_NO_MEMORY)
1599 : {
1600 0 : ChipLogError(InteractionModel, "DataVersionFilter pool full, ignore this filter");
1601 0 : err = CHIP_NO_ERROR;
1602 : }
1603 78 : return err;
1604 : }
1605 :
1606 : template <typename T, size_t N>
1607 3874 : void InteractionModelEngine::ReleasePool(ObjectList<T> *& aObjectList, ObjectPool<ObjectList<T>, N> & aObjectPool)
1608 : {
1609 3874 : ObjectList<T> * current = aObjectList;
1610 5858 : while (current != nullptr)
1611 : {
1612 1984 : ObjectList<T> * nextObject = current->mpNext;
1613 1984 : aObjectPool.ReleaseObject(current);
1614 1984 : current = nextObject;
1615 : }
1616 :
1617 3874 : aObjectList = nullptr;
1618 3874 : }
1619 :
1620 : template <typename T, size_t N>
1621 1993 : CHIP_ERROR InteractionModelEngine::PushFront(ObjectList<T> *& aObjectList, T & aData, ObjectPool<ObjectList<T>, N> & aObjectPool)
1622 : {
1623 1993 : ObjectList<T> * object = aObjectPool.CreateObject();
1624 1993 : if (object == nullptr)
1625 : {
1626 0 : return CHIP_ERROR_NO_MEMORY;
1627 : }
1628 1993 : object->mValue = aData;
1629 1993 : object->mpNext = aObjectList;
1630 1993 : aObjectList = object;
1631 1993 : return CHIP_NO_ERROR;
1632 : }
1633 :
1634 21 : void InteractionModelEngine::DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath,
1635 : TLV::TLVReader & apPayload)
1636 : {
1637 21 : CommandHandlerInterface * handler = FindCommandHandler(aCommandPath.mEndpointId, aCommandPath.mClusterId);
1638 :
1639 21 : if (handler)
1640 : {
1641 3 : CommandHandlerInterface::HandlerContext context(apCommandObj, aCommandPath, apPayload);
1642 3 : handler->InvokeCommand(context);
1643 :
1644 : //
1645 : // If the command was handled, don't proceed any further and return successfully.
1646 : //
1647 3 : if (context.mCommandHandled)
1648 : {
1649 3 : return;
1650 : }
1651 : }
1652 :
1653 18 : DispatchSingleClusterCommand(aCommandPath, apPayload, &apCommandObj);
1654 : }
1655 :
1656 28 : Protocols::InteractionModel::Status InteractionModelEngine::CommandExists(const ConcreteCommandPath & aCommandPath)
1657 : {
1658 28 : return ServerClusterCommandExists(aCommandPath);
1659 : }
1660 :
1661 6 : CHIP_ERROR InteractionModelEngine::RegisterCommandHandler(CommandHandlerInterface * handler)
1662 : {
1663 6 : VerifyOrReturnError(handler != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
1664 :
1665 6 : for (auto * cur = mCommandHandlerList; cur; cur = cur->GetNext())
1666 : {
1667 0 : if (cur->Matches(*handler))
1668 : {
1669 0 : ChipLogError(InteractionModel, "Duplicate command handler registration failed");
1670 0 : return CHIP_ERROR_INCORRECT_STATE;
1671 : }
1672 : }
1673 :
1674 6 : handler->SetNext(mCommandHandlerList);
1675 6 : mCommandHandlerList = handler;
1676 :
1677 6 : return CHIP_NO_ERROR;
1678 : }
1679 :
1680 23 : void InteractionModelEngine::UnregisterCommandHandlers(EndpointId endpointId)
1681 : {
1682 23 : CommandHandlerInterface * prev = nullptr;
1683 :
1684 29 : for (auto * cur = mCommandHandlerList; cur; cur = cur->GetNext())
1685 : {
1686 6 : if (cur->MatchesEndpoint(endpointId))
1687 : {
1688 0 : if (prev == nullptr)
1689 : {
1690 0 : mCommandHandlerList = cur->GetNext();
1691 : }
1692 : else
1693 : {
1694 0 : prev->SetNext(cur->GetNext());
1695 : }
1696 :
1697 0 : cur->SetNext(nullptr);
1698 : }
1699 : else
1700 : {
1701 6 : prev = cur;
1702 : }
1703 : }
1704 23 : }
1705 :
1706 6 : CHIP_ERROR InteractionModelEngine::UnregisterCommandHandler(CommandHandlerInterface * handler)
1707 : {
1708 6 : VerifyOrReturnError(handler != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
1709 6 : CommandHandlerInterface * prev = nullptr;
1710 :
1711 6 : for (auto * cur = mCommandHandlerList; cur; cur = cur->GetNext())
1712 : {
1713 6 : if (cur->Matches(*handler))
1714 : {
1715 6 : if (prev == nullptr)
1716 : {
1717 6 : mCommandHandlerList = cur->GetNext();
1718 : }
1719 : else
1720 : {
1721 0 : prev->SetNext(cur->GetNext());
1722 : }
1723 :
1724 6 : cur->SetNext(nullptr);
1725 :
1726 6 : return CHIP_NO_ERROR;
1727 : }
1728 :
1729 0 : prev = cur;
1730 : }
1731 :
1732 0 : return CHIP_ERROR_KEY_NOT_FOUND;
1733 : }
1734 :
1735 443 : CommandHandlerInterface * InteractionModelEngine::FindCommandHandler(EndpointId endpointId, ClusterId clusterId)
1736 : {
1737 443 : for (auto * cur = mCommandHandlerList; cur; cur = cur->GetNext())
1738 : {
1739 15 : if (cur->Matches(endpointId, clusterId))
1740 : {
1741 15 : return cur;
1742 : }
1743 : }
1744 :
1745 428 : return nullptr;
1746 : }
1747 :
1748 1 : void InteractionModelEngine::OnTimedInteractionFailed(TimedHandler * apTimedHandler)
1749 : {
1750 1 : mTimedHandlers.ReleaseObject(apTimedHandler);
1751 1 : }
1752 :
1753 1 : void InteractionModelEngine::OnTimedInvoke(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
1754 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
1755 : {
1756 : using namespace Protocols::InteractionModel;
1757 :
1758 : // Reset the ourselves as the exchange delegate for now, to match what we'd
1759 : // do with an initial unsolicited invoke.
1760 1 : apExchangeContext->SetDelegate(this);
1761 1 : mTimedHandlers.ReleaseObject(apTimedHandler);
1762 :
1763 1 : VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::InvokeCommandRequest));
1764 1 : VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
1765 :
1766 1 : Status status = OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ true);
1767 1 : if (status != Status::Success)
1768 : {
1769 0 : StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
1770 : }
1771 1 : }
1772 :
1773 0 : void InteractionModelEngine::OnTimedWrite(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
1774 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
1775 : {
1776 : using namespace Protocols::InteractionModel;
1777 :
1778 : // Reset the ourselves as the exchange delegate for now, to match what we'd
1779 : // do with an initial unsolicited write.
1780 0 : apExchangeContext->SetDelegate(this);
1781 0 : mTimedHandlers.ReleaseObject(apTimedHandler);
1782 :
1783 0 : VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::WriteRequest));
1784 0 : VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
1785 :
1786 0 : Status status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ true);
1787 0 : if (status != Status::Success)
1788 : {
1789 0 : StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
1790 : }
1791 0 : }
1792 :
1793 0 : bool InteractionModelEngine::HasActiveRead()
1794 : {
1795 0 : return ((mReadHandlers.ForEachActiveObject([](ReadHandler * handler) {
1796 0 : if (handler->IsType(ReadHandler::InteractionType::Read))
1797 : {
1798 0 : return Loop::Break;
1799 : }
1800 :
1801 0 : return Loop::Continue;
1802 0 : }) == Loop::Break));
1803 : }
1804 :
1805 0 : uint16_t InteractionModelEngine::GetMinGuaranteedSubscriptionsPerFabric() const
1806 : {
1807 : #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
1808 0 : return UINT16_MAX;
1809 : #else
1810 : return static_cast<uint16_t>(
1811 : min(GetReadHandlerPoolCapacityForSubscriptions() / GetConfigMaxFabrics(), static_cast<size_t>(UINT16_MAX)));
1812 : #endif
1813 : }
1814 :
1815 5 : size_t InteractionModelEngine::GetNumDirtySubscriptions() const
1816 : {
1817 5 : size_t numDirtySubscriptions = 0;
1818 5 : mReadHandlers.ForEachActiveObject([&](const auto readHandler) {
1819 33 : if (readHandler->IsType(ReadHandler::InteractionType::Subscribe) && readHandler->IsDirty())
1820 : {
1821 8 : numDirtySubscriptions++;
1822 : }
1823 33 : return Loop::Continue;
1824 : });
1825 5 : return numDirtySubscriptions;
1826 : }
1827 :
1828 5 : void InteractionModelEngine::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
1829 : {
1830 5 : mReadHandlers.ForEachActiveObject([fabricIndex](ReadHandler * handler) {
1831 2 : if (handler->GetAccessingFabricIndex() == fabricIndex)
1832 : {
1833 2 : ChipLogProgress(InteractionModel, "Deleting expired ReadHandler for NodeId: " ChipLogFormatX64 ", FabricIndex: %u",
1834 : ChipLogValueX64(handler->GetInitiatorNodeId()), fabricIndex);
1835 2 : handler->Close();
1836 : }
1837 :
1838 2 : return Loop::Continue;
1839 : });
1840 :
1841 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
1842 9 : for (auto * readClient = mpActiveReadClientList; readClient != nullptr; readClient = readClient->GetNextClient())
1843 : {
1844 4 : if (readClient->GetFabricIndex() == fabricIndex)
1845 : {
1846 2 : ChipLogProgress(InteractionModel, "Fabric removed, deleting obsolete read client with FabricIndex: %u", fabricIndex);
1847 2 : readClient->Close(CHIP_ERROR_IM_FABRIC_DELETED, false);
1848 : }
1849 : }
1850 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
1851 :
1852 25 : for (auto & handler : mWriteHandlers)
1853 : {
1854 20 : if (!(handler.IsFree()) && handler.GetAccessingFabricIndex() == fabricIndex)
1855 : {
1856 1 : ChipLogProgress(InteractionModel, "Fabric removed, deleting obsolete write handler with FabricIndex: %u", fabricIndex);
1857 1 : handler.Close();
1858 : }
1859 : }
1860 :
1861 : // Applications may hold references to CommandHandler instances for async command processing.
1862 : // Therefore we can't forcible destroy CommandHandlers here. Their exchanges will get closed by
1863 : // the fabric removal, though, so they will fail when they try to actually send their command response
1864 : // and will close at that point.
1865 5 : }
1866 :
1867 1 : CHIP_ERROR InteractionModelEngine::ResumeSubscriptions()
1868 : {
1869 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
1870 1 : ReturnErrorCodeIf(!mpSubscriptionResumptionStorage, CHIP_NO_ERROR);
1871 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1872 1 : ReturnErrorCodeIf(mSubscriptionResumptionScheduled, CHIP_NO_ERROR);
1873 : #endif
1874 :
1875 : // To avoid the case of a reboot loop causing rapid traffic generation / power consumption, subscription resumption should make
1876 : // use of the persisted min-interval values, and wait before resumption. Ideally, each persisted subscription should wait their
1877 : // own min-interval value before resumption, but that both A) potentially runs into a timer resource issue, and B) having a
1878 : // low-powered device wake many times also has energy use implications. The logic below waits the largest of the persisted
1879 : // min-interval values before resuming subscriptions.
1880 :
1881 : // Even though this causes subscription-to-subscription interaction by linking the min-interval values, this is the right thing
1882 : // to do for now because it's both simple and avoids the timer resource and multiple-wake problems. This issue is to track
1883 : // future improvements: https://github.com/project-chip/connectedhomeip/issues/25439
1884 :
1885 1 : SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
1886 1 : auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions();
1887 1 : int subscriptionsToResume = 0;
1888 1 : uint16_t minInterval = 0;
1889 1 : while (iterator->Next(subscriptionInfo))
1890 : {
1891 0 : subscriptionsToResume++;
1892 0 : minInterval = std::max(minInterval, subscriptionInfo.mMinInterval);
1893 : }
1894 1 : iterator->Release();
1895 :
1896 1 : if (subscriptionsToResume)
1897 : {
1898 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1899 0 : mSubscriptionResumptionScheduled = true;
1900 : #endif
1901 0 : ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", subscriptionsToResume, minInterval);
1902 0 : ReturnErrorOnFailure(mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds16(minInterval),
1903 : ResumeSubscriptionsTimerCallback, this));
1904 : }
1905 : else
1906 : {
1907 1 : ChipLogProgress(InteractionModel, "No subscriptions to resume");
1908 : }
1909 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
1910 :
1911 1 : return CHIP_NO_ERROR;
1912 1 : }
1913 :
1914 0 : void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * apSystemLayer, void * apAppState)
1915 : {
1916 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
1917 0 : VerifyOrReturn(apAppState != nullptr);
1918 0 : InteractionModelEngine * imEngine = static_cast<InteractionModelEngine *>(apAppState);
1919 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1920 0 : imEngine->mSubscriptionResumptionScheduled = false;
1921 0 : bool resumedSubscriptions = false;
1922 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1923 0 : SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
1924 0 : AutoReleaseSubscriptionInfoIterator iterator(imEngine->mpSubscriptionResumptionStorage->IterateSubscriptions());
1925 0 : while (iterator->Next(subscriptionInfo))
1926 : {
1927 : // If subscription happens between reboot and this timer callback, it's already live and should skip resumption
1928 0 : if (Loop::Break == imEngine->mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
1929 : SubscriptionId subscriptionId;
1930 0 : handler->GetSubscriptionId(subscriptionId);
1931 0 : if (subscriptionId == subscriptionInfo.mSubscriptionId)
1932 : {
1933 0 : return Loop::Break;
1934 : }
1935 0 : return Loop::Continue;
1936 : }))
1937 : {
1938 0 : ChipLogProgress(InteractionModel, "Skip resuming live subscriptionId %" PRIu32, subscriptionInfo.mSubscriptionId);
1939 0 : continue;
1940 : }
1941 :
1942 0 : auto subscriptionResumptionSessionEstablisher = Platform::MakeUnique<SubscriptionResumptionSessionEstablisher>();
1943 0 : if (subscriptionResumptionSessionEstablisher == nullptr)
1944 : {
1945 0 : ChipLogProgress(InteractionModel, "Failed to create SubscriptionResumptionSessionEstablisher");
1946 0 : return;
1947 : }
1948 :
1949 0 : if (subscriptionResumptionSessionEstablisher->ResumeSubscription(*imEngine->mpCASESessionMgr, subscriptionInfo) !=
1950 0 : CHIP_NO_ERROR)
1951 : {
1952 0 : ChipLogProgress(InteractionModel, "Failed to ResumeSubscription 0x%" PRIx32, subscriptionInfo.mSubscriptionId);
1953 0 : return;
1954 : }
1955 0 : subscriptionResumptionSessionEstablisher.release();
1956 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1957 0 : resumedSubscriptions = true;
1958 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1959 0 : }
1960 :
1961 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1962 : // If no persisted subscriptions needed resumption then all resumption retries are done
1963 0 : if (!resumedSubscriptions)
1964 : {
1965 0 : imEngine->mNumSubscriptionResumptionRetries = 0;
1966 : }
1967 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1968 :
1969 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
1970 0 : }
1971 :
1972 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
1973 12 : uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption()
1974 : {
1975 12 : if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX)
1976 : {
1977 1 : return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS;
1978 : }
1979 :
1980 : return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS +
1981 11 : GetFibonacciForIndex(mNumSubscriptionResumptionRetries) *
1982 11 : CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS;
1983 : }
1984 :
1985 727 : bool InteractionModelEngine::HasSubscriptionsToResume()
1986 : {
1987 727 : VerifyOrReturnValue(mpSubscriptionResumptionStorage != nullptr, false);
1988 :
1989 : // Look through persisted subscriptions and see if any aren't already in mReadHandlers pool
1990 0 : SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo;
1991 0 : auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions();
1992 0 : bool foundSubscriptionToResume = false;
1993 0 : while (iterator->Next(subscriptionInfo))
1994 : {
1995 0 : if (Loop::Break == mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) {
1996 : SubscriptionId subscriptionId;
1997 0 : handler->GetSubscriptionId(subscriptionId);
1998 0 : if (subscriptionId == subscriptionInfo.mSubscriptionId)
1999 : {
2000 0 : return Loop::Break;
2001 : }
2002 0 : return Loop::Continue;
2003 : }))
2004 : {
2005 0 : continue;
2006 : }
2007 :
2008 0 : foundSubscriptionToResume = true;
2009 0 : break;
2010 : }
2011 0 : iterator->Release();
2012 :
2013 0 : return foundSubscriptionToResume;
2014 0 : }
2015 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
2016 :
2017 : } // namespace app
2018 : } // namespace chip
|