blob: 2b4ccb2be8af4e8b22dd58cab3c7fc2207bbfa68 [file] [log] [blame]
/*
*
* Copyright (c) 2023 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/icd/server/ICDConfigurationData.h>
#include <app/icd/server/ICDManager.h>
#include <app/icd/server/ICDServerConfig.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/ConnectivityManager.h>
#include <platform/LockTracker.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
namespace {
enum class ICDTestEventTriggerEvent : uint64_t
{
kAddActiveModeReq = 0x0046'0000'00000001,
kRemoveActiveModeReq = 0x0046'0000'00000002,
kInvalidateHalfCounterValues = 0x0046'0000'00000003,
kInvalidateAllCounterValues = 0x0046'0000'00000004,
};
} // namespace
namespace chip {
namespace app {
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::IcdManagement;
using namespace System::Clock;
using chip::Protocols::InteractionModel::Status;
static_assert(UINT8_MAX >= CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS,
"ICDManager::mOpenExchangeContextCount cannot hold count for the max exchange count");
void ICDManager::Init(PersistentStorageDelegate * storage, FabricTable * fabricTable, Crypto::SymmetricKeystore * symmetricKeystore,
Messaging::ExchangeManager * exchangeManager, SubscriptionsInfoProvider * subInfoProvider)
{
#if CHIP_CONFIG_ENABLE_ICD_CIP
VerifyOrDie(storage != nullptr);
VerifyOrDie(fabricTable != nullptr);
VerifyOrDie(symmetricKeystore != nullptr);
VerifyOrDie(exchangeManager != nullptr);
VerifyOrDie(subInfoProvider != nullptr);
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
#if CHIP_CONFIG_ENABLE_ICD_LIT
// LIT ICD Verification Checks
if (SupportsFeature(Feature::kLongIdleTimeSupport))
{
VerifyOrDieWithMsg(SupportsFeature(Feature::kCheckInProtocolSupport), AppServer,
"The CheckIn protocol feature is required for LIT support.");
VerifyOrDieWithMsg(SupportsFeature(Feature::kUserActiveModeTrigger), AppServer,
"The user ActiveMode trigger feature is required for LIT support.");
VerifyOrDieWithMsg(ICDConfigurationData::GetInstance().GetMinLitActiveModeThreshold() <=
ICDConfigurationData::GetInstance().GetActiveModeThreshold(),
AppServer, "The minimum ActiveModeThreshold value for a LIT ICD is 5 seconds.");
// Disabling check until LIT support is compelte
// VerifyOrDieWithMsg((GetSlowPollingInterval() <= GetSITPollingThreshold()) , AppServer,
// "LIT support is required for slow polling intervals superior to 15 seconds");
}
#endif // CHIP_CONFIG_ENABLE_ICD_LIT
VerifyOrDie(ICDNotifier::GetInstance().Subscribe(this) == CHIP_NO_ERROR);
#if CHIP_CONFIG_ENABLE_ICD_CIP
mStorage = storage;
mFabricTable = fabricTable;
mSymmetricKeystore = symmetricKeystore;
mExchangeManager = exchangeManager;
mSubInfoProvider = subInfoProvider;
VerifyOrDie(ICDConfigurationData::GetInstance().GetICDCounter().Init(mStorage, DefaultStorageKeyAllocator::ICDCheckInCounter(),
ICDConfigurationData::kICDCounterPersistenceIncrement) ==
CHIP_NO_ERROR);
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
UpdateICDMode();
UpdateOperationState(OperationalState::IdleMode);
}
void ICDManager::Shutdown()
{
ICDNotifier::GetInstance().Unsubscribe(this);
// cancel any running timer of the icd
DeviceLayer::SystemLayer().CancelTimer(OnIdleModeDone, this);
DeviceLayer::SystemLayer().CancelTimer(OnActiveModeDone, this);
DeviceLayer::SystemLayer().CancelTimer(OnTransitionToIdle, this);
ICDConfigurationData::GetInstance().SetICDMode(ICDConfigurationData::ICDMode::SIT);
mOperationalState = OperationalState::ActiveMode;
mStateObserverPool.ReleaseAll();
#if CHIP_CONFIG_ENABLE_ICD_CIP
mStorage = nullptr;
mFabricTable = nullptr;
mSubInfoProvider = nullptr;
mICDSenderPool.ReleaseAll();
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
mIsBootUpResumeSubscriptionExecuted = false;
#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
}
bool ICDManager::SupportsFeature(Feature feature)
{
// Can't use attribute accessors/Attributes::FeatureMap::Get in unit tests
#if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
uint32_t featureMap = 0;
bool success = (Attributes::FeatureMap::Get(kRootEndpointId, &featureMap) == Status::Success);
return success ? ((featureMap & to_underlying(feature)) != 0) : false;
#else
return ((mFeatureMap & to_underlying(feature)) != 0);
#endif // !CONFIG_BUILD_FOR_HOST_UNIT_TEST
}
uint32_t ICDManager::StayActiveRequest(uint32_t stayActiveDuration)
{
// This should only be called when the device is in ActiveMode
VerifyOrReturnValue(mOperationalState == OperationalState::ActiveMode, 0);
uint32_t promisedActiveDuration =
std::min(ICDConfigurationData::GetInstance().GetGuaranteedStayActiveDuration().count(), stayActiveDuration);
// If the device is already in ActiveMode, we need to extend the active mode duration
// for whichever is smallest between 30000 milliseconds and stayActiveDuration, taking in account the remaining active time.
ExtendActiveMode(System::Clock::Milliseconds16(promisedActiveDuration));
promisedActiveDuration = DeviceLayer::SystemLayer().GetRemainingTime(OnActiveModeDone, this).count();
return promisedActiveDuration;
}
#if CHIP_CONFIG_ENABLE_ICD_CIP
void ICDManager::SendCheckInMsgs()
{
#if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
VerifyOrDie(mStorage != nullptr);
VerifyOrDie(mFabricTable != nullptr);
uint32_t counterValue = ICDConfigurationData::GetInstance().GetICDCounter().GetNextCheckInCounterValue();
bool counterIncremented = false;
for (const auto & fabricInfo : *mFabricTable)
{
uint16_t supported_clients = ICDConfigurationData::GetInstance().GetClientsSupportedPerFabric();
ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), supported_clients /*Table entry limit*/,
mSymmetricKeystore);
if (table.IsEmpty())
{
continue;
}
for (uint16_t i = 0; i < table.Limit(); i++)
{
ICDMonitoringEntry entry(mSymmetricKeystore);
CHIP_ERROR err = table.Get(i, entry);
if (err == CHIP_ERROR_NOT_FOUND)
{
break;
}
if (err != CHIP_NO_ERROR)
{
// Try to fetch the next entry upon failure (should not happen).
ChipLogError(AppServer, "Failed to retrieved ICDMonitoring entry for Check-In msg, will try next entry.");
continue;
}
if (!ShouldCheckInMsgsBeSentAtActiveModeFunction(entry.fabricIndex, entry.monitoredSubject))
{
continue;
}
// Increment counter only once to prevent depletion of the available range.
if (!counterIncremented)
{
counterIncremented = true;
if (CHIP_NO_ERROR != ICDConfigurationData::GetInstance().GetICDCounter().Advance())
{
ChipLogError(AppServer, "Incremented ICDCounter but failed to access/save to Persistent storage");
}
}
// SenderPool will be released upon transition from active to idle state
// This will happen when all ICD Check-In messages are sent on the network
ICDCheckInSender * sender = mICDSenderPool.CreateObject(mExchangeManager);
VerifyOrReturn(sender != nullptr, ChipLogError(AppServer, "Failed to allocate ICDCheckinSender"));
if (CHIP_NO_ERROR != sender->RequestResolve(entry, mFabricTable, counterValue))
{
ChipLogError(AppServer, "Failed to send ICD Check-In");
}
}
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
}
bool ICDManager::CheckInMessagesWouldBeSent(const std::function<ShouldCheckInMsgsBeSentFunction> & shouldCheckInMsgsBeSentFunction)
{
VerifyOrReturnValue(shouldCheckInMsgsBeSentFunction, false);
for (const auto & fabricInfo : *mFabricTable)
{
uint16_t supported_clients = ICDConfigurationData::GetInstance().GetClientsSupportedPerFabric();
ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), supported_clients /*Table entry limit*/,
mSymmetricKeystore);
if (table.IsEmpty())
{
continue;
}
for (uint16_t i = 0; i < table.Limit(); i++)
{
ICDMonitoringEntry entry(mSymmetricKeystore);
CHIP_ERROR err = table.Get(i, entry);
if (err == CHIP_ERROR_NOT_FOUND)
{
break;
}
if (err != CHIP_NO_ERROR)
{
// Try to fetch the next entry upon failure (should not happen).
ChipLogError(AppServer, "Failed to retrieved ICDMonitoring entry, will try next entry.");
continue;
}
// At least one registration would require a Check-In message
VerifyOrReturnValue(!shouldCheckInMsgsBeSentFunction(entry.fabricIndex, entry.monitoredSubject), true);
}
}
// None of the registrations would require a Check-In message
return false;
}
/**
* ShouldCheckInMsgsBeSentAtActiveModeFunction is used to determine if a Check-In message is required for a given registration.
* Due to how the ICD Check-In use-case interacts with the persistent subscription and subscription timeout resumption features,
* having a single implementation of the function renders the implementation very difficult to understand and maintain.
* Because of this, each valid feature combination has its own implementation of the function.
*/
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
/**
* @brief Implementation for when the persistent subscription and subscription timeout resumption feature are present.
* Function checks that there are no active or persisted subscriptions for a given fabricIndex or subjectID.
*
* @note When the persistent subscription and subscription timeout resumption feature are present, we need to check for
* persisted subscription at each transition to ActiveMode since there will be persisted subscriptions during normal
* operation for the subscription timeout resumption feature. Once we have finished all our subscription resumption attempts
* for a given subscription, the entry is deleted from persisted storage which will enable us to send Check-In messages for
* the client registration. This logic avoids the device sending a Check-In message while trying to resume subscriptions.
*
* @param aFabricIndex
* @param subjectID subjectID to check. Can be an operational node id or a CAT
*
* @return true Returns true if the fabricIndex and subjectId combination does not have an active or a persisted subscription.
* @return false Returns false if the fabricIndex and subjectId combination has an active or persisted subscription.
*/
bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID)
{
return !(mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID) ||
mSubInfoProvider->SubjectHasPersistedSubscription(aFabricIndex, subjectID));
}
#else
/**
* @brief Implementation for when the persistent subscription feature is present without the subscription timeout resumption
* feature. Function checks that there are no active subscriptions. If the boot up subscription resumption has not been completed,
* function also checks if there are persisted subscriptions.
*
* @note The persistent subscriptions feature tries to resume subscriptions at the highest min interval
* of all the persisted subscriptions. As such, it is possible for the ICD to return to Idle Mode
* until the timer elaspses. We do not want to send Check-In messages to clients with persisted subscriptions
* until we have tried to resubscribe.
*
* @param aFabricIndex
* @param subjectID subjectID to check. Can be an opperationnal node id or a CAT
*
* @return true Returns true if the fabricIndex and subjectId combination does not have an active subscription.
* If the boot up subscription resumption has not been completed, there must not be a persisted subscription either.
* @return false Returns false if the fabricIndex and subjectId combination has an active subscription.
* If the boot up subscription resumption has not been completed,
* returns false if the fabricIndex and subjectId combination has a persisted subscription.
*/
bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID)
{
bool mightHaveSubscription = mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID);
if (!mightHaveSubscription && !mIsBootUpResumeSubscriptionExecuted)
{
mightHaveSubscription = mSubInfoProvider->SubjectHasPersistedSubscription(aFabricIndex, subjectID);
}
return !mightHaveSubscription;
}
#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
#else
/**
* @brief Implementation for when neither the persistent subscription nor the subscription timeout resumption features are present.
* Function checks that there no active sbuscriptions for a given fabricIndex and subjectId combination.
*
* @note When neither the persistent subscription nor the subscription timeout resumption features are present, we only need to
* check for active subscription since we will never have any persisted subscription.
*
* @param aFabricIndex
* @param subjectID subjectID to check. Can be an opperationnal node id or a CAT
*
* @return true Returns true if the fabricIndex and subjectId combination does not have an active subscription.
* @return false Returns false if the fabricIndex and subjectId combination has an active subscription.
*/
bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID)
{
return !(mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID));
}
#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
void ICDManager::TriggerCheckInMessages(const std::function<ShouldCheckInMsgsBeSentFunction> & verifier)
{
VerifyOrReturn(SupportsFeature(Feature::kCheckInProtocolSupport));
// Only trigger Check-In messages when we are in IdleMode.
// If we are already in ActiveMode, Check-In messages have already been sent.
VerifyOrReturn(mOperationalState == OperationalState::IdleMode);
// If we don't have any Check-In messages to send, do nothing
VerifyOrReturn(CheckInMessagesWouldBeSent(verifier));
UpdateOperationState(OperationalState::ActiveMode);
}
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
void ICDManager::UpdateICDMode()
{
assertChipStackLockedByCurrentThread();
ICDConfigurationData::ICDMode tempMode = ICDConfigurationData::ICDMode::SIT;
#if CHIP_CONFIG_ENABLE_ICD_LIT
// Device can only switch to the LIT operating mode if LIT support is present
if (SupportsFeature(Feature::kLongIdleTimeSupport))
{
VerifyOrDie(mStorage != nullptr);
VerifyOrDie(mFabricTable != nullptr);
// We can only get to LIT Mode, if at least one client is registered with the ICD device
for (const auto & fabricInfo : *mFabricTable)
{
// We only need 1 valid entry to ensure LIT compliance
ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), 1 /*Table entry limit*/, mSymmetricKeystore);
if (!table.IsEmpty())
{
tempMode = ICDConfigurationData::ICDMode::LIT;
break;
}
}
}
#endif // CHIP_CONFIG_ENABLE_ICD_LIT
if (ICDConfigurationData::GetInstance().GetICDMode() != tempMode)
{
ICDConfigurationData::GetInstance().SetICDMode(tempMode);
// Can't use attribute accessors/Attributes::OperatingMode::Set in unit tests
#if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
Attributes::OperatingMode::Set(kRootEndpointId, static_cast<OperatingModeEnum>(tempMode));
#endif
postObserverEvent(ObserverEventType::ICDModeChange);
}
// When in SIT mode, the slow poll interval SHOULDN'T be greater than the SIT mode polling threshold, per spec.
if (ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::SIT &&
ICDConfigurationData::GetInstance().GetSlowPollingInterval() > ICDConfigurationData::GetInstance().GetSITPollingThreshold())
{
ChipLogDetail(AppServer, "The Slow Polling Interval of an ICD in SIT mode should be <= %" PRIu32 " seconds",
(ICDConfigurationData::GetInstance().GetSITPollingThreshold().count() / 1000));
}
}
void ICDManager::UpdateOperationState(OperationalState state)
{
assertChipStackLockedByCurrentThread();
// Active mode can be re-triggered.
VerifyOrReturn(mOperationalState != state || state == OperationalState::ActiveMode);
if (state == OperationalState::IdleMode)
{
mOperationalState = OperationalState::IdleMode;
#if CHIP_CONFIG_ENABLE_ICD_CIP
std::function<ShouldCheckInMsgsBeSentFunction> sendCheckInMessagesOnActiveMode =
std::bind(&ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction, this, std::placeholders::_1, std::placeholders::_2);
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
// When the active mode interval is 0, we stay in idleMode until a notification brings the icd into active mode
// unless the device would need to send Check-In messages
if (ICDConfigurationData::GetInstance().GetActiveModeDuration() > kZero
#if CHIP_CONFIG_ENABLE_ICD_CIP
|| CheckInMessagesWouldBeSent(sendCheckInMessagesOnActiveMode)
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
)
{
DeviceLayer::SystemLayer().StartTimer(ICDConfigurationData::GetInstance().GetIdleModeDuration(), OnIdleModeDone, this);
}
Milliseconds32 slowPollInterval = ICDConfigurationData::GetInstance().GetSlowPollingInterval();
#if CHIP_CONFIG_ENABLE_ICD_CIP
// Going back to Idle, all Check-In messages are sent
mICDSenderPool.ReleaseAll();
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
CHIP_ERROR err = DeviceLayer::ConnectivityMgr().SetPollingInterval(slowPollInterval);
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to set Slow Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
}
postObserverEvent(ObserverEventType::EnterIdleMode);
}
else if (state == OperationalState::ActiveMode)
{
if (mOperationalState == OperationalState::IdleMode)
{
// An event could have brought us to the active mode.
// Make sure the idle mode timer is stopped
DeviceLayer::SystemLayer().CancelTimer(OnIdleModeDone, this);
mOperationalState = OperationalState::ActiveMode;
Milliseconds32 activeModeDuration = ICDConfigurationData::GetInstance().GetActiveModeDuration();
if (activeModeDuration == kZero && !mKeepActiveFlags.HasAny())
{
// Network Activity triggered the active mode and activeModeDuration is 0.
// Stay active for at least Active Mode Threshold.
activeModeDuration = ICDConfigurationData::GetInstance().GetActiveModeThreshold();
}
DeviceLayer::SystemLayer().StartTimer(activeModeDuration, OnActiveModeDone, this);
Milliseconds32 activeModeJitterInterval = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
// TODO(#33074): Edge case when we transition to IdleMode with this condition being true
// (activeModeDuration == kZero && !mKeepActiveFlags.HasAny())
activeModeJitterInterval =
(activeModeDuration >= activeModeJitterInterval) ? activeModeDuration - activeModeJitterInterval : kZero;
// Reset this flag when we enter ActiveMode to avoid having a feedback loop that keeps us indefinitly in
// ActiveMode.
mTransitionToIdleCalled = false;
DeviceLayer::SystemLayer().StartTimer(activeModeJitterInterval, OnTransitionToIdle, this);
CHIP_ERROR err =
DeviceLayer::ConnectivityMgr().SetPollingInterval(ICDConfigurationData::GetInstance().GetFastPollingInterval());
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to set Fast Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
}
#if CHIP_CONFIG_ENABLE_ICD_CIP
if (SupportsFeature(Feature::kCheckInProtocolSupport))
{
SendCheckInMsgs();
}
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
postObserverEvent(ObserverEventType::EnterActiveMode);
}
else
{
ExtendActiveMode(ICDConfigurationData::GetInstance().GetActiveModeThreshold());
}
}
}
void ICDManager::SetKeepActiveModeRequirements(KeepActiveFlags flag, bool state)
{
assertChipStackLockedByCurrentThread();
mKeepActiveFlags.Set(flag, state);
if (mOperationalState == OperationalState::IdleMode && mKeepActiveFlags.HasAny())
{
UpdateOperationState(OperationalState::ActiveMode);
}
else if (mOperationalState == OperationalState::ActiveMode && !mKeepActiveFlags.HasAny() &&
!DeviceLayer::SystemLayer().IsTimerActive(OnActiveModeDone, this))
{
// The normal active period had ended and nothing else requires the system to be active.
UpdateOperationState(OperationalState::IdleMode);
}
}
void ICDManager::OnIdleModeDone(System::Layer * aLayer, void * appState)
{
ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
pICDManager->UpdateOperationState(OperationalState::ActiveMode);
}
void ICDManager::OnActiveModeDone(System::Layer * aLayer, void * appState)
{
ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
// Don't go to idle mode when we have a keep active requirement
if (!pICDManager->mKeepActiveFlags.HasAny())
{
pICDManager->UpdateOperationState(OperationalState::IdleMode);
}
}
void ICDManager::OnTransitionToIdle(System::Layer * aLayer, void * appState)
{
ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
// OnTransitionToIdle will trigger a report message if reporting is needed, which should extend the active mode until the
// ack for the report is received.
pICDManager->mTransitionToIdleCalled = true;
pICDManager->postObserverEvent(ObserverEventType::TransitionToIdle);
}
/* ICDListener functions. */
void ICDManager::OnKeepActiveRequest(KeepActiveFlags request)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturn(request < KeepActiveFlagsValues::kInvalidFlag);
if (request.Has(KeepActiveFlag::kExchangeContextOpen))
{
// There can be multiple open exchange contexts at the same time.
// Keep track of the requests count.
this->mOpenExchangeContextCount++;
}
#if CHIP_CONFIG_ENABLE_ICD_CIP
if (request.Has(KeepActiveFlag::kCheckInInProgress))
{
// There can be multiple check-in at the same time.
// Keep track of the requests count.
this->mCheckInRequestCount++;
}
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
this->SetKeepActiveModeRequirements(request, true /* state */);
}
void ICDManager::OnActiveRequestWithdrawal(KeepActiveFlags request)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturn(request < KeepActiveFlagsValues::kInvalidFlag);
if (request.Has(KeepActiveFlag::kExchangeContextOpen))
{
// There can be multiple open exchange contexts at the same time.
// Keep track of the requests count.
if (this->mOpenExchangeContextCount > 0)
{
this->mOpenExchangeContextCount--;
}
else
{
ChipLogError(DeviceLayer, "The ICD Manager did not account for ExchangeContext closure");
}
if (this->mOpenExchangeContextCount == 0)
{
this->SetKeepActiveModeRequirements(KeepActiveFlag::kExchangeContextOpen, false /* state */);
}
}
#if CHIP_CONFIG_ENABLE_ICD_CIP
if (request.Has(KeepActiveFlag::kCheckInInProgress))
{
// There can be multiple open exchange contexts at the same time.
// Keep track of the requests count.
if (this->mCheckInRequestCount > 0)
{
this->mCheckInRequestCount--;
}
else
{
ChipLogError(DeviceLayer, "The ICD Manager did not account for Check-In Sender start");
}
if (this->mCheckInRequestCount == 0)
{
this->SetKeepActiveModeRequirements(KeepActiveFlag::kCheckInInProgress, false /* state */);
}
}
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
if (request.Has(KeepActiveFlag::kCommissioningWindowOpen) || request.Has(KeepActiveFlag::kFailSafeArmed))
{
// Only 1 request per type (kCommissioningWindowOpen, kFailSafeArmed)
// remove requirement directly
this->SetKeepActiveModeRequirements(request, false /* state */);
}
}
void ICDManager::OnNetworkActivity()
{
this->UpdateOperationState(OperationalState::ActiveMode);
}
void ICDManager::OnICDManagementServerEvent(ICDManagementEvents event)
{
switch (event)
{
case ICDManagementEvents::kTableUpdated:
this->UpdateICDMode();
break;
default:
break;
}
}
void ICDManager::OnSubscriptionReport()
{
// If the device is already in ActiveMode, that means that all active subscriptions have already been marked dirty.
// Since we only mark them dirty when we enter ActiveMode, it is not necessary to update the operational state a second time.
// Doing so will only add an ActiveModeThreshold to the active time which we don't want to do here.
VerifyOrReturn(mOperationalState == OperationalState::IdleMode);
this->UpdateOperationState(OperationalState::ActiveMode);
}
void ICDManager::ExtendActiveMode(Milliseconds16 extendDuration)
{
DeviceLayer::SystemLayer().ExtendTimerTo(extendDuration, OnActiveModeDone, this);
Milliseconds32 activeModeJitterThreshold = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
activeModeJitterThreshold = (extendDuration >= activeModeJitterThreshold) ? extendDuration - activeModeJitterThreshold : kZero;
if (!mTransitionToIdleCalled)
{
DeviceLayer::SystemLayer().ExtendTimerTo(activeModeJitterThreshold, OnTransitionToIdle, this);
}
}
CHIP_ERROR ICDManager::HandleEventTrigger(uint64_t eventTrigger)
{
ICDTestEventTriggerEvent trigger = static_cast<ICDTestEventTriggerEvent>(eventTrigger);
CHIP_ERROR err = CHIP_NO_ERROR;
switch (trigger)
{
case ICDTestEventTriggerEvent::kAddActiveModeReq:
SetKeepActiveModeRequirements(KeepActiveFlag::kTestEventTriggerActiveMode, true);
break;
case ICDTestEventTriggerEvent::kRemoveActiveModeReq:
SetKeepActiveModeRequirements(KeepActiveFlag::kTestEventTriggerActiveMode, false);
break;
#if CHIP_CONFIG_ENABLE_ICD_CIP
case ICDTestEventTriggerEvent::kInvalidateHalfCounterValues:
err = ICDConfigurationData::GetInstance().GetICDCounter().InvalidateHalfCheckInCounterValues();
break;
case ICDTestEventTriggerEvent::kInvalidateAllCounterValues:
err = ICDConfigurationData::GetInstance().GetICDCounter().InvalidateAllCheckInCounterValues();
break;
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
default:
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}
return err;
}
ICDManager::ObserverPointer * ICDManager::RegisterObserver(ICDStateObserver * observer)
{
return mStateObserverPool.CreateObject(observer);
}
void ICDManager::ReleaseObserver(ICDStateObserver * observer)
{
mStateObserverPool.ForEachActiveObject([this, observer](ObserverPointer * obs) {
if (obs->mObserver == observer)
{
mStateObserverPool.ReleaseObject(obs);
return Loop::Break;
}
return Loop::Continue;
});
}
void ICDManager::postObserverEvent(ObserverEventType event)
{
mStateObserverPool.ForEachActiveObject([event](ObserverPointer * obs) {
switch (event)
{
case ObserverEventType::EnterActiveMode: {
obs->mObserver->OnEnterActiveMode();
return Loop::Continue;
}
case ObserverEventType::EnterIdleMode: {
obs->mObserver->OnEnterIdleMode();
return Loop::Continue;
}
case ObserverEventType::TransitionToIdle: {
obs->mObserver->OnTransitionToIdle();
return Loop::Continue;
}
case ObserverEventType::ICDModeChange: {
obs->mObserver->OnICDModeChange();
return Loop::Continue;
}
default: {
ChipLogError(DeviceLayer, "Invalid ICD Observer event type");
return Loop::Break;
}
}
});
}
} // namespace app
} // namespace chip