blob: d79a36f14f045b8e6ce36eaa3c1b2dff030b8340 [file] [log] [blame]
/*
*
* Copyright (c) 2022 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.
*/
/**
* @file
* Provides the implementation of the FailSafeContext object.
*/
#include <lib/support/SafeInt.h>
#include <platform/CHIPDeviceConfig.h>
#include <platform/ConnectivityManager.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
#include "FailSafeContext.h"
using namespace chip::DeviceLayer;
namespace chip {
namespace app {
void FailSafeContext::HandleArmFailSafeTimer(System::Layer * layer, void * aAppState)
{
FailSafeContext * failSafeContext = reinterpret_cast<FailSafeContext *>(aAppState);
failSafeContext->FailSafeTimerExpired();
}
void FailSafeContext::HandleMaxCumulativeFailSafeTimer(System::Layer * layer, void * aAppState)
{
FailSafeContext * failSafeContext = reinterpret_cast<FailSafeContext *>(aAppState);
failSafeContext->FailSafeTimerExpired();
}
void FailSafeContext::HandleDisarmFailSafe(intptr_t arg)
{
FailSafeContext * failSafeContext = reinterpret_cast<FailSafeContext *>(arg);
failSafeContext->DisarmFailSafe();
}
void FailSafeContext::SetFailSafeArmed(bool armed)
{
#if CHIP_DEVICE_CONFIG_ENABLE_SED
if (IsFailSafeArmed() != armed)
{
// Per spec, we should be staying in active mode while a fail-safe is
// armed.
DeviceLayer::ConnectivityMgr().RequestSEDActiveMode(armed);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_SED
#if CHIP_CONFIG_ENABLE_ICD_SERVER
if (IsFailSafeArmed() != armed)
{
DeviceLayer::ChipDeviceEvent event;
event.Type = DeviceLayer::DeviceEventType::kFailSafeStateChanged;
event.FailSafeState.armed = armed;
CHIP_ERROR err = DeviceLayer::PlatformMgr().PostEvent(&event);
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to post kFailSafeStateChanged event %" CHIP_ERROR_FORMAT, err.Format());
}
}
#endif
mFailSafeArmed = armed;
}
void FailSafeContext::FailSafeTimerExpired()
{
if (!IsFailSafeArmed())
{
// In case this was a pending timer event in event loop, and we had
// done CommissioningComplete or manual disarm.
return;
}
ChipLogProgress(FailSafe, "Fail-safe timer expired");
ScheduleFailSafeCleanup(mFabricIndex, mAddNocCommandHasBeenInvoked, mUpdateNocCommandHasBeenInvoked);
}
void FailSafeContext::ScheduleFailSafeCleanup(FabricIndex fabricIndex, bool addNocCommandInvoked, bool updateNocCommandInvoked)
{
// Not armed, but busy so cannot rearm (via General Commissioning cluster) until the flushing
// via `HandleDisarmFailSafe` path is complete.
// TODO: This is hacky and we need to remove all this event pushing business, to keep all fail-safe logic-only.
mFailSafeBusy = true;
SetFailSafeArmed(false);
ChipDeviceEvent event;
event.Type = DeviceEventType::kFailSafeTimerExpired;
event.FailSafeTimerExpired.fabricIndex = fabricIndex;
event.FailSafeTimerExpired.addNocCommandHasBeenInvoked = addNocCommandInvoked;
event.FailSafeTimerExpired.updateNocCommandHasBeenInvoked = updateNocCommandInvoked;
CHIP_ERROR status = PlatformMgr().PostEvent(&event);
if (status != CHIP_NO_ERROR)
{
ChipLogError(FailSafe, "Failed to post fail-safe timer expired: %" CHIP_ERROR_FORMAT, status.Format());
}
PlatformMgr().ScheduleWork(HandleDisarmFailSafe, reinterpret_cast<intptr_t>(this));
}
CHIP_ERROR FailSafeContext::ArmFailSafe(FabricIndex accessingFabricIndex, System::Clock::Seconds16 expiryLengthSeconds)
{
VerifyOrReturnError(!IsFailSafeBusy(), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = CHIP_NO_ERROR;
bool cancelTimersIfError = false;
if (!IsFailSafeArmed())
{
System::Clock::Timeout maxCumulativeTimeout = System::Clock::Seconds32(CHIP_DEVICE_CONFIG_MAX_CUMULATIVE_FAILSAFE_SEC);
SuccessOrExit(err = DeviceLayer::SystemLayer().StartTimer(maxCumulativeTimeout, HandleMaxCumulativeFailSafeTimer, this));
cancelTimersIfError = true;
}
SuccessOrExit(
err = DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(expiryLengthSeconds), HandleArmFailSafeTimer, this));
SetFailSafeArmed(true);
mFabricIndex = accessingFabricIndex;
exit:
if (err != CHIP_NO_ERROR && cancelTimersIfError)
{
DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleMaxCumulativeFailSafeTimer, this);
}
return err;
}
void FailSafeContext::DisarmFailSafe()
{
DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleMaxCumulativeFailSafeTimer, this);
ResetState();
ChipLogProgress(FailSafe, "Fail-safe cleanly disarmed");
}
void FailSafeContext::ForceFailSafeTimerExpiry()
{
if (!IsFailSafeArmed())
{
return;
}
// Cancel the timer since we force its action
DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleMaxCumulativeFailSafeTimer, this);
FailSafeTimerExpired();
}
} // namespace app
} // namespace chip