blob: 24640d24057f2090349e6840f3ef669986ab124b [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2016-2017 Nest Labs, Inc.
* All rights reserved.
*
* 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
*
* @brief
* Class declarations for a monotonically-increasing counter that is periodically
* saved to the provided storage.
*/
#pragma once
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>
#include <lib/support/CHIPCounter.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <limits>
namespace chip {
/**
* @class PersistedCounter
*
* @brief
* A class for managing a counter as an integer value intended to persist
* across reboots.
*
* Counter values are always set to start at a multiple of a bootup value
* "epoch".
*
* Example:
*
* - Assuming epoch is 100 via PersistedCounter::Init(_, 100) and GetValue +
* AdvanceValue is called, we get the following outputs:
*
* - Output: 0, 1, 2, 3, 4 <reboot/reinit>
* - Output: 100, 101, 102, 103, 104, 105 <reboot/reinit>
* - Output: 200, 201, 202, ...., 299, 300, 301, 302 <reboot/reinit>
* - Output: 400, 401 ...
*
*/
template <typename T>
class PersistedCounter : public MonotonicallyIncreasingCounter<T>
{
public:
PersistedCounter() : mKey(StorageKeyName::Uninitialized()) {}
~PersistedCounter() override {}
/**
* @brief
* Initialize a PersistedCounter object.
*
* @param[in] aStorage the storage to use for the counter values.
* @param[in] aKey the key to use for storing the counter values.
* @param[in] aEpoch On bootup, values we vend will start at a
* multiple of this parameter.
*
* @return CHIP_ERROR_INVALID_ARGUMENT if aStorageDelegate or aKey is NULL
* CHIP_ERROR_INVALID_INTEGER_VALUE if aEpoch is 0.
* CHIP_NO_ERROR otherwise
*/
CHIP_ERROR Init(PersistentStorageDelegate * aStorage, StorageKeyName aKey, T aEpoch)
{
VerifyOrReturnError(aStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(aKey.IsInitialized(), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(aEpoch > 0, CHIP_ERROR_INVALID_INTEGER_VALUE);
mStorage = aStorage;
mKey = aKey;
mEpoch = aEpoch;
T startValue;
// Read our previously-stored starting value.
ReturnErrorOnFailure(ReadStartValue(startValue));
#if CHIP_CONFIG_PERSISTED_COUNTER_DEBUG_LOGGING
if constexpr (std::is_same_v<decltype(startValue), uint64_t>)
{
ChipLogDetail(EventLogging, "PersistedCounter::Init() aEpoch 0x" ChipLogFormatX64 " startValue 0x" ChipLogFormatX64,
ChipLogValueX64(aEpoch), ChipLogValueX64(startValue));
}
else if (std::is_same_v<decltype(startValue), uint32_t>)
{
ChipLogDetail(EventLogging, "PersistedCounter::Init() aEpoch 0x%" PRIx32 " startValue 0x%" PRIx32,
static_cast<uint32_t>(aEpoch), static_cast<uint32_t>(startValue));
}
#endif
ReturnErrorOnFailure(PersistNextEpochStart(static_cast<T>(startValue + aEpoch)));
// This will set the starting value, after which we're ready.
return MonotonicallyIncreasingCounter<T>::Init(startValue);
}
/**
* @brief Increment the counter by N and write to persisted storage if we've completed the current epoch.
*
* @param value value of N
*
* @return Any error returned by a write to persisted storage.
*/
CHIP_ERROR AdvanceBy(T value) override
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mKey.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
// If value is 0, we do not need to do anything
VerifyOrReturnError(value > 0, CHIP_NO_ERROR);
// We should update the persisted epoch value if :
// 1- Sum of the current counter and value is greater or equal to the mNextEpoch.
// This is the standard operating case.
// 2- Increasing the current counter by value would cause a roll over. This would cause the current value to be < to the
// mNextEpoch so we force an update.
bool shouldDoEpochUpdate = ((MonotonicallyIncreasingCounter<T>::GetValue() + value) >= mNextEpoch) ||
(MonotonicallyIncreasingCounter<T>::GetValue() > std::numeric_limits<T>::max() - value);
ReturnErrorOnFailure(MonotonicallyIncreasingCounter<T>::AdvanceBy(value));
if (shouldDoEpochUpdate)
{
// Since AdvanceBy allows the counter to be increased by an arbitrary value, it is possible that the new counter value
// is greater than mNextEpoch + mEpoch. As such, we want the next Epoch value to be calculated from the new current
// value.
PersistAndVerifyNextEpochStart(MonotonicallyIncreasingCounter<T>::GetValue());
}
return CHIP_NO_ERROR;
}
/**
* @brief Increment the counter and write to persisted storage if we've completed the current epoch.
*
* @return Any error returned by a write to persisted storage.
*/
CHIP_ERROR Advance() override
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mKey.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(MonotonicallyIncreasingCounter<T>::Advance());
if (MonotonicallyIncreasingCounter<T>::GetValue() >= mNextEpoch)
{
ReturnErrorOnFailure(PersistAndVerifyNextEpochStart(mNextEpoch));
}
return CHIP_NO_ERROR;
}
private:
CHIP_ERROR PersistAndVerifyNextEpochStart(T refEpoch)
{
// Value advanced past the previously persisted "start point".
// Ensure that a new starting point is persisted.
ReturnErrorOnFailure(PersistNextEpochStart(static_cast<T>(refEpoch + mEpoch)));
// Advancing the epoch should have ensured that the current value is valid
VerifyOrReturnError(static_cast<T>(MonotonicallyIncreasingCounter<T>::GetValue() + mEpoch) == mNextEpoch,
CHIP_ERROR_INTERNAL);
// Previous check did not take into consideration that the counter value can be equal to the max counter value or
// rollover.
// TODO(#33175): PersistedCounter allows rollover so this check is incorrect. We need a Counter class that adequatly
// manages rollover behavior for counters that cannot rollover.
// VerifyOrReturnError(MonotonicallyIncreasingCounter<T>::GetValue() < mNextEpoch, CHIP_ERROR_INTERNAL);
return CHIP_NO_ERROR;
}
/**
* @brief
* Write out the counter value to persistent storage.
*
* @param[in] aStartValue The counter value to write out.
*
* @return Any error returned by a write to persistent storage.
*/
CHIP_ERROR
PersistNextEpochStart(T aStartValue)
{
mNextEpoch = aStartValue;
#if CHIP_CONFIG_PERSISTED_COUNTER_DEBUG_LOGGING
if constexpr (std::is_same_v<decltype(aStartValue), uint64_t>)
{
ChipLogDetail(EventLogging, "PersistedCounter::WriteStartValue() aStartValue 0x" ChipLogFormatX64,
ChipLogValueX64(aStartValue));
}
else
{
ChipLogDetail(EventLogging, "PersistedCounter::WriteStartValue() aStartValue 0x%" PRIx32,
static_cast<uint32_t>(aStartValue));
}
#endif
T valueLE = Encoding::LittleEndian::HostSwap<T>(aStartValue);
return mStorage->SyncSetKeyValue(mKey.KeyName(), &valueLE, sizeof(valueLE));
}
/**
* @brief
* Read our starting counter value (if we have one) in from persistent storage.
*
* @param[in,out] aStartValue The value read out.
*
* @return Any error returned by a read from persistent storage.
*/
CHIP_ERROR ReadStartValue(T & aStartValue)
{
T valueLE = GetInitialCounterValue();
uint16_t size = sizeof(valueLE);
VerifyOrReturnError(mKey.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = mStorage->SyncGetKeyValue(mKey.KeyName(), &valueLE, size);
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
// No previously-stored value, no worries, the counter is initialized to zero.
// Suppress the error.
err = CHIP_NO_ERROR;
}
else
{
// TODO: Figure out how to avoid a bootloop here. Maybe we should just
// init to 0? Or a random value?
ReturnErrorOnFailure(err);
}
if (size != sizeof(valueLE))
{
// TODO: Again, figure out whether this could lead to bootloops.
return CHIP_ERROR_INCORRECT_STATE;
}
aStartValue = Encoding::LittleEndian::HostSwap<T>(valueLE);
#if CHIP_CONFIG_PERSISTED_COUNTER_DEBUG_LOGGING
if constexpr (std::is_same_v<decltype(aStartValue), uint64_t>)
{
ChipLogDetail(EventLogging, "PersistedCounter::ReadStartValue() aStartValue 0x" ChipLogFormatX64,
ChipLogValueX64(aStartValue));
}
else
{
ChipLogDetail(EventLogging, "PersistedCounter::ReadStartValue() aStartValue 0x%" PRIx32,
static_cast<uint32_t>(aStartValue));
}
#endif
return CHIP_NO_ERROR;
}
/**
* @brief Get the Initial Counter Value
*
* By default, persisted counters start off at 0.
*/
virtual inline T GetInitialCounterValue() { return 0; }
PersistentStorageDelegate * mStorage = nullptr; // start value is stored here
StorageKeyName mKey;
T mEpoch = 0; // epoch modulus value
T mNextEpoch = 0; // next epoch start
};
} // namespace chip