Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 Project CHIP Authors
4 : * Copyright (c) 2016-2017 Nest Labs, Inc.
5 : * All rights reserved.
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : /**
21 : * @file
22 : *
23 : * @brief
24 : * Class declarations for a monotonically-increasing counter that is periodically
25 : * saved to the provided storage.
26 : */
27 :
28 : #pragma once
29 :
30 : #include <lib/core/CHIPEncoding.h>
31 : #include <lib/core/CHIPPersistentStorageDelegate.h>
32 : #include <lib/support/CHIPCounter.h>
33 : #include <lib/support/CodeUtils.h>
34 : #include <lib/support/DefaultStorageKeyAllocator.h>
35 : #include <limits>
36 :
37 : namespace chip {
38 :
39 : /**
40 : * @class PersistedCounter
41 : *
42 : * @brief
43 : * A class for managing a counter as an integer value intended to persist
44 : * across reboots.
45 : *
46 : * Counter values are always set to start at a multiple of a bootup value
47 : * "epoch".
48 : *
49 : * Example:
50 : *
51 : * - Assuming epoch is 100 via PersistedCounter::Init(_, 100) and GetValue +
52 : * AdvanceValue is called, we get the following outputs:
53 : *
54 : * - Output: 0, 1, 2, 3, 4 <reboot/reinit>
55 : * - Output: 100, 101, 102, 103, 104, 105 <reboot/reinit>
56 : * - Output: 200, 201, 202, ...., 299, 300, 301, 302 <reboot/reinit>
57 : * - Output: 400, 401 ...
58 : *
59 : */
60 : template <typename T>
61 : class PersistedCounter : public MonotonicallyIncreasingCounter<T>
62 : {
63 : public:
64 77 : PersistedCounter() : mKey(StorageKeyName::Uninitialized()) {}
65 77 : ~PersistedCounter() override {}
66 :
67 : /**
68 : * @brief
69 : * Initialize a PersistedCounter object.
70 : *
71 : * @param[in] aStorage the storage to use for the counter values.
72 : * @param[in] aKey the key to use for storing the counter values.
73 : * @param[in] aEpoch On bootup, values we vend will start at a
74 : * multiple of this parameter.
75 : *
76 : * @return CHIP_ERROR_INVALID_ARGUMENT if aStorageDelegate or aKey is NULL
77 : * CHIP_ERROR_INVALID_INTEGER_VALUE if aEpoch is 0.
78 : * CHIP_NO_ERROR otherwise
79 : */
80 1 : CHIP_ERROR Init(PersistentStorageDelegate * aStorage, StorageKeyName aKey, T aEpoch)
81 : {
82 1 : VerifyOrReturnError(aStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
83 1 : VerifyOrReturnError(aKey.IsInitialized(), CHIP_ERROR_INVALID_ARGUMENT);
84 1 : VerifyOrReturnError(aEpoch > 0, CHIP_ERROR_INVALID_INTEGER_VALUE);
85 :
86 1 : mStorage = aStorage;
87 1 : mKey = aKey;
88 1 : mEpoch = aEpoch;
89 :
90 : T startValue;
91 :
92 : // Read our previously-stored starting value.
93 1 : ReturnErrorOnFailure(ReadStartValue(startValue));
94 :
95 : #if CHIP_CONFIG_PERSISTED_COUNTER_DEBUG_LOGGING
96 : if constexpr (std::is_same_v<decltype(startValue), uint64_t>)
97 : {
98 : ChipLogDetail(EventLogging, "PersistedCounter::Init() aEpoch 0x" ChipLogFormatX64 " startValue 0x" ChipLogFormatX64,
99 : ChipLogValueX64(aEpoch), ChipLogValueX64(startValue));
100 : }
101 : else if (std::is_same_v<decltype(startValue), uint32_t>)
102 : {
103 : ChipLogDetail(EventLogging, "PersistedCounter::Init() aEpoch 0x%" PRIx32 " startValue 0x%" PRIx32,
104 : static_cast<uint32_t>(aEpoch), static_cast<uint32_t>(startValue));
105 : }
106 : #endif
107 :
108 1 : ReturnErrorOnFailure(PersistNextEpochStart(static_cast<T>(startValue + aEpoch)));
109 :
110 : // This will set the starting value, after which we're ready.
111 1 : return MonotonicallyIncreasingCounter<T>::Init(startValue);
112 : }
113 :
114 : /**
115 : * @brief Increment the counter by N and write to persisted storage if we've completed the current epoch.
116 : *
117 : * @param value value of N
118 : *
119 : * @return Any error returned by a write to persisted storage.
120 : */
121 0 : CHIP_ERROR AdvanceBy(T value) override
122 : {
123 0 : VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
124 0 : VerifyOrReturnError(mKey.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
125 :
126 : // If value is 0, we do not need to do anything
127 0 : VerifyOrReturnError(value > 0, CHIP_NO_ERROR);
128 :
129 : // We should update the persisted epoch value if :
130 : // 1- Sum of the current counter and value is greater or equal to the mNextEpoch.
131 : // This is the standard operating case.
132 : // 2- Increasing the current counter by value would cause a roll over. This would cause the current value to be < to the
133 : // mNextEpoch so we force an update.
134 0 : bool shouldDoEpochUpdate = ((MonotonicallyIncreasingCounter<T>::GetValue() + value) >= mNextEpoch) ||
135 0 : (MonotonicallyIncreasingCounter<T>::GetValue() > std::numeric_limits<T>::max() - value);
136 :
137 0 : ReturnErrorOnFailure(MonotonicallyIncreasingCounter<T>::AdvanceBy(value));
138 :
139 0 : if (shouldDoEpochUpdate)
140 : {
141 : // Since AdvanceBy allows the counter to be increased by an arbitrary value, it is possible that the new counter value
142 : // is greater than mNextEpoch + mEpoch. As such, we want the next Epoch value to be calculated from the new current
143 : // value.
144 0 : PersistAndVerifyNextEpochStart(MonotonicallyIncreasingCounter<T>::GetValue());
145 : }
146 :
147 0 : return CHIP_NO_ERROR;
148 : }
149 :
150 : /**
151 : * @brief Increment the counter and write to persisted storage if we've completed the current epoch.
152 : *
153 : * @return Any error returned by a write to persisted storage.
154 : */
155 0 : CHIP_ERROR Advance() override
156 : {
157 0 : VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
158 0 : VerifyOrReturnError(mKey.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
159 :
160 0 : ReturnErrorOnFailure(MonotonicallyIncreasingCounter<T>::Advance());
161 :
162 0 : if (MonotonicallyIncreasingCounter<T>::GetValue() >= mNextEpoch)
163 : {
164 0 : ReturnErrorOnFailure(PersistAndVerifyNextEpochStart(mNextEpoch));
165 : }
166 :
167 0 : return CHIP_NO_ERROR;
168 : }
169 :
170 : private:
171 0 : CHIP_ERROR PersistAndVerifyNextEpochStart(T refEpoch)
172 : {
173 : // Value advanced past the previously persisted "start point".
174 : // Ensure that a new starting point is persisted.
175 0 : ReturnErrorOnFailure(PersistNextEpochStart(static_cast<T>(refEpoch + mEpoch)));
176 :
177 : // Advancing the epoch should have ensured that the current value is valid
178 0 : VerifyOrReturnError(static_cast<T>(MonotonicallyIncreasingCounter<T>::GetValue() + mEpoch) == mNextEpoch,
179 : CHIP_ERROR_INTERNAL);
180 :
181 : // Previous check did not take into consideration that the counter value can be equal to the max counter value or
182 : // rollover.
183 : // TODO(#33175): PersistedCounter allows rollover so this check is incorrect. We need a Counter class that adequatly
184 : // manages rollover behavior for counters that cannot rollover.
185 : // VerifyOrReturnError(MonotonicallyIncreasingCounter<T>::GetValue() < mNextEpoch, CHIP_ERROR_INTERNAL);
186 :
187 0 : return CHIP_NO_ERROR;
188 : }
189 :
190 : /**
191 : * @brief
192 : * Write out the counter value to persistent storage.
193 : *
194 : * @param[in] aStartValue The counter value to write out.
195 : *
196 : * @return Any error returned by a write to persistent storage.
197 : */
198 : CHIP_ERROR
199 1 : PersistNextEpochStart(T aStartValue)
200 : {
201 1 : mNextEpoch = aStartValue;
202 : #if CHIP_CONFIG_PERSISTED_COUNTER_DEBUG_LOGGING
203 : if constexpr (std::is_same_v<decltype(aStartValue), uint64_t>)
204 : {
205 : ChipLogDetail(EventLogging, "PersistedCounter::WriteStartValue() aStartValue 0x" ChipLogFormatX64,
206 : ChipLogValueX64(aStartValue));
207 : }
208 : else
209 : {
210 : ChipLogDetail(EventLogging, "PersistedCounter::WriteStartValue() aStartValue 0x%" PRIx32,
211 : static_cast<uint32_t>(aStartValue));
212 : }
213 : #endif
214 :
215 1 : T valueLE = Encoding::LittleEndian::HostSwap<T>(aStartValue);
216 1 : return mStorage->SyncSetKeyValue(mKey.KeyName(), &valueLE, sizeof(valueLE));
217 : }
218 :
219 : /**
220 : * @brief
221 : * Read our starting counter value (if we have one) in from persistent storage.
222 : *
223 : * @param[in,out] aStartValue The value read out.
224 : *
225 : * @return Any error returned by a read from persistent storage.
226 : */
227 1 : CHIP_ERROR ReadStartValue(T & aStartValue)
228 : {
229 1 : T valueLE = GetInitialCounterValue();
230 1 : uint16_t size = sizeof(valueLE);
231 :
232 1 : VerifyOrReturnError(mKey.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
233 :
234 1 : CHIP_ERROR err = mStorage->SyncGetKeyValue(mKey.KeyName(), &valueLE, size);
235 1 : if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
236 : {
237 : // No previously-stored value, no worries, the counter is initialized to zero.
238 : // Suppress the error.
239 1 : err = CHIP_NO_ERROR;
240 : }
241 : else
242 : {
243 : // TODO: Figure out how to avoid a bootloop here. Maybe we should just
244 : // init to 0? Or a random value?
245 0 : ReturnErrorOnFailure(err);
246 : }
247 :
248 1 : if (size != sizeof(valueLE))
249 : {
250 : // TODO: Again, figure out whether this could lead to bootloops.
251 0 : return CHIP_ERROR_INCORRECT_STATE;
252 : }
253 :
254 1 : aStartValue = Encoding::LittleEndian::HostSwap<T>(valueLE);
255 :
256 : #if CHIP_CONFIG_PERSISTED_COUNTER_DEBUG_LOGGING
257 : if constexpr (std::is_same_v<decltype(aStartValue), uint64_t>)
258 : {
259 : ChipLogDetail(EventLogging, "PersistedCounter::ReadStartValue() aStartValue 0x" ChipLogFormatX64,
260 : ChipLogValueX64(aStartValue));
261 : }
262 : else
263 : {
264 : ChipLogDetail(EventLogging, "PersistedCounter::ReadStartValue() aStartValue 0x%" PRIx32,
265 : static_cast<uint32_t>(aStartValue));
266 : }
267 : #endif
268 :
269 1 : return CHIP_NO_ERROR;
270 : }
271 :
272 : /**
273 : * @brief Get the Initial Counter Value
274 : *
275 : * By default, persisted counters start off at 0.
276 : */
277 1 : virtual inline T GetInitialCounterValue() { return 0; }
278 :
279 : PersistentStorageDelegate * mStorage = nullptr; // start value is stored here
280 : StorageKeyName mKey;
281 : T mEpoch = 0; // epoch modulus value
282 : T mNextEpoch = 0; // next epoch start
283 : };
284 :
285 : } // namespace chip
|