Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include <app-common/zap-generated/attributes/Accessors.h>
19 : #include <app-common/zap-generated/ids/Attributes.h>
20 : #include <app-common/zap-generated/ids/Clusters.h>
21 : #include <app/icd/server/ICDConfigurationData.h>
22 : #include <app/icd/server/ICDManager.h>
23 : #include <app/icd/server/ICDServerConfig.h>
24 : #include <lib/core/ClusterEnums.h>
25 : #include <lib/support/CodeUtils.h>
26 : #include <lib/support/logging/CHIPLogging.h>
27 : #include <platform/ConnectivityManager.h>
28 : #include <platform/LockTracker.h>
29 : #include <platform/internal/CHIPDeviceLayerInternal.h>
30 :
31 : namespace {
32 : enum class ICDTestEventTriggerEvent : uint64_t
33 : {
34 : kAddActiveModeReq = 0x0046'0000'00000001,
35 : kRemoveActiveModeReq = 0x0046'0000'00000002,
36 : kInvalidateHalfCounterValues = 0x0046'0000'00000003,
37 : kInvalidateAllCounterValues = 0x0046'0000'00000004,
38 : kForceMaximumCheckInBackOffState = 0x0046'0000'00000005,
39 : kDSLSForceSitMode = 0x0046'0000'00000006,
40 : kDSLSWithdrawSitMode = 0x0046'0000'00000007,
41 : };
42 : } // namespace
43 :
44 : namespace chip {
45 : namespace app {
46 :
47 : using namespace chip::app;
48 : using namespace chip::app::Clusters;
49 : using namespace chip::app::Clusters::IcdManagement;
50 : using namespace System::Clock;
51 :
52 : using chip::Protocols::InteractionModel::Status;
53 :
54 : static_assert(UINT8_MAX >= CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS,
55 : "ICDManager::mOpenExchangeContextCount cannot hold count for the max exchange count");
56 :
57 10 : void ICDManager::Init()
58 : {
59 : #if CHIP_CONFIG_ENABLE_ICD_CIP
60 : VerifyOrDie(mStorage != nullptr);
61 : VerifyOrDie(mFabricTable != nullptr);
62 : VerifyOrDie(mSymmetricKeystore != nullptr);
63 : VerifyOrDie(mExchangeManager != nullptr);
64 : VerifyOrDie(mSubInfoProvider != nullptr);
65 : VerifyOrDie(mICDCheckInBackOffStrategy != nullptr);
66 :
67 : VerifyOrDie(ICDConfigurationData::GetInstance().GetICDCounter().Init(mStorage, DefaultStorageKeyAllocator::ICDCheckInCounter(),
68 : ICDConfigurationData::kICDCounterPersistenceIncrement) ==
69 : CHIP_NO_ERROR);
70 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
71 :
72 : #if CHIP_CONFIG_ENABLE_ICD_LIT
73 : // LIT ICD Verification Checks
74 : if (SupportsFeature(Feature::kLongIdleTimeSupport))
75 : {
76 : VerifyOrDieWithMsg(SupportsFeature(Feature::kCheckInProtocolSupport), AppServer,
77 : "The CheckIn protocol feature is required for LIT support.");
78 : VerifyOrDieWithMsg(SupportsFeature(Feature::kUserActiveModeTrigger), AppServer,
79 : "The user ActiveMode trigger feature is required for LIT support.");
80 : VerifyOrDieWithMsg(ICDConfigurationData::GetInstance().GetMinLitActiveModeThreshold() <=
81 : ICDConfigurationData::GetInstance().GetActiveModeThreshold(),
82 : AppServer, "The minimum ActiveModeThreshold value for a LIT ICD is 5 seconds.");
83 : }
84 : #endif // CHIP_CONFIG_ENABLE_ICD_LIT
85 :
86 10 : SuccessOrDie(ICDNotifier::GetInstance().Subscribe(this));
87 :
88 10 : UpdateICDMode();
89 10 : UpdateOperationState(OperationalState::IdleMode);
90 10 : }
91 :
92 10 : void ICDManager::Shutdown()
93 : {
94 10 : ICDNotifier::GetInstance().Unsubscribe(this);
95 :
96 : // cancel any running timer of the icd
97 10 : DeviceLayer::SystemLayer().CancelTimer(OnIdleModeDone, this);
98 10 : DeviceLayer::SystemLayer().CancelTimer(OnActiveModeDone, this);
99 10 : DeviceLayer::SystemLayer().CancelTimer(OnTransitionToIdle, this);
100 :
101 10 : ICDConfigurationData::GetInstance().SetICDMode(ICDConfigurationData::ICDMode::SIT);
102 10 : mOperationalState = OperationalState::ActiveMode;
103 10 : mStateObserverPool.ReleaseAll();
104 :
105 : #if CHIP_CONFIG_ENABLE_ICD_CIP
106 : mStorage = nullptr;
107 : mFabricTable = nullptr;
108 : mSubInfoProvider = nullptr;
109 : mICDSenderPool.ReleaseAll();
110 :
111 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
112 : mIsBootUpResumeSubscriptionExecuted = false;
113 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
114 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
115 10 : }
116 :
117 0 : bool ICDManager::SupportsFeature(Feature feature)
118 : {
119 0 : return ICDConfigurationData::GetInstance().GetFeatureMap().Has(feature);
120 : }
121 :
122 4 : uint32_t ICDManager::StayActiveRequest(uint32_t stayActiveDuration)
123 : {
124 : // This should only be called when the device is in ActiveMode
125 4 : VerifyOrReturnValue(mOperationalState == OperationalState::ActiveMode, 0);
126 :
127 : uint32_t promisedActiveDuration =
128 4 : std::min(ICDConfigurationData::GetInstance().GetGuaranteedStayActiveDuration().count(), stayActiveDuration);
129 :
130 : // If the device is already in ActiveMode, we need to extend the active mode duration
131 : // for whichever is smallest between 30000 milliseconds and stayActiveDuration, taking in account the remaining active time.
132 4 : ExtendActiveMode(System::Clock::Milliseconds16(promisedActiveDuration));
133 4 : promisedActiveDuration = DeviceLayer::SystemLayer().GetRemainingTime(OnActiveModeDone, this).count();
134 :
135 4 : return promisedActiveDuration;
136 : }
137 :
138 : #if CHIP_CONFIG_ENABLE_ICD_CIP
139 : void ICDManager::SendCheckInMsgs(Optional<Access::SubjectDescriptor> specificSubject)
140 : {
141 : #if !(CONFIG_BUILD_FOR_HOST_UNIT_TEST)
142 : VerifyOrDie(SupportsFeature(Feature::kCheckInProtocolSupport));
143 : VerifyOrDie(mStorage != nullptr);
144 : VerifyOrDie(mFabricTable != nullptr);
145 :
146 : uint32_t counterValue = ICDConfigurationData::GetInstance().GetICDCounter().GetNextCheckInCounterValue();
147 : bool counterIncremented = false;
148 :
149 : for (const auto & fabricInfo : *mFabricTable)
150 : {
151 : uint16_t supported_clients = ICDConfigurationData::GetInstance().GetClientsSupportedPerFabric();
152 :
153 : ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), supported_clients /*Table entry limit*/,
154 : mSymmetricKeystore);
155 :
156 : if (table.IsEmpty())
157 : {
158 : continue;
159 : }
160 :
161 : for (uint16_t i = 0; i < table.Limit(); i++)
162 : {
163 : ICDMonitoringEntry entry(mSymmetricKeystore);
164 : CHIP_ERROR err = table.Get(i, entry);
165 : if (err == CHIP_ERROR_NOT_FOUND)
166 : {
167 : break;
168 : }
169 :
170 : if (err != CHIP_NO_ERROR)
171 : {
172 : // Try to fetch the next entry upon failure (should not happen).
173 : ChipLogError(AppServer, "Failed to retrieved ICDMonitoring entry for Check-In msg, will try next entry.");
174 : continue;
175 : }
176 :
177 : if (specificSubject.HasValue() && !ShouldSendCheckInMessageForSpecificSubject(entry, specificSubject.Value()))
178 : {
179 : continue;
180 : }
181 :
182 : if (!specificSubject.HasValue() &&
183 : !ShouldCheckInMsgsBeSentAtActiveModeFunction(entry.fabricIndex, entry.monitoredSubject))
184 : {
185 : continue;
186 : }
187 :
188 : if (!mICDCheckInBackOffStrategy->ShouldSendCheckInMessage(entry))
189 : {
190 : // continue to next entry
191 : continue;
192 : }
193 :
194 : // Increment counter only once to prevent depletion of the available range.
195 : if (!counterIncremented)
196 : {
197 : counterIncremented = true;
198 :
199 : if (CHIP_NO_ERROR != ICDConfigurationData::GetInstance().GetICDCounter().Advance())
200 : {
201 : ChipLogError(AppServer, "Incremented ICDCounter but failed to access/save to Persistent storage");
202 : }
203 : }
204 :
205 : // SenderPool will be released upon transition from active to idle state
206 : // This will happen when all ICD Check-In messages are sent on the network
207 : ICDCheckInSender * sender = mICDSenderPool.CreateObject(mExchangeManager);
208 : VerifyOrReturn(sender != nullptr, ChipLogError(AppServer, "Failed to allocate ICDCheckinSender"));
209 :
210 : if (CHIP_NO_ERROR != sender->RequestResolve(entry, mFabricTable, counterValue))
211 : {
212 : ChipLogError(AppServer, "Failed to send ICD Check-In");
213 : }
214 : }
215 : }
216 : #endif // !(CONFIG_BUILD_FOR_HOST_UNIT_TEST)
217 : }
218 :
219 : bool ICDManager::ShouldSendCheckInMessageForSpecificSubject(const ICDMonitoringEntry & entry,
220 : const Access::SubjectDescriptor & specificSubject)
221 : {
222 : if (specificSubject.fabricIndex != entry.fabricIndex)
223 : {
224 : return false;
225 : }
226 :
227 : if (specificSubject.cats.CheckSubjectAgainstCATs(entry.monitoredSubject) || entry.monitoredSubject == specificSubject.subject)
228 : {
229 : ChipLogProgress(AppServer, "Proceed to send Check-In msg for specific subject: " ChipLogFormatX64,
230 : ChipLogValueX64(specificSubject.subject));
231 : return true;
232 : }
233 :
234 : return false;
235 : }
236 :
237 : bool ICDManager::CheckInMessagesWouldBeSent(const std::function<ShouldCheckInMsgsBeSentFunction> & shouldCheckInMsgsBeSentFunction)
238 : {
239 : VerifyOrReturnValue(shouldCheckInMsgsBeSentFunction, false);
240 :
241 : for (const auto & fabricInfo : *mFabricTable)
242 : {
243 : uint16_t supported_clients = ICDConfigurationData::GetInstance().GetClientsSupportedPerFabric();
244 :
245 : ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), supported_clients /*Table entry limit*/,
246 : mSymmetricKeystore);
247 : if (table.IsEmpty())
248 : {
249 : continue;
250 : }
251 :
252 : for (uint16_t i = 0; i < table.Limit(); i++)
253 : {
254 : ICDMonitoringEntry entry(mSymmetricKeystore);
255 : CHIP_ERROR err = table.Get(i, entry);
256 : if (err == CHIP_ERROR_NOT_FOUND)
257 : {
258 : break;
259 : }
260 :
261 : if (err != CHIP_NO_ERROR)
262 : {
263 : // Try to fetch the next entry upon failure (should not happen).
264 : ChipLogError(AppServer, "Failed to retrieved ICDMonitoring entry, will try next entry.");
265 : continue;
266 : }
267 :
268 : if (entry.clientType == ClientTypeEnum::kEphemeral)
269 : {
270 : // If the registered client is ephemeral, no Check-In message would be sent to this client
271 : continue;
272 : }
273 :
274 : // At least one registration would require a Check-In message
275 : VerifyOrReturnValue(!shouldCheckInMsgsBeSentFunction(entry.fabricIndex, entry.monitoredSubject), true);
276 : }
277 : }
278 :
279 : // None of the registrations would require a Check-In message
280 : return false;
281 : }
282 :
283 : /**
284 : * ShouldCheckInMsgsBeSentAtActiveModeFunction is used to determine if a Check-In message is required for a given registration.
285 : * Due to how the ICD Check-In use-case interacts with the persistent subscription and subscription timeout resumption features,
286 : * having a single implementation of the function renders the implementation very difficult to understand and maintain.
287 : * Because of this, each valid feature combination has its own implementation of the function.
288 : */
289 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
290 : #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
291 : /**
292 : * @brief Implementation for when the persistent subscription and subscription timeout resumption feature are present.
293 : * Function checks that there are no active or persisted subscriptions for a given fabricIndex or subjectID.
294 : *
295 : * @note When the persistent subscription and subscription timeout resumption feature are present, we need to check for
296 : * persisted subscription at each transition to ActiveMode since there will be persisted subscriptions during normal
297 : * operation for the subscription timeout resumption feature. Once we have finished all our subscription resumption attempts
298 : * for a given subscription, the entry is deleted from persisted storage which will enable us to send Check-In messages for
299 : * the client registration. This logic avoids the device sending a Check-In message while trying to resume subscriptions.
300 : *
301 : * @param aFabricIndex
302 : * @param subjectID subjectID to check. Can be an operational node id or a CAT
303 : *
304 : * @return true Returns true if the fabricIndex and subjectId combination does not have an active or a persisted subscription.
305 : * @return false Returns false if the fabricIndex and subjectId combination has an active or persisted subscription.
306 : */
307 : bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID)
308 : {
309 : return !(mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID) ||
310 : mSubInfoProvider->SubjectHasPersistedSubscription(aFabricIndex, subjectID));
311 : }
312 : #else
313 : /**
314 : * @brief Implementation for when the persistent subscription feature is present without the subscription timeout resumption
315 : * feature. Function checks that there are no active subscriptions. If the boot up subscription resumption has not been completed,
316 : * function also checks if there are persisted subscriptions.
317 : *
318 : * @note The persistent subscriptions feature tries to resume subscriptions at the highest min interval
319 : * of all the persisted subscriptions. As such, it is possible for the ICD to return to Idle Mode
320 : * until the timer elaspses. We do not want to send Check-In messages to clients with persisted subscriptions
321 : * until we have tried to resubscribe.
322 : *
323 : * @param aFabricIndex
324 : * @param subjectID subjectID to check. Can be an opperationnal node id or a CAT
325 : *
326 : * @return true Returns true if the fabricIndex and subjectId combination does not have an active subscription.
327 : * If the boot up subscription resumption has not been completed, there must not be a persisted subscription either.
328 : * @return false Returns false if the fabricIndex and subjectId combination has an active subscription.
329 : * If the boot up subscription resumption has not been completed,
330 : * returns false if the fabricIndex and subjectId combination has a persisted subscription.
331 : */
332 : bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID)
333 : {
334 : bool mightHaveSubscription = mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID);
335 : if (!mightHaveSubscription && !mIsBootUpResumeSubscriptionExecuted)
336 : {
337 : mightHaveSubscription = mSubInfoProvider->SubjectHasPersistedSubscription(aFabricIndex, subjectID);
338 : }
339 :
340 : return !mightHaveSubscription;
341 : }
342 : #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
343 : #else
344 : /**
345 : * @brief Implementation for when neither the persistent subscription nor the subscription timeout resumption features are present.
346 : * Function checks that there no active sbuscriptions for a given fabricIndex and subjectId combination.
347 : *
348 : * @note When neither the persistent subscription nor the subscription timeout resumption features are present, we only need to
349 : * check for active subscription since we will never have any persisted subscription.
350 : *
351 : * @param aFabricIndex
352 : * @param subjectID subjectID to check. Can be an opperationnal node id or a CAT
353 : *
354 : * @return true Returns true if the fabricIndex and subjectId combination does not have an active subscription.
355 : * @return false Returns false if the fabricIndex and subjectId combination has an active subscription.
356 : */
357 : bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID)
358 : {
359 : return !(mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID));
360 : }
361 : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
362 :
363 : void ICDManager::TriggerCheckInMessages(const std::function<ShouldCheckInMsgsBeSentFunction> & verifier)
364 : {
365 : VerifyOrReturn(SupportsFeature(Feature::kCheckInProtocolSupport));
366 :
367 : // Only trigger Check-In messages when we are in IdleMode.
368 : // If we are already in ActiveMode, Check-In messages have already been sent.
369 : VerifyOrReturn(mOperationalState == OperationalState::IdleMode);
370 :
371 : // If we don't have any Check-In messages to send, do nothing
372 : VerifyOrReturn(CheckInMessagesWouldBeSent(verifier));
373 : UpdateOperationState(OperationalState::ActiveMode);
374 : }
375 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
376 :
377 10 : void ICDManager::UpdateICDMode()
378 : {
379 10 : assertChipStackLockedByCurrentThread();
380 :
381 10 : ICDConfigurationData::ICDMode tempMode = ICDConfigurationData::ICDMode::SIT;
382 :
383 : #if CHIP_CONFIG_ENABLE_ICD_LIT
384 : // Device can only switch to the LIT operating mode if LIT support is present
385 : if (SupportsFeature(Feature::kLongIdleTimeSupport))
386 : {
387 : #if CHIP_CONFIG_ENABLE_ICD_DSLS
388 : // Ensure SIT mode is not requested
389 : if (SupportsFeature(Feature::kDynamicSitLitSupport) && !mSITModeRequested)
390 : {
391 : #endif // CHIP_CONFIG_ENABLE_ICD_DSLS
392 :
393 : VerifyOrDie(mStorage != nullptr);
394 : VerifyOrDie(mFabricTable != nullptr);
395 : // We can only get to LIT Mode, if at least one client is registered with the ICD device
396 : for (const auto & fabricInfo : *mFabricTable)
397 : {
398 : // We only need 1 valid entry to ensure LIT compliance
399 : ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), 1 /*Table entry limit*/, mSymmetricKeystore);
400 : if (!table.IsEmpty())
401 : {
402 : tempMode = ICDConfigurationData::ICDMode::LIT;
403 : break;
404 : }
405 : }
406 : #if CHIP_CONFIG_ENABLE_ICD_DSLS
407 : }
408 : #endif // CHIP_CONFIG_ENABLE_ICD_DSLS
409 : }
410 : #endif // CHIP_CONFIG_ENABLE_ICD_LIT
411 :
412 10 : if (ICDConfigurationData::GetInstance().GetICDMode() != tempMode)
413 : {
414 0 : ICDConfigurationData::GetInstance().SetICDMode(tempMode);
415 0 : postObserverEvent(ObserverEventType::ICDModeChange);
416 : }
417 :
418 : // When in SIT mode, the slow poll interval SHOULDN'T be greater than the SIT mode polling threshold, per spec.
419 20 : if (ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::SIT &&
420 20 : ICDConfigurationData::GetInstance().GetSlowPollingInterval() > ICDConfigurationData::GetInstance().GetSITPollingThreshold())
421 : {
422 0 : ChipLogDetail(AppServer, "The Slow Polling Interval of an ICD in SIT mode should be <= %" PRIu32 " seconds",
423 : (ICDConfigurationData::GetInstance().GetSITPollingThreshold().count() / 1000));
424 : }
425 10 : }
426 :
427 43 : void ICDManager::UpdateOperationState(OperationalState state)
428 : {
429 43 : assertChipStackLockedByCurrentThread();
430 : // Active mode can be re-triggered.
431 43 : VerifyOrReturn(mOperationalState != state || state == OperationalState::ActiveMode);
432 :
433 43 : ICDConfigurationData & configData = ICDConfigurationData::GetInstance();
434 43 : if (state == OperationalState::IdleMode)
435 : {
436 23 : mOperationalState = OperationalState::IdleMode;
437 :
438 : #if CHIP_CONFIG_ENABLE_ICD_CIP
439 : std::function<ShouldCheckInMsgsBeSentFunction> sendCheckInMessagesOnActiveMode =
440 : std::bind(&ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction, this, std::placeholders::_1, std::placeholders::_2);
441 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
442 :
443 : // When the ActiveModeDuration is set to 0, the ICDManager does not need to periodically transition to active mode.
444 : // Instead, It can stay in idle mode until a notification, Report or other network event automatically toggles the ICD into
445 : // active mode. The following conditions will schedule a transition to Active Mode after the Idle Mode duration expires.
446 : // - An ActiveModeDuration interval must be respected.
447 : // - The device state indicates to shorten its idle duration and report faster to provide better responsiveness
448 : // - Check-In messages must be sent
449 23 : if (configData.GetActiveModeDuration() > kZero || configData.ShouldUseShortIdle()
450 : #if CHIP_CONFIG_ENABLE_ICD_CIP
451 : || CheckInMessagesWouldBeSent(sendCheckInMessagesOnActiveMode)
452 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
453 : )
454 : {
455 22 : TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().StartTimer(configData.GetModeBasedIdleModeDuration(),
456 : OnIdleModeDone, this);
457 : }
458 :
459 : #if CHIP_CONFIG_ENABLE_ICD_CIP
460 : // Going back to Idle, all Check-In messages are sent
461 : mICDSenderPool.ReleaseAll();
462 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
463 :
464 23 : CHIP_ERROR err = DeviceLayer::ConnectivityMgr().SetPollingInterval(configData.GetSlowPollingInterval());
465 46 : if (err != CHIP_NO_ERROR)
466 : {
467 23 : ChipLogError(AppServer, "Failed to set Slow Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
468 : }
469 :
470 23 : postObserverEvent(ObserverEventType::EnterIdleMode);
471 : }
472 20 : else if (state == OperationalState::ActiveMode)
473 : {
474 20 : if (mOperationalState == OperationalState::IdleMode)
475 : {
476 : // An event could have brought us to the active mode.
477 : // Make sure the idle mode timer is stopped
478 16 : DeviceLayer::SystemLayer().CancelTimer(OnIdleModeDone, this);
479 :
480 16 : mOperationalState = OperationalState::ActiveMode;
481 16 : Milliseconds32 activeModeDuration = configData.GetActiveModeDuration();
482 :
483 16 : if (activeModeDuration == kZero && !mKeepActiveFlags.HasAny())
484 : {
485 : // Network Activity triggered the active mode and activeModeDuration is 0.
486 : // Stay active for at least Active Mode Threshold.
487 1 : activeModeDuration = configData.GetActiveModeThreshold();
488 : }
489 :
490 16 : TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().StartTimer(activeModeDuration, OnActiveModeDone, this);
491 :
492 16 : Milliseconds32 activeModeJitterInterval = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
493 : // TODO(#33074): Edge case when we transition to IdleMode with this condition being true
494 : // (activeModeDuration == kZero && !mKeepActiveFlags.HasAny())
495 16 : activeModeJitterInterval =
496 32 : (activeModeDuration >= activeModeJitterInterval) ? activeModeDuration - activeModeJitterInterval : kZero;
497 :
498 : // Reset this flag when we enter ActiveMode to avoid having a feedback loop that keeps us indefinitly in
499 : // ActiveMode.
500 16 : mTransitionToIdleCalled = false;
501 16 : TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().StartTimer(activeModeJitterInterval, OnTransitionToIdle, this);
502 :
503 16 : CHIP_ERROR err = DeviceLayer::ConnectivityMgr().SetPollingInterval(configData.GetFastPollingInterval());
504 32 : if (err != CHIP_NO_ERROR)
505 : {
506 16 : ChipLogError(AppServer, "Failed to set Fast Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
507 : }
508 :
509 : #if CHIP_CONFIG_ENABLE_ICD_CIP
510 : SendCheckInMsgs();
511 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
512 :
513 16 : postObserverEvent(ObserverEventType::EnterActiveMode);
514 : }
515 : else
516 : {
517 4 : ExtendActiveMode(configData.GetActiveModeThreshold());
518 : }
519 : }
520 : }
521 :
522 10 : void ICDManager::SetKeepActiveModeRequirements(KeepActiveFlags flag, bool state)
523 : {
524 10 : assertChipStackLockedByCurrentThread();
525 :
526 10 : mKeepActiveFlags.Set(flag, state);
527 10 : if (mOperationalState == OperationalState::IdleMode && mKeepActiveFlags.HasAny())
528 : {
529 4 : UpdateOperationState(OperationalState::ActiveMode);
530 : }
531 10 : else if (mOperationalState == OperationalState::ActiveMode && !mKeepActiveFlags.HasAny() &&
532 4 : !DeviceLayer::SystemLayer().IsTimerActive(OnActiveModeDone, this))
533 : {
534 : // The normal active period had ended and nothing else requires the system to be active.
535 3 : UpdateOperationState(OperationalState::IdleMode);
536 : }
537 10 : }
538 :
539 9 : void ICDManager::OnIdleModeDone(System::Layer * aLayer, void * appState)
540 : {
541 9 : ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
542 9 : pICDManager->UpdateOperationState(OperationalState::ActiveMode);
543 9 : }
544 :
545 13 : void ICDManager::OnActiveModeDone(System::Layer * aLayer, void * appState)
546 : {
547 13 : ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
548 :
549 : // Don't go to idle mode when we have a keep active requirement
550 13 : if (!pICDManager->mKeepActiveFlags.HasAny())
551 : {
552 10 : pICDManager->UpdateOperationState(OperationalState::IdleMode);
553 : }
554 13 : }
555 :
556 15 : void ICDManager::OnTransitionToIdle(System::Layer * aLayer, void * appState)
557 : {
558 15 : ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
559 :
560 : // OnTransitionToIdle will trigger a report message if reporting is needed, which should extend the active mode until the
561 : // ack for the report is received.
562 15 : pICDManager->mTransitionToIdleCalled = true;
563 15 : pICDManager->postObserverEvent(ObserverEventType::TransitionToIdle);
564 15 : }
565 :
566 : /* ICDListener functions. */
567 :
568 4 : void ICDManager::OnKeepActiveRequest(KeepActiveFlags request)
569 : {
570 4 : assertChipStackLockedByCurrentThread();
571 4 : VerifyOrReturn(request < KeepActiveFlagsValues::kInvalidFlag);
572 :
573 4 : if (request.Has(KeepActiveFlag::kExchangeContextOpen))
574 : {
575 : // There can be multiple open exchange contexts at the same time.
576 : // Keep track of the requests count.
577 1 : this->mOpenExchangeContextCount++;
578 : }
579 :
580 : #if CHIP_CONFIG_ENABLE_ICD_CIP
581 : if (request.Has(KeepActiveFlag::kCheckInInProgress))
582 : {
583 : // There can be multiple check-in at the same time.
584 : // Keep track of the requests count.
585 : this->mCheckInRequestCount++;
586 : }
587 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
588 :
589 4 : this->SetKeepActiveModeRequirements(request, true /* state */);
590 : }
591 :
592 4 : void ICDManager::OnActiveRequestWithdrawal(KeepActiveFlags request)
593 : {
594 4 : assertChipStackLockedByCurrentThread();
595 4 : VerifyOrReturn(request < KeepActiveFlagsValues::kInvalidFlag);
596 :
597 4 : if (request.Has(KeepActiveFlag::kExchangeContextOpen))
598 : {
599 : // There can be multiple open exchange contexts at the same time.
600 : // Keep track of the requests count.
601 1 : if (this->mOpenExchangeContextCount > 0)
602 : {
603 1 : this->mOpenExchangeContextCount--;
604 : }
605 : else
606 : {
607 0 : ChipLogError(DeviceLayer, "The ICD Manager did not account for ExchangeContext closure");
608 : }
609 :
610 1 : if (this->mOpenExchangeContextCount == 0)
611 : {
612 1 : this->SetKeepActiveModeRequirements(KeepActiveFlag::kExchangeContextOpen, false /* state */);
613 : }
614 : }
615 :
616 : #if CHIP_CONFIG_ENABLE_ICD_CIP
617 : if (request.Has(KeepActiveFlag::kCheckInInProgress))
618 : {
619 : // There can be multiple open exchange contexts at the same time.
620 : // Keep track of the requests count.
621 : if (this->mCheckInRequestCount > 0)
622 : {
623 : this->mCheckInRequestCount--;
624 : }
625 : else
626 : {
627 : ChipLogError(DeviceLayer, "The ICD Manager did not account for Check-In Sender start");
628 : }
629 :
630 : if (this->mCheckInRequestCount == 0)
631 : {
632 : this->SetKeepActiveModeRequirements(KeepActiveFlag::kCheckInInProgress, false /* state */);
633 : }
634 : }
635 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
636 :
637 4 : if (request.Has(KeepActiveFlag::kCommissioningWindowOpen) || request.Has(KeepActiveFlag::kFailSafeArmed))
638 : {
639 : // Only 1 request per type (kCommissioningWindowOpen, kFailSafeArmed)
640 : // remove requirement directly
641 3 : this->SetKeepActiveModeRequirements(request, false /* state */);
642 : }
643 : }
644 :
645 : #if CHIP_CONFIG_ENABLE_ICD_DSLS
646 : void ICDManager::OnSITModeRequest()
647 : {
648 : mSITModeRequested = true;
649 : this->UpdateICDMode();
650 : // Update the poll interval also to comply with SIT requirements
651 : UpdateOperationState(OperationalState::ActiveMode);
652 : }
653 :
654 : void ICDManager::OnSITModeRequestWithdrawal()
655 : {
656 : mSITModeRequested = false;
657 : this->UpdateICDMode();
658 : // Update the poll interval also to comply with LIT requirements
659 : UpdateOperationState(OperationalState::ActiveMode);
660 : }
661 : #endif // CHIP_CONFIG_ENABLE_ICD_DSLS
662 :
663 4 : void ICDManager::OnNetworkActivity()
664 : {
665 4 : this->UpdateOperationState(OperationalState::ActiveMode);
666 4 : }
667 :
668 0 : void ICDManager::OnICDManagementServerEvent(ICDManagementEvents event)
669 : {
670 0 : switch (event)
671 : {
672 0 : case ICDManagementEvents::kTableUpdated:
673 0 : this->UpdateICDMode();
674 0 : break;
675 0 : default:
676 0 : break;
677 : }
678 0 : }
679 :
680 3 : void ICDManager::OnSubscriptionReport()
681 : {
682 : // If the device is already in ActiveMode, that means that all active subscriptions have already been marked dirty.
683 : // Since we only mark them dirty when we enter ActiveMode, it is not necessary to update the operational state a second time.
684 : // Doing so will only add an ActiveModeThreshold to the active time which we don't want to do here.
685 3 : VerifyOrReturn(mOperationalState == OperationalState::IdleMode);
686 3 : this->UpdateOperationState(OperationalState::ActiveMode);
687 : }
688 :
689 : #if CHIP_CONFIG_ENABLE_ICD_SERVER && CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_ENABLE_ICD_CHECK_IN_ON_REPORT_TIMEOUT
690 : void ICDManager::OnSendCheckIn(const Access::SubjectDescriptor & subject)
691 : {
692 : ChipLogProgress(AppServer, "Received OnSendCheckIn for subject: " ChipLogFormatX64, ChipLogValueX64(subject.subject));
693 : SendCheckInMsgs(MakeOptional(subject));
694 : }
695 : #endif // CHIP_CONFIG_ENABLE_ICD_SERVER && CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_ENABLE_ICD_CHECK_IN_ON_REPORT_TIMEOUT
696 :
697 8 : void ICDManager::ExtendActiveMode(Milliseconds16 extendDuration)
698 : {
699 8 : TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ExtendTimerTo(extendDuration, OnActiveModeDone, this);
700 :
701 8 : Milliseconds32 activeModeJitterThreshold = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
702 8 : activeModeJitterThreshold = (extendDuration >= activeModeJitterThreshold) ? extendDuration - activeModeJitterThreshold : kZero;
703 :
704 8 : if (!mTransitionToIdleCalled)
705 : {
706 1 : TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ExtendTimerTo(activeModeJitterThreshold, OnTransitionToIdle, this);
707 : }
708 8 : }
709 :
710 2 : CHIP_ERROR ICDManager::HandleEventTrigger(uint64_t eventTrigger)
711 : {
712 2 : eventTrigger = clearEndpointInEventTrigger(eventTrigger);
713 2 : ICDTestEventTriggerEvent trigger = static_cast<ICDTestEventTriggerEvent>(eventTrigger);
714 2 : CHIP_ERROR err = CHIP_NO_ERROR;
715 :
716 2 : switch (trigger)
717 : {
718 1 : case ICDTestEventTriggerEvent::kAddActiveModeReq:
719 1 : SetKeepActiveModeRequirements(KeepActiveFlag::kTestEventTriggerActiveMode, true);
720 1 : break;
721 1 : case ICDTestEventTriggerEvent::kRemoveActiveModeReq:
722 1 : SetKeepActiveModeRequirements(KeepActiveFlag::kTestEventTriggerActiveMode, false);
723 1 : break;
724 : #if CHIP_CONFIG_ENABLE_ICD_CIP
725 : case ICDTestEventTriggerEvent::kInvalidateHalfCounterValues:
726 : err = ICDConfigurationData::GetInstance().GetICDCounter().InvalidateHalfCheckInCounterValues();
727 : break;
728 : case ICDTestEventTriggerEvent::kInvalidateAllCounterValues:
729 : err = ICDConfigurationData::GetInstance().GetICDCounter().InvalidateAllCheckInCounterValues();
730 : break;
731 : case ICDTestEventTriggerEvent::kForceMaximumCheckInBackOffState:
732 : err = mICDCheckInBackOffStrategy->ForceMaximumCheckInBackoff();
733 : break;
734 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
735 : #if CHIP_CONFIG_ENABLE_ICD_DSLS
736 : case ICDTestEventTriggerEvent::kDSLSForceSitMode:
737 : OnSITModeRequest();
738 : break;
739 : case ICDTestEventTriggerEvent::kDSLSWithdrawSitMode:
740 : OnSITModeRequestWithdrawal();
741 : break;
742 : #endif // CHIP_CONFIG_ENABLE_ICD_DSLS
743 0 : default:
744 0 : err = CHIP_ERROR_INVALID_ARGUMENT;
745 0 : break;
746 : }
747 :
748 2 : return err;
749 : }
750 :
751 10 : ICDManager::ObserverPointer * ICDManager::RegisterObserver(ICDStateObserver * observer)
752 : {
753 10 : return mStateObserverPool.CreateObject(observer);
754 : }
755 :
756 0 : void ICDManager::ReleaseObserver(ICDStateObserver * observer)
757 : {
758 0 : mStateObserverPool.ForEachActiveObject([this, observer](ObserverPointer * obs) {
759 0 : if (obs->mObserver == observer)
760 : {
761 0 : mStateObserverPool.ReleaseObject(obs);
762 0 : return Loop::Break;
763 : }
764 0 : return Loop::Continue;
765 : });
766 0 : }
767 :
768 54 : void ICDManager::postObserverEvent(ObserverEventType event)
769 : {
770 54 : mStateObserverPool.ForEachActiveObject([event](ObserverPointer * obs) {
771 54 : switch (event)
772 : {
773 16 : case ObserverEventType::EnterActiveMode: {
774 16 : obs->mObserver->OnEnterActiveMode();
775 16 : return Loop::Continue;
776 : }
777 23 : case ObserverEventType::EnterIdleMode: {
778 23 : obs->mObserver->OnEnterIdleMode();
779 23 : return Loop::Continue;
780 : }
781 15 : case ObserverEventType::TransitionToIdle: {
782 15 : obs->mObserver->OnTransitionToIdle();
783 15 : return Loop::Continue;
784 : }
785 0 : case ObserverEventType::ICDModeChange: {
786 0 : obs->mObserver->OnICDModeChange();
787 0 : return Loop::Continue;
788 : }
789 0 : default: {
790 0 : ChipLogError(DeviceLayer, "Invalid ICD Observer event type");
791 0 : return Loop::Break;
792 : }
793 : }
794 : });
795 54 : }
796 :
797 : } // namespace app
798 : } // namespace chip
|