pw_kvs: Allow specifying max entries and sectors
Move the KeyDescriptor and SectorDescriptor lists to a templated derived
KeyValueStoreBuffer class. This allows multiple KVSs supporting
different numbers of keys and sectors to exist side-by-side.
Change-Id: I43d0647382e763008ae3b08580c736c1978793dd
diff --git a/pw_kvs/debug_cli.cc b/pw_kvs/debug_cli.cc
index 6af7164..7d2c744 100644
--- a/pw_kvs/debug_cli.cc
+++ b/pw_kvs/debug_cli.cc
@@ -48,7 +48,7 @@
FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
test_partition.Erase(0, test_partition.sector_count());
- KeyValueStore kvs(&test_partition, format);
+ KeyValueStoreBuffer<256, 256> kvs(&test_partition, format);
kvs.Init();
while (true) {
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 5ae647b..2fe9504 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -37,30 +37,29 @@
} // namespace
KeyValueStore::KeyValueStore(FlashPartition* partition,
+ Vector<KeyDescriptor>& key_descriptor_list,
+ Vector<SectorDescriptor>& sector_descriptor_list,
const EntryHeaderFormat& format,
const Options& options)
: partition_(*partition),
entry_header_format_(format),
options_(options),
- sectors_(partition_.sector_count()),
+ key_descriptors_(key_descriptor_list),
+ sectors_(sector_descriptor_list),
last_new_sector_(sectors_.data()) {}
Status KeyValueStore::Init() {
INF("Initializing key value store");
- if (kMaxUsableSectors < partition_.sector_count()) {
+ if (partition_.sector_count() > sectors_.max_size()) {
ERR("KVS init failed: kMaxUsableSectors (=%zu) must be at least as "
"large as the number of sectors in the flash partition (=%zu)",
- kMaxUsableSectors,
+ sectors_.max_size(),
partition_.sector_count());
return Status::FAILED_PRECONDITION;
}
- if (kMaxUsableSectors > sectors_.size()) {
- DBG("KeyValueStore::kMaxUsableSectors is %zu sectors larger than needed",
- kMaxUsableSectors - sectors_.size());
- }
-
- // Reset the number of occupied key descriptors; we will fill them later.
+ // Reset descriptor lists. Key descriptors will be filled later.
+ sectors_.resize(partition_.sector_count());
key_descriptors_.clear();
// TODO: init last_new_sector_ to a random sector. Since the on-flash stored
@@ -755,7 +754,7 @@
DBG(" ");
DBG("Flash partition:");
DBG(" Sector count = %zu", partition_.sector_count());
- DBG(" Sector max count = %zu", kMaxUsableSectors);
+ DBG(" Sector max count = %zu", sectors_.max_size());
DBG(" Sectors in use = %zu", sectors_.size());
DBG(" Sector size = %zu", sector_size_bytes);
DBG(" Total size = %zu", partition_.size_bytes());
@@ -763,7 +762,7 @@
DBG(" ");
DBG("Key descriptors:");
DBG(" Entry count = %zu", key_descriptors_.size());
- DBG(" Max entry count = %zu", kMaxEntries);
+ DBG(" Max entry count = %zu", key_descriptors_.max_size());
DBG(" ");
DBG(" # hash version address address (hex)");
for (size_t i = 0; i < key_descriptors_.size(); ++i) {
diff --git a/pw_kvs/key_value_store_fuzz_test.cc b/pw_kvs/key_value_store_fuzz_test.cc
index b751bce..45dc0cc 100644
--- a/pw_kvs/key_value_store_fuzz_test.cc
+++ b/pw_kvs/key_value_store_fuzz_test.cc
@@ -22,6 +22,9 @@
using std::byte;
+constexpr size_t kMaxEntries = 256;
+constexpr size_t kMaxUsableSectors = 256;
+
// 4 x 4k sectors, 16 byte alignment
FakeFlashBuffer<4 * 1024, 4> test_flash(16);
FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
@@ -36,7 +39,7 @@
ASSERT_EQ(Status::OK, kvs_.Init());
}
- KeyValueStore kvs_;
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
};
TEST_F(EmptyInitializedKvs, Put_VaryingKeysAndValues) {
diff --git a/pw_kvs/key_value_store_map_test.cc b/pw_kvs/key_value_store_map_test.cc
index 43da122..6f3a5a4 100644
--- a/pw_kvs/key_value_store_map_test.cc
+++ b/pw_kvs/key_value_store_map_test.cc
@@ -39,6 +39,9 @@
using std::byte;
+constexpr size_t kMaxEntries = 256;
+constexpr size_t kMaxUsableSectors = 256;
+
constexpr std::string_view kChars =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -220,7 +223,7 @@
if (key.empty() || key.size() > Entry::kMaxKeyLength) {
EXPECT_EQ(Status::INVALID_ARGUMENT, result);
- } else if (map_.size() == KeyValueStore::kMaxEntries) {
+ } else if (map_.size() == kvs_.max_size()) {
EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result);
} else if (result == Status::RESOURCE_EXHAUSTED) {
EXPECT_FALSE(map_.empty());
@@ -295,7 +298,7 @@
static FakeFlashBuffer<kParams.sector_size, kParams.sector_count> flash_;
FlashPartition partition_;
- KeyValueStore kvs_;
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
std::unordered_map<std::string, std::string> map_;
std::unordered_set<std::string> deleted_;
unsigned count_ = 0;
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 8a4a6d8..0c29979 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -47,11 +47,14 @@
using std::byte;
+constexpr size_t kMaxEntries = 256;
+constexpr size_t kMaxUsableSectors = 256;
+
// Test the functions in byte_utils.h. Create a byte array with AsBytes and
// ByteStr and check that its contents are correct.
-inline constexpr std::array<char, 2> kTestArray = {'a', 'b'};
+constexpr std::array<char, 2> kTestArray = {'a', 'b'};
-inline constexpr auto kAsBytesTest = AsBytes(
+constexpr auto kAsBytesTest = AsBytes(
'a', uint16_t(1), uint8_t(23), kTestArray, ByteStr("c"), uint64_t(-1));
static_assert(kAsBytesTest.size() == 15);
@@ -233,7 +236,7 @@
ASSERT_EQ(Status::OK, kvs_.Delete(key));
}
- KeyValueStore kvs_;
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
};
uint16_t CalcKvsCrc(const char* key, const void* data, size_t data_len) {
@@ -356,7 +359,8 @@
EXPECT_STREQ(data, "45678");
// Ensure that the re-added key is still present after reinitialization.
- KeyValueStore new_kvs(&test_partition, format);
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> new_kvs(&test_partition,
+ format);
ASSERT_EQ(Status::OK, new_kvs.Init());
EXPECT_EQ(Status::OK, new_kvs.Put("kEy", as_bytes(span("45678"))));
@@ -568,7 +572,8 @@
// Create and initialize the KVS.
constexpr EntryHeaderFormat format{.magic = 0xBAD'C0D3,
.checksum = nullptr};
- KeyValueStore kvs(&flash.partition, format);
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
+ format);
ASSERT_OK(kvs.Init());
// Write the same entry many times.
@@ -608,7 +613,8 @@
// Create and initialize the KVS.
constexpr EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
- KeyValueStore kvs(&flash.partition, format);
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
+ format);
ASSERT_OK(kvs.Init());
// Write the same entry many times.
@@ -634,7 +640,8 @@
// Create and initialize the KVS.
constexpr EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
- KeyValueStore kvs(&flash.partition, format);
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
+ format);
ASSERT_OK(kvs.Init());
// Add two entries with different keys and values.
@@ -662,7 +669,8 @@
// Create and initialize the KVS.
constexpr EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
- KeyValueStore kvs(&flash.partition, format);
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
+ format);
ASSERT_OK(kvs.Init());
// Add two entries with different keys and values.
@@ -783,7 +791,8 @@
// Enable different KVS which should be able to properly setup the same map
// from what is stored in flash.
- static KeyValueStore kvs_local(&test_partition, format);
+ static KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_local(
+ &test_partition, format);
ASSERT_EQ(Status::OK, kvs_local.Init());
EXPECT_EQ(kvs_local.size(), keys.size());
@@ -1169,7 +1178,7 @@
ASSERT_EQ(Status::OK, kvs_.Init());
}
- KeyValueStore kvs_;
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
};
TEST_F(LargeEmptyInitializedKvs, Basic) {
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index c966af5..da40270 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -82,20 +82,16 @@
};
class KeyValueStore {
- private:
+ protected:
struct KeyDescriptor;
public:
- // TODO: Make these configurable
- static constexpr size_t kMaxEntries = 256;
- static constexpr size_t kMaxUsableSectors = 256;
+ // TODO: Make this configurable.
static constexpr size_t kWorkingBufferSizeBytes = (4 * 1024);
- // In the future, will be able to provide additional EntryHeaderFormats for
- // backwards compatibility.
- KeyValueStore(FlashPartition* partition,
- const EntryHeaderFormat& format,
- const Options& options = {});
+ // KeyValueStores are declared as instances of
+ // KeyValueStoreBuffer<MAX_ENTRIES, MAX_SECTORS>, which allocates buffers for
+ // tracking entries and flash sectors.
Status Init();
@@ -234,7 +230,7 @@
size_t empty() const { return size() == 0u; }
- private:
+ protected:
using Address = FlashPartition::Address;
using SectorDescriptor = internal::SectorDescriptor;
@@ -260,6 +256,15 @@
State state;
};
+ // In the future, will be able to provide additional EntryHeaderFormats for
+ // backwards compatibility.
+ KeyValueStore(FlashPartition* partition,
+ Vector<KeyDescriptor>& key_descriptor_list,
+ Vector<SectorDescriptor>& sector_descriptor_list,
+ const EntryHeaderFormat& format,
+ const Options& options);
+
+ private:
static uint32_t HashKey(std::string_view string);
Status FixedSizeGet(std::string_view key,
@@ -359,10 +364,10 @@
// Unordered list of KeyDescriptors. Finding a key requires scanning and
// verifying a match by reading the actual entry.
- Vector<KeyDescriptor, kMaxEntries> key_descriptors_;
+ Vector<KeyDescriptor>& key_descriptors_;
// List of sectors used by this KVS.
- Vector<SectorDescriptor, kMaxUsableSectors> sectors_;
+ Vector<SectorDescriptor>& sectors_;
// The last sector that was selected as the "new empty sector" to write to.
// This last new sector is used as the starting point for the next "find a new
@@ -383,4 +388,20 @@
std::array<std::byte, kWorkingBufferSizeBytes> working_buffer_;
};
+template <size_t kMaxEntries, size_t kMaxUsableSectors>
+class KeyValueStoreBuffer : public KeyValueStore {
+ public:
+ KeyValueStoreBuffer(FlashPartition* partition,
+ const EntryHeaderFormat& format,
+ const Options& options = {})
+ : KeyValueStore(partition, key_descriptors_, sectors_, format, options) {}
+
+ private:
+ static_assert(kMaxEntries > 0u);
+ static_assert(kMaxUsableSectors > 0u);
+
+ Vector<KeyDescriptor, kMaxEntries> key_descriptors_;
+ Vector<SectorDescriptor, kMaxUsableSectors> sectors_;
+};
+
} // namespace pw::kvs