Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021-2022 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : #pragma once
20 :
21 : #include <algorithm>
22 : #include <lib/core/CHIPCore.h>
23 : #include <lib/core/CHIPPersistentStorageDelegate.h>
24 : #include <lib/support/DLLUtil.h>
25 : #include <lib/support/SafeInt.h>
26 : #include <lib/support/logging/CHIPLogging.h>
27 : #include <map>
28 : #include <set>
29 : #include <string>
30 : #include <vector>
31 :
32 : namespace chip {
33 :
34 : /**
35 : * Implementation of PersistentStorageDelegate suitable for unit tests,
36 : * where persistence lasts for the object's lifetime and where all data is retained
37 : * is memory.
38 : *
39 : * This version also has "poison keys" which, if accessed, yield an error. This can
40 : * be used in unit tests to make sure a module making use of the PersistentStorageDelegate
41 : * does not access some particular keys which should remain untouched by underlying
42 : * logic.
43 : */
44 : class TestPersistentStorageDelegate : public PersistentStorageDelegate
45 : {
46 : public:
47 : enum class LoggingLevel : unsigned
48 : {
49 : kDisabled = 0,
50 : kLogMutation = 1,
51 : kLogMutationAndReads = 2,
52 : };
53 :
54 2007 : TestPersistentStorageDelegate() {}
55 :
56 101789 : CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override
57 : {
58 101789 : if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
59 : {
60 23 : ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncGetKeyValue: Get key '%s'", StringOrNullMarker(key));
61 : }
62 :
63 101789 : CHIP_ERROR err = SyncGetKeyValueInternal(key, buffer, size);
64 :
65 101789 : if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
66 : {
67 46 : if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
68 : {
69 14 : ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncGetKeyValue: Key '%s' not found",
70 : StringOrNullMarker(key));
71 : }
72 18 : else if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
73 : {
74 0 : ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncGetKeyValue: Key '%s' is a poison key",
75 : StringOrNullMarker(key));
76 : }
77 : }
78 :
79 101789 : return err;
80 : }
81 :
82 355101 : CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override
83 : {
84 355101 : if (mLoggingLevel >= LoggingLevel::kLogMutation)
85 : {
86 26 : ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncSetKeyValue, Set key '%s' with data size %u", key,
87 : static_cast<unsigned>(size));
88 : }
89 :
90 355101 : CHIP_ERROR err = SyncSetKeyValueInternal(key, value, size);
91 :
92 355101 : if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
93 : {
94 24 : if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
95 : {
96 0 : ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncSetKeyValue: Key '%s' is a poison key",
97 : StringOrNullMarker(key));
98 : }
99 : }
100 :
101 355101 : return err;
102 : }
103 :
104 72110 : CHIP_ERROR SyncDeleteKeyValue(const char * key) override
105 : {
106 72110 : if (mLoggingLevel >= LoggingLevel::kLogMutation)
107 : {
108 7 : ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncDeleteKeyValue, Delete key '%s'", StringOrNullMarker(key));
109 : }
110 72110 : CHIP_ERROR err = SyncDeleteKeyValueInternal(key);
111 :
112 72110 : if (mLoggingLevel >= LoggingLevel::kLogMutation)
113 : {
114 14 : if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
115 : {
116 0 : ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncDeleteKeyValue: Key '%s' not found",
117 : StringOrNullMarker(key));
118 : }
119 14 : else if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
120 : {
121 0 : ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncDeleteKeyValue: Key '%s' is a poison key",
122 : StringOrNullMarker(key));
123 : }
124 : }
125 :
126 72110 : return err;
127 : }
128 :
129 : /**
130 : * @brief Adds a "poison key" that causes all operations on that key to fail.
131 : */
132 16 : virtual void AddPoisonKey(const std::string & key) { mPoisonKeys[key] = { 0, 0 }; }
133 :
134 : /**
135 : * @brief Adds a "poison key" with separate read and write success patterns.
136 : *
137 : * Each pattern is a bit field that controls the sequence of outcomes for successive
138 : * operations on the key. The lowest bit determines the next outcome: 1 = success,
139 : * 0 = failure. After each operation the pattern is arithmetically right-shifted.
140 : * (This means the highest bit repeats indefinitely once all other bits have been consumed).
141 : *
142 : * Examples:
143 : * 0 always fail
144 : * 0b1 succeed once, then fail forever
145 : * 0b11 succeed twice, then fail forever
146 : * 0b010 fail once, succeed once, then fail forever
147 : * -1 always succeed (effectively not poisoned for this operation type)
148 : *
149 : * Note: Write patterns will not advance while `SetRejectWrites(true)` is in effect.
150 : *
151 : * @param key Key to poison.
152 : * @param readPattern Bit pattern for read (Get) operations.
153 : * @param writePattern Bit pattern for write (Set/Delete) operations.
154 : */
155 12 : virtual void AddPoisonKey(const std::string & key, int readPattern, int writePattern)
156 : {
157 12 : mPoisonKeys[key] = { readPattern, writePattern };
158 12 : }
159 :
160 : /**
161 : * Allows subsequent writes to be rejected for unit testing purposes.
162 : */
163 2 : virtual void SetRejectWrites(bool rejectWrites) { mRejectWrites = rejectWrites; }
164 :
165 : /**
166 : * @brief Clear all "poison keys"
167 : *
168 : */
169 24 : virtual void ClearPoisonKeys() { mPoisonKeys.clear(); }
170 :
171 : /**
172 : * @brief Reset entire contents back to empty. This does NOT clear the "poison keys"
173 : *
174 : */
175 581 : virtual void ClearStorage() { mStorage.clear(); }
176 :
177 : /**
178 : * @return the number of keys currently written in storage
179 : */
180 109 : virtual size_t GetNumKeys() { return mStorage.size(); }
181 :
182 : /**
183 : * @return a set of all the keys stored
184 : */
185 2 : virtual std::set<std::string> GetKeys()
186 : {
187 2 : std::set<std::string> keys;
188 :
189 14 : for (auto it = mStorage.begin(); it != mStorage.end(); ++it)
190 : {
191 12 : keys.insert(it->first);
192 : }
193 :
194 2 : return keys;
195 : }
196 :
197 : /**
198 : * @brief Determine if storage has a given key
199 : *
200 : * @param key - key to find (case-sensitive)
201 : * @return true if key is present in storage, false otherwise
202 : */
203 173898 : virtual bool HasKey(const std::string & key) { return (mStorage.find(key) != mStorage.end()); }
204 :
205 : /**
206 : * @brief Set the logging verbosity for debugging
207 : *
208 : * @param loggingLevel - logging verbosity level to set
209 : */
210 2 : virtual void SetLoggingLevel(LoggingLevel loggingLevel) { mLoggingLevel = loggingLevel; }
211 :
212 : /**
213 : * @brief Dump a list of all storage keys, sorted alphabetically
214 : */
215 1 : virtual void DumpKeys()
216 : {
217 1 : ChipLogError(Test, "TestPersistentStorageDelegate::DumpKeys: %u keys", static_cast<unsigned>(GetNumKeys()));
218 :
219 1 : auto allKeys = GetKeys();
220 1 : std::vector<std::string> allKeysSorted(allKeys.cbegin(), allKeys.cend());
221 1 : std::sort(allKeysSorted.begin(), allKeysSorted.end());
222 :
223 11 : for (const std::string & key : allKeysSorted)
224 : {
225 10 : (void) key.c_str(); // Guard against log level disabling error logging which would make `key` unused.
226 10 : ChipLogError(Test, " -> %s", key.c_str());
227 : }
228 1 : }
229 :
230 : protected:
231 101789 : virtual CHIP_ERROR SyncGetKeyValueInternal(const char * key, void * buffer, uint16_t & size)
232 : {
233 101789 : VerifyOrReturnError((buffer != nullptr) || (size == 0), CHIP_ERROR_INVALID_ARGUMENT);
234 :
235 : // Making sure poison keys are not accessed
236 101787 : if (IsReadPoisoned(key))
237 : {
238 2 : return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
239 : }
240 :
241 101785 : bool contains = HasKey(key);
242 101785 : VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
243 :
244 89453 : std::vector<uint8_t> & value = mStorage[key];
245 89453 : size_t valueSize = value.size();
246 89453 : if (!CanCastTo<uint16_t>(valueSize))
247 : {
248 0 : return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
249 : }
250 :
251 89453 : uint16_t valueSizeUint16 = static_cast<uint16_t>(valueSize);
252 89453 : VerifyOrReturnError(size != 0 || valueSizeUint16 != 0, CHIP_NO_ERROR);
253 89448 : VerifyOrReturnError(buffer != nullptr, CHIP_ERROR_BUFFER_TOO_SMALL);
254 :
255 89340 : uint16_t sizeToCopy = std::min(size, valueSizeUint16);
256 :
257 89340 : size = sizeToCopy;
258 89340 : memcpy(buffer, value.data(), size);
259 89340 : return size < valueSizeUint16 ? CHIP_ERROR_BUFFER_TOO_SMALL : CHIP_NO_ERROR;
260 : }
261 :
262 355101 : virtual CHIP_ERROR SyncSetKeyValueInternal(const char * key, const void * value, uint16_t size)
263 : {
264 : // Make sure writes are allowed and poison keys are not accessed
265 355101 : if (mRejectWrites || /* has side effect */ IsWritePoisoned(key))
266 : {
267 24 : return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
268 : }
269 :
270 : // Handle empty values
271 355077 : if (value == nullptr)
272 : {
273 4 : if (size == 0)
274 : {
275 3 : mStorage[key] = std::vector<uint8_t>();
276 3 : return CHIP_NO_ERROR;
277 : }
278 :
279 1 : return CHIP_ERROR_INVALID_ARGUMENT;
280 : }
281 : // Handle non-empty values
282 :
283 355073 : const uint8_t * bytes = static_cast<const uint8_t *>(value);
284 355073 : mStorage[key] = std::vector<uint8_t>(bytes, bytes + size);
285 355073 : return CHIP_NO_ERROR;
286 : }
287 :
288 72110 : virtual CHIP_ERROR SyncDeleteKeyValueInternal(const char * key)
289 : {
290 : // Make sure writes are allowed and poison keys are not accessed
291 72110 : if (mRejectWrites || /* has side effect */ IsWritePoisoned(key))
292 : {
293 1 : return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
294 : }
295 :
296 72109 : bool contains = HasKey(key);
297 72109 : VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
298 71831 : mStorage.erase(key);
299 71831 : return CHIP_NO_ERROR;
300 : }
301 :
302 : std::map<std::string, std::vector<uint8_t>> mStorage;
303 :
304 : struct PoisonPattern
305 : {
306 : int read;
307 : int write;
308 : };
309 : std::map<std::string, PoisonPattern> mPoisonKeys;
310 : bool mRejectWrites = false;
311 : LoggingLevel mLoggingLevel = LoggingLevel::kDisabled;
312 :
313 : // Advances and checks the given pattern. Returns true if the operation should fail.
314 32 : static bool TakePoison(int & pattern)
315 : {
316 32 : bool poisoned = (pattern & 1) == 0;
317 32 : pattern >>= 1; // arithmetic right shift (sign bit repeats, so -1 stays -1)
318 32 : return poisoned;
319 : }
320 :
321 101787 : bool IsReadPoisoned(const char * key)
322 : {
323 101787 : auto it = mPoisonKeys.find(std::string(key));
324 101787 : return it != mPoisonKeys.end() && TakePoison(it->second.read);
325 : }
326 :
327 427209 : bool IsWritePoisoned(const char * key)
328 : {
329 427209 : auto it = mPoisonKeys.find(std::string(key));
330 427209 : return it != mPoisonKeys.end() && TakePoison(it->second.write);
331 : }
332 : };
333 :
334 : } // namespace chip
|