* Copyright (c) 2026 Huawei Device Co., Ltd.
* 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 "backoff_retry_timer.h"
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include "iam_check.h"
#include "iam_logger.h"
#include "relative_timer.h"
#define LOG_TAG "CDA_SA"
#define LOG_FILE_ID LOG_FILE_BACKOFF_RETRY_TIMER
namespace OHOS {
namespace UserIam {
namespace CompanionDeviceAuth {
BackoffRetryTimer::BackoffRetryTimer(const Config &config, RetryCallback &&callback)
: callback_(std::move(callback)),
config_(config)
{
if (config_.baseDelayMs == 0) {
IAM_LOGE("%{public}s baseDelayMs is invalid, fallback to default %{public}u", config_.name.c_str(),
Config::DEFAULT_BASE_DELAY_MS);
config_.baseDelayMs = Config::DEFAULT_BASE_DELAY_MS;
}
if (config_.maxDelayMs < config_.baseDelayMs) {
IAM_LOGW("%{public}s maxDelayMs %{public}u < baseDelayMs %{public}u, clamped to baseDelayMs",
config_.name.c_str(), config_.maxDelayMs, config_.baseDelayMs);
config_.maxDelayMs = config_.baseDelayMs;
}
}
bool BackoffRetryTimer::OnFailure()
{
timerSubscription_.reset();
failureCount_ = (failureCount_ < UINT32_MAX) ? (failureCount_ + 1) : UINT32_MAX;
backoffStep_ = (backoffStep_ < UINT32_MAX) ? (backoffStep_ + 1) : UINT32_MAX;
if (failureCount_ > config_.maxRetryCount) {
IAM_LOGE("%{public}s retry exhausted after %{public}u attempts", config_.name.c_str(), failureCount_);
return false;
}
uint32_t delayMs = CalculateNextDelayMs(backoffStep_, config_);
IAM_LOGI("%{public}s failure recorded %{public}u times, scheduling retry in %{public}u ms", config_.name.c_str(),
failureCount_, delayMs);
timerSubscription_ = RelativeTimer::GetInstance().Register(
[callback = callback_, name = config_.name]() {
IAM_LOGI("%{public}s executing retry callback", name.c_str());
ENSURE_OR_RETURN(callback != nullptr);
callback();
},
delayMs);
return true;
}
void BackoffRetryTimer::ResetBackoff()
{
timerSubscription_.reset();
backoffStep_ = 0;
IAM_LOGI("%{public}s retry backoff reset, keep failure budget %{public}u", config_.name.c_str(), failureCount_);
}
void BackoffRetryTimer::Reset()
{
timerSubscription_.reset();
failureCount_ = 0;
backoffStep_ = 0;
IAM_LOGI("%{public}s retry timer reset", config_.name.c_str());
}
uint32_t BackoffRetryTimer::CalculateNextDelayMs(uint32_t failureCount, const Config &config)
{
constexpr uint32_t NUM_MAX_SHIFT_COUNT = 31;
if (failureCount <= 1) {
return config.baseDelayMs;
}
uint32_t shiftCount = failureCount - 1;
if (shiftCount > NUM_MAX_SHIFT_COUNT) {
return config.maxDelayMs;
}
uint64_t delayMs = config.baseDelayMs;
delayMs <<= shiftCount;
if (delayMs > config.maxDelayMs) {
return config.maxDelayMs;
}
return static_cast<uint32_t>(delayMs);
}
}
}
}