blob: 65aca79acda778dcdecd174eec6b0f68741fa360 [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/ICDManagementServer.h>
#include <app/icd/ICDManager.h>
#include <app/icd/ICDMonitoringTable.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>
#include <stdlib.h>
#ifndef ICD_ENFORCE_SIT_SLOW_POLL_LIMIT
// Set to 1 to enforce SIT Slow Polling Max value to 15seconds (spec 9.16.1.5)
#define ICD_ENFORCE_SIT_SLOW_POLL_LIMIT 0
#endif
namespace chip {
namespace app {
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::IcdManagement;
uint8_t ICDManager::OpenExchangeContextCount = 0;
static_assert(UINT8_MAX >= CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS,
"ICDManager::OpenExchangeContextCount cannot hold count for the max exchange count");
void ICDManager::Init(PersistentStorageDelegate * storage, FabricTable * fabricTable, Crypto::SymmetricKeystore * symmetricKeystore)
{
VerifyOrDie(storage != nullptr);
VerifyOrDie(fabricTable != nullptr);
VerifyOrDie(symmetricKeystore != nullptr);
bool supportLIT = SupportsFeature(Feature::kLongIdleTimeSupport);
VerifyOrDieWithMsg((supportLIT == false) || SupportsFeature(Feature::kCheckInProtocolSupport), AppServer,
"The CheckIn protocol feature is required for LIT support");
VerifyOrDieWithMsg((supportLIT == false) || SupportsFeature(Feature::kUserActiveModeTrigger), AppServer,
"The user ActiveMode trigger feature is required for LIT support");
// Disabling check until LIT support is compelte
// VerifyOrDieWithMsg((supportLIT == false) && (GetSlowPollingInterval() <= GetSITPollingThreshold()) , AppServer,
// "LIT support is required for slow polling intervals superior to 15 seconds");
mStorage = storage;
mFabricTable = fabricTable;
VerifyOrDie(ICDNotifier::GetInstance().Subscribe(this) == CHIP_NO_ERROR);
mSymmetricKeystore = symmetricKeystore;
ICDManagementServer::GetInstance().SetSymmetricKeystore(mSymmetricKeystore);
// Removing the check for now since it is possible for the Fast polling
// to be larger than the ActiveModeDuration for now
// uint32_t activeModeDuration = ICDManagementServer::GetInstance().GetActiveModeDurationMs();
// VerifyOrDie(kFastPollingInterval.count() < activeModeDuration);
UpdateICDMode();
UpdateOperationState(OperationalState::ActiveMode);
}
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);
mICDMode = ICDMode::SIT;
mOperationalState = OperationalState::IdleMode;
mStorage = nullptr;
mFabricTable = nullptr;
mStateObserverPool.ReleaseAll();
}
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) == EMBER_ZCL_STATUS_SUCCESS);
return success ? ((featureMap & to_underlying(feature)) != 0) : false;
#else
return ((mFeatureMap & to_underlying(feature)) != 0);
#endif // !CONFIG_BUILD_FOR_HOST_UNIT_TEST
}
void ICDManager::UpdateICDMode()
{
assertChipStackLockedByCurrentThread();
ICDMode tempMode = ICDMode::SIT;
// 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 = ICDMode::LIT;
break;
}
}
}
if (mICDMode != tempMode)
{
mICDMode = tempMode;
postObserverEvent(ObserverEventType::ICDModeChange);
}
// When in SIT mode, the slow poll interval SHOULDN'T be greater than the SIT mode polling threshold, per spec.
if (mICDMode == ICDMode::SIT && GetSlowPollingInterval() > GetSITPollingThreshold())
{
ChipLogDetail(AppServer, "The Slow Polling Interval of an ICD in SIT mode should be <= %" PRIu32 " seconds",
(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;
// When the active mode interval is 0, we stay in idleMode until a notification brings the icd into active mode
if (ICDManagementServer::GetInstance().GetActiveModeDurationMs() > 0)
{
uint32_t idleModeDuration = ICDManagementServer::GetInstance().GetIdleModeDurationSec();
DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(idleModeDuration), OnIdleModeDone, this);
}
System::Clock::Milliseconds32 slowPollInterval = GetSlowPollingInterval();
#if ICD_ENFORCE_SIT_SLOW_POLL_LIMIT
// When in SIT mode, the slow poll interval SHOULDN'T be greater than the SIT mode polling threshold, per spec.
if (mICDMode == ICDMode::SIT && GetSlowPollingInterval() > GetSITPollingThreshold())
{
slowPollInterval = GetSITPollingThreshold();
}
#endif
CHIP_ERROR err = DeviceLayer::ConnectivityMgr().SetPollingInterval(slowPollInterval);
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to set Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
}
}
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;
uint32_t activeModeDuration = ICDManagementServer::GetInstance().GetActiveModeDurationMs();
if (activeModeDuration == 0 && !mKeepActiveFlags.HasAny())
{
// A Network Activity triggered the active mode and activeModeDuration is 0.
// Stay active for at least Active Mode Threshold.
activeModeDuration = ICDManagementServer::GetInstance().GetActiveModeThresholdMs();
}
DeviceLayer::SystemLayer().StartTimer(System::Clock::Timeout(activeModeDuration), OnActiveModeDone, this);
uint32_t activeModeJitterInterval =
(activeModeDuration >= ICD_ACTIVE_TIME_JITTER_MS) ? activeModeDuration - ICD_ACTIVE_TIME_JITTER_MS : 0;
DeviceLayer::SystemLayer().StartTimer(System::Clock::Timeout(activeModeJitterInterval), OnTransitionToIdle, this);
CHIP_ERROR err = DeviceLayer::ConnectivityMgr().SetPollingInterval(GetFastPollingInterval());
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to set Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
}
postObserverEvent(ObserverEventType::EnterActiveMode);
}
else
{
uint16_t activeModeThreshold = ICDManagementServer::GetInstance().GetActiveModeThresholdMs();
DeviceLayer::SystemLayer().ExtendTimerTo(System::Clock::Timeout(activeModeThreshold), OnActiveModeDone, this);
uint16_t activeModeJitterThreshold =
(activeModeThreshold >= ICD_ACTIVE_TIME_JITTER_MS) ? activeModeThreshold - ICD_ACTIVE_TIME_JITTER_MS : 0;
if (!mTransitionToIdleCalled)
{
DeviceLayer::SystemLayer().ExtendTimerTo(System::Clock::Timeout(activeModeJitterThreshold), OnTransitionToIdle,
this);
}
}
}
}
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);
// We only reset this flag when idle mode is complete to avoid re-triggering the check when an event brings us back to active,
// which could cause a loop.
pICDManager->mTransitionToIdleCalled = false;
}
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();
if (request == KeepActiveFlags::kExchangeContextOpen)
{
// There can be multiple open exchange contexts at the same time.
// Keep track of the requests count.
this->OpenExchangeContextCount++;
this->SetKeepActiveModeRequirements(request, true /* state */);
}
else /* !kExchangeContextOpen */
{
// Only 1 request per type (kCommissioningWindowOpen, kFailSafeArmed)
// set requirement directly
this->SetKeepActiveModeRequirements(request, true /* state */);
}
}
void ICDManager::OnActiveRequestWithdrawal(KeepActiveFlags request)
{
assertChipStackLockedByCurrentThread();
if (request == KeepActiveFlags::kExchangeContextOpen)
{
// There can be multiple open exchange contexts at the same time.
// Keep track of the requests count.
if (this->OpenExchangeContextCount > 0)
{
this->OpenExchangeContextCount--;
}
else
{
ChipLogError(DeviceLayer, "The ICD Manager did not account for ExchangeContext closure");
}
if (this->OpenExchangeContextCount == 0)
{
this->SetKeepActiveModeRequirements(request, false /* state */);
}
}
else /* !kExchangeContextOpen */
{
// 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;
case ICDManagementEvents::kStayActiveRequestReceived:
// TODO : Implement the StayActiveRequest
// https://github.com/project-chip/connectedhomeip/issues/24259
break;
default:
break;
}
}
System::Clock::Milliseconds32 ICDManager::GetSlowPollingInterval()
{
#if ICD_ENFORCE_SIT_SLOW_POLL_LIMIT
// When in SIT mode, the slow poll interval SHOULDN'T be greater than the SIT mode polling threshold, per spec.
// This is important for ICD device configured for LIT operation but currently operating as a SIT
// due to a lack of client registration
if (mICDMode == ICDMode::SIT && GetSlowPollingInterval() > GetSITPollingThreshold())
{
return GetSITPollingThreshold();
}
#endif
return kSlowPollingInterval;
}
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::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