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