1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #define DUMP_KVS_STATE_TO_FILE 0
16 #define USE_MEMORY_BUFFER 1
17
18 #include "pw_kvs/key_value_store.h"
19
20 #include <array>
21 #include <cstdio>
22 #include <cstring>
23
24 #include "pw_span/span.h"
25
26 #if DUMP_KVS_STATE_TO_FILE
27 #include <vector>
28 #endif // DUMP_KVS_STATE_TO_FILE
29
30 #include "pw_assert/check.h"
31 #include "pw_bytes/array.h"
32 #include "pw_checksum/crc16_ccitt.h"
33 #include "pw_kvs/crc16_checksum.h"
34 #include "pw_kvs/fake_flash_memory.h"
35 #include "pw_kvs/flash_memory.h"
36 #include "pw_kvs/internal/entry.h"
37 #include "pw_kvs_private/config.h"
38 #include "pw_log/log.h"
39 #include "pw_status/status.h"
40 #include "pw_string/string_builder.h"
41 #include "pw_unit_test/framework.h"
42
43 namespace pw::kvs {
44 namespace {
45
46 using internal::EntryHeader;
47
48 constexpr size_t kMaxEntries = 256;
49 constexpr size_t kMaxUsableSectors = 256;
50
51 // This is a self contained flash unit with both memory and a single partition.
52 template <uint32_t kSectorSizeBytes, uint16_t kSectorCount>
53 struct FlashWithPartitionFake {
54 // Default to 16 byte alignment, which is common in practice.
FlashWithPartitionFakepw::kvs::__anon4189dda60111::FlashWithPartitionFake55 FlashWithPartitionFake() : FlashWithPartitionFake(16) {}
FlashWithPartitionFakepw::kvs::__anon4189dda60111::FlashWithPartitionFake56 FlashWithPartitionFake(size_t alignment_bytes)
57 : memory(alignment_bytes), partition(&memory, 0, memory.sector_count()) {}
58
59 FakeFlashMemoryBuffer<kSectorSizeBytes, kSectorCount> memory;
60 FlashPartition partition;
61
62 public:
63 #if DUMP_KVS_STATE_TO_FILE
Dumppw::kvs::__anon4189dda60111::FlashWithPartitionFake64 Status Dump(const char* filename) {
65 std::FILE* out_file = std::fopen(filename, "w+");
66 if (out_file == nullptr) {
67 PW_LOG_ERROR("Failed to dump to %s", filename);
68 return Status::DataLoss();
69 }
70 std::vector<std::byte> out_vec(memory.size_bytes());
71 Status status =
72 memory.Read(0, span<std::byte>(out_vec.data(), out_vec.size()));
73 if (status != OkStatus()) {
74 fclose(out_file);
75 return status;
76 }
77
78 size_t written =
79 std::fwrite(out_vec.data(), 1, memory.size_bytes(), out_file);
80 if (written != memory.size_bytes()) {
81 PW_LOG_ERROR("Failed to dump to %s, written=%u",
82 filename,
83 static_cast<unsigned>(written));
84 status = Status::DataLoss();
85 } else {
86 PW_LOG_INFO("Dumped to %s", filename);
87 status = OkStatus();
88 }
89
90 fclose(out_file);
91 return status;
92 }
93 #else
94 Status Dump(const char*) { return OkStatus(); }
95 #endif // DUMP_KVS_STATE_TO_FILE
96 };
97
98 typedef FlashWithPartitionFake<4 * 128 /*sector size*/, 6 /*sectors*/> Flash;
99
100 FakeFlashMemoryBuffer<1024, 60> large_test_flash(8);
101 FlashPartition large_test_partition(&large_test_flash,
102 0,
103 large_test_flash.sector_count());
104
105 constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
106
107 ChecksumCrc16 checksum;
108 // For KVS magic value always use a random 32 bit integer rather than a
109 // human readable 4 bytes. See pw_kvs/format.h for more information.
110 constexpr EntryFormat default_format{.magic = 0xa6cb3c16,
111 .checksum = &checksum};
112
113 } // namespace
114
TEST(InitCheck,TooFewSectors)115 TEST(InitCheck, TooFewSectors) {
116 // Use test flash with 1 x 4k sectors, 16 byte alignment
117 FakeFlashMemoryBuffer<4 * 1024, 1> test_flash(16);
118 FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
119
120 // For KVS magic value always use a random 32 bit integer rather than a
121 // human readable 4 bytes. See pw_kvs/format.h for more information.
122 constexpr EntryFormat format{.magic = 0x89bb14d2, .checksum = nullptr};
123 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&test_partition,
124 format);
125
126 EXPECT_EQ(kvs.Init(), Status::FailedPrecondition());
127 }
128
TEST(InitCheck,ZeroSectors)129 TEST(InitCheck, ZeroSectors) {
130 // Use test flash with 1 x 4k sectors, 16 byte alignment
131 FakeFlashMemoryBuffer<4 * 1024, 1> test_flash(16);
132
133 // Set FlashPartition to have 0 sectors.
134 FlashPartition test_partition(&test_flash, 0, 0);
135
136 // For KVS magic value always use a random 32 bit integer rather than a
137 // human readable 4 bytes. See pw_kvs/format.h for more information.
138 constexpr EntryFormat format{.magic = 0xd1da57c1, .checksum = nullptr};
139 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&test_partition,
140 format);
141
142 EXPECT_EQ(kvs.Init(), Status::FailedPrecondition());
143 }
144
TEST(InitCheck,TooManySectors)145 TEST(InitCheck, TooManySectors) {
146 // Use test flash with 1 x 4k sectors, 16 byte alignment
147 FakeFlashMemoryBuffer<4 * 1024, 5> test_flash(16);
148
149 // Set FlashPartition to have 0 sectors.
150 FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
151
152 // For KVS magic value always use a random 32 bit integer rather than a
153 // human readable 4 bytes. See pw_kvs/format.h for more information.
154 constexpr EntryFormat format{.magic = 0x610f6d17, .checksum = nullptr};
155 KeyValueStoreBuffer<kMaxEntries, 2> kvs(&test_partition, format);
156
157 EXPECT_EQ(kvs.Init(), Status::FailedPrecondition());
158 }
159
160 #define ASSERT_OK(expr) ASSERT_EQ(OkStatus(), expr)
161 #define EXPECT_OK(expr) EXPECT_EQ(OkStatus(), expr)
162
TEST(InMemoryKvs,WriteOneKeyMultipleTimes)163 TEST(InMemoryKvs, WriteOneKeyMultipleTimes) {
164 // Create and erase the fake flash. It will persist across reloads.
165 Flash flash;
166 ASSERT_OK(flash.partition.Erase());
167
168 int num_reloads = 2;
169 for (int reload = 0; reload < num_reloads; ++reload) {
170 PW_LOG_DEBUG("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
171 PW_LOG_DEBUG("xxx xxxx");
172 PW_LOG_DEBUG("xxx Reload %2d xxxx", reload);
173 PW_LOG_DEBUG("xxx xxxx");
174 PW_LOG_DEBUG("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
175
176 // Create and initialize the KVS. For KVS magic value always use a random 32
177 // bit integer rather than a human readable 4 bytes. See pw_kvs/format.h for
178 // more information.
179 constexpr EntryFormat format{.magic = 0x83a9257, .checksum = nullptr};
180 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
181 format);
182 ASSERT_OK(kvs.Init());
183
184 // Write the same entry many times.
185 const char* key = "abcd";
186 const size_t num_writes = 99;
187 uint32_t written_value;
188 EXPECT_EQ(kvs.size(), (reload == 0) ? 0 : 1u);
189 for (uint32_t i = 0; i < num_writes; ++i) {
190 PW_LOG_DEBUG(
191 "PUT #%zu for key %s with value %zu", size_t(i), key, size_t(i));
192
193 written_value = i + 0xfc; // Prevent accidental pass with zero.
194 EXPECT_OK(kvs.Put(key, written_value));
195 EXPECT_EQ(kvs.size(), 1u);
196 }
197
198 // Verify that we can read the value back.
199 PW_LOG_DEBUG("GET final value for key: %s", key);
200 uint32_t actual_value;
201 EXPECT_OK(kvs.Get(key, &actual_value));
202 EXPECT_EQ(actual_value, written_value);
203
204 char fname_buf[64] = {'\0'};
205 snprintf(&fname_buf[0],
206 sizeof(fname_buf),
207 "WriteOneKeyMultipleTimes_%d.bin",
208 reload);
209 ASSERT_EQ(OkStatus(), flash.Dump(fname_buf));
210 }
211 }
212
TEST(InMemoryKvs,WritingMultipleKeysIncreasesSize)213 TEST(InMemoryKvs, WritingMultipleKeysIncreasesSize) {
214 // Create and erase the fake flash.
215 Flash flash;
216 ASSERT_OK(flash.partition.Erase());
217
218 // Create and initialize the KVS. For KVS magic value always use a random 32
219 // bit integer rather than a human readable 4 bytes. See pw_kvs/format.h for
220 // more information.
221 constexpr EntryFormat format{.magic = 0x2ed3a058, .checksum = nullptr};
222 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
223 format);
224 ASSERT_OK(kvs.Init());
225
226 // Write the same entry many times.
227 const size_t num_writes = 10;
228 EXPECT_EQ(kvs.size(), 0u);
229 for (size_t i = 0; i < num_writes; ++i) {
230 StringBuffer<150> key;
231 key << "key_" << i;
232 PW_LOG_DEBUG("PUT #%zu for key %s with value %zu", i, key.c_str(), i);
233
234 size_t value = i + 77; // Prevent accidental pass with zero.
235 EXPECT_OK(kvs.Put(key.view(), value));
236 EXPECT_EQ(kvs.size(), i + 1);
237 }
238 ASSERT_EQ(OkStatus(), flash.Dump("WritingMultipleKeysIncreasesSize.bin"));
239 }
240
TEST(InMemoryKvs,WriteAndReadOneKey)241 TEST(InMemoryKvs, WriteAndReadOneKey) {
242 // Create and erase the fake flash.
243 Flash flash;
244 ASSERT_OK(flash.partition.Erase());
245
246 // Create and initialize the KVS.
247 // For KVS magic value always use a random 32 bit integer rather than a
248 // human readable 4 bytes. See pw_kvs/format.h for more information.
249 constexpr EntryFormat format{.magic = 0x5d70896, .checksum = nullptr};
250 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
251 format);
252 ASSERT_OK(kvs.Init());
253
254 // Add one entry.
255 const char* key = "Key1";
256 PW_LOG_DEBUG("PUT value for key: %s", key);
257 uint8_t written_value = 0xDA;
258 ASSERT_OK(kvs.Put(key, written_value));
259 EXPECT_EQ(kvs.size(), 1u);
260
261 PW_LOG_DEBUG("GET value for key: %s", key);
262 uint8_t actual_value;
263 ASSERT_OK(kvs.Get(key, &actual_value));
264 EXPECT_EQ(actual_value, written_value);
265
266 EXPECT_EQ(kvs.size(), 1u);
267 }
268
TEST(InMemoryKvs,WriteOneKeyValueMultipleTimes)269 TEST(InMemoryKvs, WriteOneKeyValueMultipleTimes) {
270 // Create and erase the fake flash.
271 Flash flash;
272 ASSERT_OK(flash.partition.Erase());
273
274 // Create and initialize the KVS.
275 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
276 default_format);
277 ASSERT_OK(kvs.Init());
278
279 // Add one entry, with the same key and value, multiple times.
280 const char* key = "Key1";
281 uint8_t written_value = 0xDA;
282 for (int i = 0; i < 50; i++) {
283 PW_LOG_DEBUG("PUT [%d] value for key: %s", i, key);
284 ASSERT_OK(kvs.Put(key, written_value));
285 EXPECT_EQ(kvs.size(), 1u);
286 }
287
288 PW_LOG_DEBUG("GET value for key: %s", key);
289 uint8_t actual_value;
290 ASSERT_OK(kvs.Get(key, &actual_value));
291 EXPECT_EQ(actual_value, written_value);
292
293 // Verify that only one entry was written to the KVS.
294 EXPECT_EQ(kvs.size(), 1u);
295 EXPECT_EQ(kvs.transaction_count(), 1u);
296 KeyValueStore::StorageStats stats = kvs.GetStorageStats();
297 EXPECT_EQ(stats.reclaimable_bytes, 0u);
298 }
299
TEST(InMemoryKvs,Basic)300 TEST(InMemoryKvs, Basic) {
301 const char* key1 = "Key1";
302 const char* key2 = "Key2";
303
304 // Create and erase the fake flash.
305 Flash flash;
306 ASSERT_EQ(OkStatus(), flash.partition.Erase());
307
308 // Create and initialize the KVS.
309 // For KVS magic value always use a random 32 bit integer rather than a
310 // human readable 4 bytes. See pw_kvs/format.h for more information.
311 constexpr EntryFormat format{.magic = 0x7bf19895, .checksum = nullptr};
312 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
313 format);
314 ASSERT_OK(kvs.Init());
315
316 // Add two entries with different keys and values.
317 uint8_t value1 = 0xDA;
318 ASSERT_OK(kvs.Put(key1, as_bytes(span(&value1, sizeof(value1)))));
319 EXPECT_EQ(kvs.size(), 1u);
320
321 uint32_t value2 = 0xBAD0301f;
322 ASSERT_OK(kvs.Put(key2, value2));
323 EXPECT_EQ(kvs.size(), 2u);
324
325 // Verify data
326 uint32_t test2;
327 EXPECT_OK(kvs.Get(key2, &test2));
328
329 uint8_t test1;
330 ASSERT_OK(kvs.Get(key1, &test1));
331
332 EXPECT_EQ(test1, value1);
333 EXPECT_EQ(test2, value2);
334
335 EXPECT_EQ(kvs.size(), 2u);
336 }
337
TEST(InMemoryKvs,CallingEraseTwice_NothingWrittenToFlash)338 TEST(InMemoryKvs, CallingEraseTwice_NothingWrittenToFlash) {
339 // Create and erase the fake flash.
340 Flash flash;
341 ASSERT_EQ(OkStatus(), flash.partition.Erase());
342
343 // Create and initialize the KVS.
344 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
345 default_format);
346 ASSERT_OK(kvs.Init());
347
348 const uint8_t kValue = 0xDA;
349 ASSERT_EQ(OkStatus(), kvs.Put(keys[0], kValue));
350 ASSERT_EQ(OkStatus(), kvs.Delete(keys[0]));
351
352 // Compare before / after checksums to verify that nothing was written.
353 const uint16_t crc = checksum::Crc16Ccitt::Calculate(flash.memory.buffer());
354
355 EXPECT_EQ(kvs.Delete(keys[0]), Status::NotFound());
356
357 EXPECT_EQ(crc, checksum::Crc16Ccitt::Calculate(flash.memory.buffer()));
358 }
359
360 class LargeEmptyInitializedKvs : public ::testing::Test {
361 protected:
LargeEmptyInitializedKvs()362 LargeEmptyInitializedKvs() : kvs_(&large_test_partition, default_format) {
363 PW_CHECK_OK(large_test_partition.Erase());
364 PW_CHECK_OK(kvs_.Init());
365 }
366
367 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
368 };
369
TEST_F(LargeEmptyInitializedKvs,Basic)370 TEST_F(LargeEmptyInitializedKvs, Basic) {
371 const uint8_t kValue1 = 0xDA;
372 const uint8_t kValue2 = 0x12;
373 uint8_t value;
374 ASSERT_EQ(OkStatus(), kvs_.Put(keys[0], kValue1));
375 EXPECT_EQ(kvs_.size(), 1u);
376 ASSERT_EQ(OkStatus(), kvs_.Delete(keys[0]));
377 EXPECT_EQ(kvs_.Get(keys[0], &value), Status::NotFound());
378 ASSERT_EQ(OkStatus(), kvs_.Put(keys[1], kValue1));
379 ASSERT_EQ(OkStatus(), kvs_.Put(keys[2], kValue2));
380 ASSERT_EQ(OkStatus(), kvs_.Delete(keys[1]));
381 EXPECT_EQ(OkStatus(), kvs_.Get(keys[2], &value));
382 EXPECT_EQ(kValue2, value);
383 ASSERT_EQ(kvs_.Get(keys[1], &value), Status::NotFound());
384 EXPECT_EQ(kvs_.size(), 1u);
385 }
386
TEST_F(LargeEmptyInitializedKvs,FullMaintenance)387 TEST_F(LargeEmptyInitializedKvs, FullMaintenance) {
388 const uint8_t kValue1 = 0xDA;
389 const uint8_t kValue2 = 0x12;
390
391 // Write a key and write again with a different value, resulting in a stale
392 // entry from the first write.
393 ASSERT_EQ(OkStatus(), kvs_.Put(keys[0], kValue1));
394 ASSERT_EQ(OkStatus(), kvs_.Put(keys[0], kValue2));
395 EXPECT_EQ(kvs_.size(), 1u);
396
397 KeyValueStore::StorageStats stats = kvs_.GetStorageStats();
398 EXPECT_EQ(stats.sector_erase_count, 0u);
399 EXPECT_GT(stats.reclaimable_bytes, 0u);
400
401 // Do regular FullMaintenance, which should not touch the sector with valid
402 // data.
403 EXPECT_EQ(OkStatus(), kvs_.FullMaintenance());
404 stats = kvs_.GetStorageStats();
405 EXPECT_EQ(stats.sector_erase_count, 0u);
406 EXPECT_GT(stats.reclaimable_bytes, 0u);
407
408 // Do aggressive FullMaintenance, which should GC the sector with valid data,
409 // resulting in no reclaimable bytes and an erased sector.
410 EXPECT_EQ(OkStatus(), kvs_.HeavyMaintenance());
411 stats = kvs_.GetStorageStats();
412 EXPECT_EQ(stats.sector_erase_count, 1u);
413 EXPECT_EQ(stats.reclaimable_bytes, 0u);
414 }
415
TEST_F(LargeEmptyInitializedKvs,KeyDeletionMaintenance)416 TEST_F(LargeEmptyInitializedKvs, KeyDeletionMaintenance) {
417 const uint8_t kValue1 = 0xDA;
418 const uint8_t kValue2 = 0x12;
419 uint8_t val = 0;
420
421 // Write and delete a key. The key should be gone, but the size should be 1.
422 ASSERT_EQ(OkStatus(), kvs_.Put(keys[0], kValue1));
423 ASSERT_EQ(kvs_.size(), 1u);
424 ASSERT_EQ(OkStatus(), kvs_.Delete(keys[0]));
425
426 // Ensure the key is indeed gone and the size is 1 before continuing.
427 ASSERT_EQ(kvs_.Get(keys[0], &val), Status::NotFound());
428 ASSERT_EQ(kvs_.size(), 0u);
429 ASSERT_EQ(kvs_.total_entries_with_deleted(), 1u);
430
431 KeyValueStore::StorageStats stats = kvs_.GetStorageStats();
432 EXPECT_EQ(stats.sector_erase_count, 0u);
433 EXPECT_GT(stats.reclaimable_bytes, 0u);
434
435 // Do aggressive FullMaintenance, which should GC the sector with valid data,
436 // resulting in no reclaimable bytes and an erased sector.
437 EXPECT_EQ(OkStatus(), kvs_.HeavyMaintenance());
438 stats = kvs_.GetStorageStats();
439 EXPECT_EQ(stats.reclaimable_bytes, 0u);
440 ASSERT_EQ(kvs_.size(), 0u);
441
442 if (PW_KVS_REMOVE_DELETED_KEYS_IN_HEAVY_MAINTENANCE) {
443 EXPECT_GT(stats.sector_erase_count, 1u);
444 ASSERT_EQ(kvs_.total_entries_with_deleted(), 0u);
445 } else { // The deleted entries are only removed if that feature is enabled.
446 EXPECT_EQ(stats.sector_erase_count, 1u);
447 ASSERT_EQ(kvs_.total_entries_with_deleted(), 1u);
448 }
449
450 // Do it again but with 2 keys and keep one.
451 ASSERT_EQ(OkStatus(), kvs_.Put(keys[0], kValue1));
452 ASSERT_EQ(OkStatus(), kvs_.Put(keys[1], kValue2));
453 ASSERT_EQ(kvs_.size(), 2u);
454 ASSERT_EQ(OkStatus(), kvs_.Delete(keys[0]));
455
456 // Ensure the key is indeed gone and the size is 1 before continuing.
457 ASSERT_EQ(kvs_.Get(keys[0], &val), Status::NotFound());
458 ASSERT_EQ(kvs_.size(), 1u);
459 ASSERT_EQ(kvs_.total_entries_with_deleted(), 2u);
460
461 // Do aggressive FullMaintenance, which should GC the sector with valid data,
462 // resulting in no reclaimable bytes and an erased sector.
463 EXPECT_EQ(OkStatus(), kvs_.HeavyMaintenance());
464 stats = kvs_.GetStorageStats();
465 ASSERT_EQ(kvs_.size(), 1u);
466
467 if (PW_KVS_REMOVE_DELETED_KEYS_IN_HEAVY_MAINTENANCE) {
468 ASSERT_EQ(kvs_.total_entries_with_deleted(), 1u);
469 } else { // The deleted entries are only removed if that feature is enabled.
470 ASSERT_EQ(kvs_.total_entries_with_deleted(), 2u);
471 }
472
473 // Read back the second key to make sure it is still valid.
474 ASSERT_EQ(kvs_.Get(keys[1], &val), OkStatus());
475 ASSERT_EQ(val, kValue2);
476 }
477
TEST(InMemoryKvs,Put_MaxValueSize)478 TEST(InMemoryKvs, Put_MaxValueSize) {
479 // Create and erase the fake flash.
480 Flash flash;
481 ASSERT_EQ(OkStatus(), flash.partition.Erase());
482
483 // Create and initialize the KVS.
484 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
485 default_format);
486 ASSERT_OK(kvs.Init());
487
488 size_t max_key_value_size = kvs.max_key_value_size_bytes();
489 EXPECT_EQ(max_key_value_size,
490 KeyValueStore::max_key_value_size_bytes(
491 flash.partition.sector_size_bytes()));
492
493 size_t max_value_size =
494 flash.partition.sector_size_bytes() - sizeof(EntryHeader) - 1;
495 EXPECT_EQ(max_key_value_size, (max_value_size + 1));
496
497 // Use the large_test_flash as a big chunk of data for the Put statement.
498 ASSERT_GT(sizeof(large_test_flash), max_value_size + 2 * sizeof(EntryHeader));
499 auto big_data = as_bytes(span(&large_test_flash, 1));
500
501 EXPECT_EQ(OkStatus(), kvs.Put("K", big_data.subspan(0, max_value_size)));
502
503 // Larger than maximum is rejected.
504 EXPECT_EQ(Status::InvalidArgument(),
505 kvs.Put("K", big_data.subspan(0, max_value_size + 1)));
506 EXPECT_EQ(Status::InvalidArgument(), kvs.Put("K", big_data));
507 }
508
509 } // namespace pw::kvs
510