pw_kvs: Support multiple entry formats

Change-Id: If25ea00210178b8495585a651d3df6ad8347ee82
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index 571e44f..218d199 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -29,6 +29,7 @@
         "checksum.cc",
         "entry.cc",
         "flash_memory.cc",
+        "format.cc",
         "key_value_store.cc",
         "public/pw_kvs/internal/entry.h",
         "public/pw_kvs/internal/hash.h",
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index b191dba..d8396b5 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -34,6 +34,7 @@
     "checksum.cc",
     "entry.cc",
     "flash_memory.cc",
+    "format.cc",
     "key_value_store.cc",
     "public/pw_kvs/internal/entry.h",
     "public/pw_kvs/internal/hash.h",
diff --git a/pw_kvs/entry.cc b/pw_kvs/entry.cc
index 894b2ce..2c19fae 100644
--- a/pw_kvs/entry.cc
+++ b/pw_kvs/entry.cc
@@ -25,7 +25,10 @@
 using std::byte;
 using std::string_view;
 
-Status Entry::Read(FlashPartition& partition, Address address, Entry* entry) {
+Status Entry::Read(FlashPartition& partition,
+                   Address address,
+                   const internal::EntryFormats& formats,
+                   Entry* entry) {
   EntryHeader header;
   TRY(partition.Read(address, sizeof(header), &header));
 
@@ -36,7 +39,15 @@
     return Status::DATA_LOSS;
   }
 
-  *entry = Entry(&partition, address, header);
+  const EntryFormat* format = formats.Find(header.magic);
+  if (format == nullptr) {
+    PW_LOG_ERROR("Found corrupt magic: %" PRIx32 " at address %zx",
+                 header.magic,
+                 size_t(address));
+    return Status::DATA_LOSS;
+  }
+
+  *entry = Entry(&partition, address, *format, header);
   return Status::OK;
 }
 
@@ -61,6 +72,7 @@
              uint32_t transaction_id)
     : Entry(&partition,
             address,
+            format,
             {.magic = format.magic,
              .checksum = 0,
              .alignment_units =
@@ -68,8 +80,8 @@
              .key_length_bytes = static_cast<uint8_t>(key.size()),
              .value_size_bytes = value_size_bytes,
              .transaction_id = transaction_id}) {
-  if (format.checksum != nullptr) {
-    span<const byte> checksum = CalculateChecksum(format.checksum, key, value);
+  if (checksum_ != nullptr) {
+    span<const byte> checksum = CalculateChecksum(key, value);
     std::memcpy(&header_.checksum,
                 checksum.data(),
                 std::min(checksum.size(), sizeof(header_.checksum)));
@@ -104,17 +116,15 @@
   return StatusWithSize(read_size);
 }
 
-Status Entry::VerifyChecksum(ChecksumAlgorithm* algorithm,
-                             string_view key,
-                             span<const byte> value) const {
-  if (algorithm == nullptr) {
+Status Entry::VerifyChecksum(string_view key, span<const byte> value) const {
+  if (checksum_ == nullptr) {
     return checksum() == 0 ? Status::OK : Status::DATA_LOSS;
   }
-  CalculateChecksum(algorithm, key, value);
-  return algorithm->Verify(checksum_bytes());
+  CalculateChecksum(key, value);
+  return checksum_->Verify(checksum_bytes());
 }
 
-Status Entry::VerifyChecksumInFlash(ChecksumAlgorithm* algorithm) const {
+Status Entry::VerifyChecksumInFlash() const {
   // Read the entire entry piece-by-piece into a small buffer. If the entry is
   // 32 B or less, only one read is required.
   union {
@@ -137,18 +147,18 @@
     return Status::DATA_LOSS;
   }
 
-  if (algorithm == nullptr) {
+  if (checksum_ == nullptr) {
     return checksum() == 0 ? Status::OK : Status::DATA_LOSS;
   }
 
   // The checksum is calculated as if the header's checksum field were 0.
   header_to_verify.checksum = 0;
 
-  algorithm->Reset();
+  checksum_->Reset();
 
   while (true) {
     // Add the chunk in the buffer to the checksum.
-    algorithm->Update(buffer, read_size);
+    checksum_->Update(buffer, read_size);
 
     bytes_to_read -= read_size;
     if (bytes_to_read == 0u) {
@@ -161,8 +171,8 @@
     TRY(partition().Read(read_address, read_size, buffer));
   }
 
-  algorithm->Finish();
-  return algorithm->Verify(checksum_bytes());
+  checksum_->Finish();
+  return checksum_->Verify(checksum_bytes());
 }
 
 void Entry::DebugLog() {
@@ -176,18 +186,17 @@
   PW_LOG_DEBUG("   Alignment    = 0x%zx", size_t(alignment_bytes()));
 }
 
-span<const byte> Entry::CalculateChecksum(ChecksumAlgorithm* algorithm,
-                                          const string_view key,
+span<const byte> Entry::CalculateChecksum(const string_view key,
                                           span<const byte> value) const {
-  algorithm->Reset();
+  checksum_->Reset();
 
   {
     EntryHeader header_for_checksum = header_;
     header_for_checksum.checksum = 0;
 
-    algorithm->Update(&header_for_checksum, sizeof(header_for_checksum));
-    algorithm->Update(as_bytes(span(key)));
-    algorithm->Update(value);
+    checksum_->Update(&header_for_checksum, sizeof(header_for_checksum));
+    checksum_->Update(as_bytes(span(key)));
+    checksum_->Update(value);
   }
 
   // Update the checksum with 0s to pad the entry to its alignment boundary.
@@ -196,11 +205,11 @@
 
   while (padding_to_add > 0u) {
     const size_t chunk_size = std::min(padding_to_add, sizeof(padding));
-    algorithm->Update(padding, chunk_size);
+    checksum_->Update(padding, chunk_size);
     padding_to_add -= chunk_size;
   }
 
-  return algorithm->Finish();
+  return checksum_->Finish();
 }
 
 }  // namespace pw::kvs::internal
diff --git a/pw_kvs/entry_test.cc b/pw_kvs/entry_test.cc
index 64b1718..7d8a9c8 100644
--- a/pw_kvs/entry_test.cc
+++ b/pw_kvs/entry_test.cc
@@ -43,6 +43,7 @@
     for (size_t value : {size_t(0), align - 1, align, align + 1, 2 * align}) {
       Entry entry =
           Entry::Valid(partition, 0, kFormat, "k", {nullptr, value}, 0);
+
       ASSERT_EQ(AlignUp(sizeof(EntryHeader) + 1 /* key */ + value, align),
                 entry.size());
     }
@@ -77,8 +78,10 @@
   EXPECT_EQ(entry.transaction_id(), 123u);
 }
 
+constexpr uint32_t kMagicWithChecksum = 0x600df00d;
+
 constexpr auto kHeader1 = ByteStr(
-    "\x0d\xf0\x0d\x60"  // magic
+    "\x0d\xf0\x0d\x60"  // magic (600df00d)
     "\xC5\x65\x00\x00"  // checksum (CRC16)
     "\x01"              // alignment
     "\x05"              // key length
@@ -93,10 +96,14 @@
 constexpr auto kEntry1 = AsBytes(kHeader1, kKey1, kValue1, kPadding1);
 static_assert(kEntry1.size() == 32);
 
+ChecksumCrc16 checksum;
+constexpr EntryFormat kFormatWithChecksum{kMagicWithChecksum, &checksum};
+constexpr internal::EntryFormats kFormats(kFormatWithChecksum);
+
 class ValidEntryInFlash : public ::testing::Test {
  protected:
   ValidEntryInFlash() : flash_(kEntry1), partition_(&flash_) {
-    EXPECT_EQ(Status::OK, Entry::Read(partition_, 0, &entry_));
+    EXPECT_EQ(Status::OK, Entry::Read(partition_, 0, kFormats, &entry_));
   }
 
   FakeFlashBuffer<1024, 4> flash_;
@@ -105,13 +112,12 @@
 };
 
 TEST_F(ValidEntryInFlash, PassesChecksumVerification) {
-  ChecksumCrc16 checksum;
-  EXPECT_EQ(Status::OK, entry_.VerifyChecksumInFlash(&checksum));
-  EXPECT_EQ(Status::OK, entry_.VerifyChecksum(&checksum, "key45", kValue1));
+  EXPECT_EQ(Status::OK, entry_.VerifyChecksumInFlash());
+  EXPECT_EQ(Status::OK, entry_.VerifyChecksum("key45", kValue1));
 }
 
 TEST_F(ValidEntryInFlash, HeaderContents) {
-  EXPECT_EQ(entry_.magic(), 0x600DF00Du);
+  EXPECT_EQ(entry_.magic(), kMagicWithChecksum);
   EXPECT_EQ(entry_.key_length(), 5u);
   EXPECT_EQ(entry_.value_size(), 6u);
   EXPECT_EQ(entry_.transaction_id(), 0x96979899u);
@@ -187,11 +193,9 @@
 TEST(ValidEntry, Write) {
   FakeFlashBuffer<1024, 4> flash;
   FlashPartition partition(&flash, 0, flash.sector_count(), 32);
-  ChecksumCrc16 checksum;
-  const EntryFormat format{0x600DF00Du, &checksum};
 
-  Entry entry =
-      Entry::Valid(partition, 53, format, "key45", kValue1, 0x96979899u);
+  Entry entry = Entry::Valid(
+      partition, 53, kFormatWithChecksum, "key45", kValue1, 0x96979899u);
 
   auto result = entry.Write("key45", kValue1);
   EXPECT_EQ(Status::OK, result.status());
@@ -215,7 +219,7 @@
  protected:
   TombstoneEntryInFlash()
       : flash_(AsBytes(kHeader2, kKeyAndPadding2)), partition_(&flash_) {
-    EXPECT_EQ(Status::OK, Entry::Read(partition_, 0, &entry_));
+    EXPECT_EQ(Status::OK, Entry::Read(partition_, 0, kFormats, &entry_));
   }
 
   FakeFlashBuffer<1024, 4> flash_;
@@ -224,13 +228,12 @@
 };
 
 TEST_F(TombstoneEntryInFlash, PassesChecksumVerification) {
-  ChecksumCrc16 checksum;
-  EXPECT_EQ(Status::OK, entry_.VerifyChecksumInFlash(&checksum));
-  EXPECT_EQ(Status::OK, entry_.VerifyChecksum(&checksum, "K", {}));
+  EXPECT_EQ(Status::OK, entry_.VerifyChecksumInFlash());
+  EXPECT_EQ(Status::OK, entry_.VerifyChecksum("K", {}));
 }
 
 TEST_F(TombstoneEntryInFlash, HeaderContents) {
-  EXPECT_EQ(entry_.magic(), 0x600DF00Du);
+  EXPECT_EQ(entry_.magic(), kMagicWithChecksum);
   EXPECT_EQ(entry_.key_length(), 1u);
   EXPECT_EQ(entry_.value_size(), 0u);
   EXPECT_EQ(entry_.transaction_id(), 0x03020100u);
@@ -258,9 +261,9 @@
   FakeFlashBuffer<1024, 4> flash;
   FlashPartition partition(&flash);
   ChecksumCrc16 checksum;
-  const EntryFormat format{0x600DF00Du, &checksum};
 
-  Entry entry = Entry::Tombstone(partition, 16, format, "K", 0x03020100);
+  Entry entry =
+      Entry::Tombstone(partition, 16, kFormatWithChecksum, "K", 0x03020100);
 
   auto result = entry.Write("K", {});
   EXPECT_EQ(Status::OK, result.status());
@@ -275,15 +278,19 @@
   FakeFlashBuffer<1024, 4> flash(kEntry1);
   FlashPartition partition(&flash);
   Entry entry;
-  ASSERT_EQ(Status::OK, Entry::Read(partition, 0, &entry));
 
-  EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksumInFlash(nullptr));
-  EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksum(nullptr, {}, {}));
+  const EntryFormat format{kMagicWithChecksum, nullptr};
+  const internal::EntryFormats formats(format);
+
+  ASSERT_EQ(Status::OK, Entry::Read(partition, 0, formats, &entry));
+
+  EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksumInFlash());
+  EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksum({}, {}));
 
   std::memset(&flash.buffer()[4], 0, 4);  // set the checksum field to 0
-  ASSERT_EQ(Status::OK, Entry::Read(partition, 0, &entry));
-  EXPECT_EQ(Status::OK, entry.VerifyChecksumInFlash(nullptr));
-  EXPECT_EQ(Status::OK, entry.VerifyChecksum(nullptr, {}, {}));
+  ASSERT_EQ(Status::OK, Entry::Read(partition, 0, formats, &entry));
+  EXPECT_EQ(Status::OK, entry.VerifyChecksumInFlash());
+  EXPECT_EQ(Status::OK, entry.VerifyChecksum({}, {}));
 }
 
 TEST(Entry, Checksum_ChecksPadding) {
@@ -291,17 +298,16 @@
       AsBytes(kHeader1, kKey1, kValue1, ByteStr("\0\0\0\0\1")));
   FlashPartition partition(&flash);
   Entry entry;
-  ASSERT_EQ(Status::OK, Entry::Read(partition, 0, &entry));
+  ASSERT_EQ(Status::OK, Entry::Read(partition, 0, kFormats, &entry));
 
   // Last byte in padding is a 1; should fail.
-  ChecksumCrc16 checksum;
-  EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksumInFlash(&checksum));
+  EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksumInFlash());
 
   // The in-memory verification fills in 0s for the padding.
-  EXPECT_EQ(Status::OK, entry.VerifyChecksum(&checksum, "key45", kValue1));
+  EXPECT_EQ(Status::OK, entry.VerifyChecksum("key45", kValue1));
 
   flash.buffer()[kEntry1.size() - 1] = byte{0};
-  EXPECT_EQ(Status::OK, entry.VerifyChecksumInFlash(&checksum));
+  EXPECT_EQ(Status::OK, entry.VerifyChecksumInFlash());
 }
 }  // namespace
 }  // namespace pw::kvs::internal
diff --git a/pw_kvs/format.cc b/pw_kvs/format.cc
new file mode 100644
index 0000000..e1756a7
--- /dev/null
+++ b/pw_kvs/format.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_kvs/format.h"
+
+namespace pw::kvs::internal {
+
+const EntryFormat* EntryFormats::Find(const uint32_t magic) const {
+  for (const EntryFormat& format : formats_) {
+    if (format.magic == magic) {
+      return &format;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace pw::kvs::internal
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 8e859f2..eb1631f 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -19,6 +19,8 @@
 #include <cstring>
 #include <type_traits>
 
+#include "pw_kvs/format.h"
+
 #define PW_LOG_USE_ULTRA_SHORT_NAMES 1
 #include "pw_kvs/internal/entry.h"
 #include "pw_kvs_private/macros.h"
@@ -39,10 +41,10 @@
 KeyValueStore::KeyValueStore(FlashPartition* partition,
                              Vector<KeyDescriptor>& key_descriptor_list,
                              Vector<SectorDescriptor>& sector_descriptor_list,
-                             const EntryFormat& format,
+                             span<const EntryFormat> formats,
                              const Options& options)
     : partition_(*partition),
-      entry_header_format_(format),
+      formats_(formats),
       key_descriptors_(key_descriptor_list),
       sectors_(sector_descriptor_list),
       options_(options) {
@@ -169,7 +171,7 @@
   for (KeyDescriptor& key_descriptor : key_descriptors_) {
     for (auto& address : key_descriptor.addresses()) {
       Entry entry;
-      TRY(Entry::Read(partition_, address, &entry));
+      TRY(Entry::Read(partition_, key_descriptor.address(), formats_, &entry));
       SectorFromAddress(address)->AddValidBytes(entry.size());
     }
     if (key_descriptor.IsNewerThan(last_transaction_id_)) {
@@ -242,24 +244,14 @@
 Status KeyValueStore::LoadEntry(Address entry_address,
                                 Address* next_entry_address) {
   Entry entry;
-  TRY(Entry::Read(partition_, entry_address, &entry));
-
-  // TODO: Handle multiple magics for formats that have changed.
-  if (entry.magic() != entry_header_format_.magic) {
-    // TODO: It may be cleaner to have some logging helpers for these cases.
-    ERR("Found corrupt magic: %zx; expecting %zx; at address %zx",
-        size_t(entry.magic()),
-        size_t(entry_header_format_.magic),
-        size_t(entry_address));
-    return Status::DATA_LOSS;
-  }
+  TRY(Entry::Read(partition_, entry_address, formats_, &entry));
 
   // Read the key from flash & validate the entry (which reads the value).
   Entry::KeyBuffer key_buffer;
   TRY_ASSIGN(size_t key_length, entry.ReadKey(key_buffer));
   const string_view key(key_buffer.data(), key_length);
 
-  TRY(entry.VerifyChecksumInFlash(entry_header_format_.checksum));
+  TRY(entry.VerifyChecksumInFlash());
 
   // A valid entry was found, so update the next entry address before doing any
   // of the checks that happen in AppendNewOrOverwriteStaleExistingDescriptor().
@@ -284,10 +276,9 @@
   for (Address address = AlignUp(start_address, Entry::kMinAlignmentBytes);
        AddressInSector(sector, address);
        address += Entry::kMinAlignmentBytes) {
-    // TODO: Handle multiple magics for formats that have changed.
     uint32_t magic;
     TRY(partition_.Read(address, as_writable_bytes(span(&magic, 1))));
-    if (magic == entry_header_format_.magic) {
+    if (formats_.KnownMagic(magic)) {
       DBG("Found entry magic at address %zx", size_t(address));
       *next_entry_address = address;
       return Status::OK;
@@ -419,7 +410,9 @@
   key_buffer_.fill('\0');
 
   Entry entry;
-  if (Entry::Read(kvs_.partition_, descriptor_->address(), &entry).ok()) {
+  if (Entry::Read(
+          kvs_.partition_, descriptor_->address(), kvs_.formats_, &entry)
+          .ok()) {
     entry.ReadKey(key_buffer_);
   }
 }
@@ -469,12 +462,13 @@
                                   span<std::byte> value_buffer,
                                   size_t offset_bytes) const {
   Entry entry;
-  TRY_WITH_SIZE(Entry::Read(partition_, descriptor.address(), &entry));
+  TRY_WITH_SIZE(
+      Entry::Read(partition_, descriptor.address(), formats_, &entry));
 
   StatusWithSize result = entry.ReadValue(value_buffer, offset_bytes);
   if (result.ok() && options_.verify_on_read && offset_bytes == 0u) {
-    Status verify_result = entry.VerifyChecksum(
-        entry_header_format_.checksum, key, value_buffer.first(result.size()));
+    Status verify_result =
+        entry.VerifyChecksum(key, value_buffer.first(result.size()));
     if (!verify_result.ok()) {
       std::memset(value_buffer.data(), 0, result.size());
       return StatusWithSize(verify_result, 0);
@@ -517,7 +511,8 @@
 
 StatusWithSize KeyValueStore::ValueSize(const KeyDescriptor& descriptor) const {
   Entry entry;
-  TRY_WITH_SIZE(Entry::Read(partition_, descriptor.address(), &entry));
+  TRY_WITH_SIZE(
+      Entry::Read(partition_, descriptor.address(), formats_, &entry));
 
   return StatusWithSize(entry.value_size());
 }
@@ -589,7 +584,8 @@
                                                span<const byte> value) {
   // Find the original entry and sector to update the sector's valid_bytes.
   Entry original_entry;
-  TRY(Entry::Read(partition_, key_descriptor->address(), &original_entry));
+  TRY(Entry::Read(
+      partition_, key_descriptor->address(), formats_, &original_entry));
 
   SectorDescriptor* sector;
   TRY(FindOrRecoverSectorWithSpace(&sector,
@@ -650,7 +646,7 @@
   // store the key and value in the TempEntry stored in the static allocated
   // working_buffer_.
   Entry entry;
-  TRY(Entry::Read(partition_, key_descriptor.address(), &entry));
+  TRY(Entry::Read(partition_, key_descriptor.address(), formats_, &entry));
 
   TRY_ASSIGN(size_t key_length, entry.ReadKey(key_buffer));
   string_view key = string_view(key_buffer.data(), key_length);
@@ -661,7 +657,7 @@
   }
 
   const span value = span(value_buffer.data(), result.size());
-  TRY(entry.VerifyChecksum(entry_header_format_.checksum, key, value));
+  TRY(entry.VerifyChecksum(key, value));
 
   // Find a new sector for the entry and write it to the new location. For
   // relocation the find should not not be a sector already containing the key
@@ -927,7 +923,7 @@
   }
 
   if (options_.verify_on_write) {
-    TRY(entry.VerifyChecksumInFlash(entry_header_format_.checksum));
+    TRY(entry.VerifyChecksumInFlash());
   }
 
   // Entry was written successfully; update the key descriptor and the sector
@@ -957,11 +953,11 @@
 
   if (state == KeyDescriptor::kDeleted) {
     return Entry::Tombstone(
-        partition_, address, entry_header_format_, key, last_transaction_id_);
+        partition_, address, formats_.primary(), key, last_transaction_id_);
   }
   return Entry::Valid(partition_,
                       address,
-                      entry_header_format_,
+                      formats_.primary(),
                       key,
                       value,
                       last_transaction_id_);
diff --git a/pw_kvs/key_value_store_binary_format_test.cc b/pw_kvs/key_value_store_binary_format_test.cc
index d429992..19f0354 100644
--- a/pw_kvs/key_value_store_binary_format_test.cc
+++ b/pw_kvs/key_value_store_binary_format_test.cc
@@ -18,6 +18,7 @@
 
 #include "gtest/gtest.h"
 #include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/format.h"
 #include "pw_kvs/in_memory_fake_flash.h"
 #include "pw_kvs/internal/hash.h"
 #include "pw_kvs/key_value_store.h"
@@ -32,27 +33,31 @@
 constexpr size_t kMaxEntries = 256;
 constexpr size_t kMaxUsableSectors = 256;
 
-constexpr uint32_t SimpleChecksum(span<const byte> data, uint32_t state = 0) {
+constexpr uint32_t SimpleChecksum(span<const byte> data, uint32_t state) {
   for (byte b : data) {
     state += uint32_t(b);
   }
   return state;
 }
 
-class SimpleChecksumAlgorithm final : public ChecksumAlgorithm {
+template <typename State>
+class ChecksumFunction final : public ChecksumAlgorithm {
  public:
-  SimpleChecksumAlgorithm()
-      : ChecksumAlgorithm(as_bytes(span(&checksum_, 1))) {}
+  ChecksumFunction(State (&algorithm)(span<const byte>, State))
+      : ChecksumAlgorithm(as_bytes(span(&state_, 1))), algorithm_(algorithm) {}
 
-  void Reset() override { checksum_ = 0; }
+  void Reset() override { state_ = {}; }
 
   void Update(span<const byte> data) override {
-    checksum_ = SimpleChecksum(data, checksum_);
+    state_ = algorithm_(data, state_);
   }
 
  private:
-  uint32_t checksum_;
-} checksum;
+  State state_;
+  State (&algorithm_)(span<const byte>, State);
+};
+
+ChecksumFunction<uint32_t> checksum(SimpleChecksum);
 
 // Returns a buffer containing the necessary padding for an entry.
 template <size_t kAlignmentBytes, size_t kKeyLength, size_t kValueSize>
@@ -63,7 +68,8 @@
 }
 
 // Creates a buffer containing a valid entry at compile time.
-template <size_t kAlignmentBytes = sizeof(internal::EntryHeader),
+template <uint32_t (*kChecksum)(span<const byte>, uint32_t) = &SimpleChecksum,
+          size_t kAlignmentBytes = sizeof(internal::EntryHeader),
           size_t kKeyLengthWithNull,
           size_t kValueSize>
 constexpr auto MakeValidEntry(uint32_t magic,
@@ -83,7 +89,7 @@
                       EntryPadding<kAlignmentBytes, kKeyLength, kValueSize>());
 
   // Calculate the checksum
-  uint32_t checksum = SimpleChecksum(data);
+  uint32_t checksum = kChecksum(data, 0);
   for (size_t i = 0; i < sizeof(checksum); ++i) {
     data[4 + i] = byte(checksum & 0xff);
     checksum >>= 8;
@@ -93,7 +99,6 @@
 }
 
 constexpr uint32_t kMagic = 0xc001beef;
-constexpr EntryFormat kFormat{.magic = kMagic, .checksum = &checksum};
 constexpr Options kNoGcOptions{
     .gc_on_write = GargbageCollectOnWrite::kDisabled,
     .verify_on_read = true,
@@ -110,7 +115,9 @@
   KvsErrorHandling()
       : flash_(internal::Entry::kMinAlignmentBytes),
         partition_(&flash_),
-        kvs_(&partition_, kFormat, kNoGcOptions) {}
+        kvs_(&partition_,
+             {.magic = kMagic, .checksum = &checksum},
+             kNoGcOptions) {}
 
   void InitFlashTo(span<const byte> contents) {
     partition_.Erase();
@@ -287,5 +294,94 @@
   EXPECT_EQ(stats.writable_bytes, 512u * 3 - 32 * 2);
 }
 
+constexpr uint32_t kAltMagic = 0xbadD00D;
+
+constexpr uint32_t AltChecksum(span<const byte> data, uint32_t state) {
+  for (byte b : data) {
+    state = (state << 8) | uint32_t(byte(state >> 24) ^ b);
+  }
+  return state;
+}
+
+ChecksumFunction<uint32_t> alt_checksum(AltChecksum);
+
+constexpr auto kAltEntry =
+    MakeValidEntry<AltChecksum>(kAltMagic, 32, "A Key", ByteStr("XD"));
+
+constexpr uint32_t NoChecksum(span<const byte>, uint32_t) { return 0; }
+constexpr uint32_t kNoChecksumMagic = 0x6000061e;
+
+constexpr auto kNoChecksumEntry =
+    MakeValidEntry<NoChecksum>(kNoChecksumMagic, 64, "kee", ByteStr("O_o"));
+
+class InitializedMultiMagicKvs : public ::testing::Test {
+ protected:
+  static constexpr auto kInitialContents =
+      AsBytes(kNoChecksumEntry, kEntry1, kAltEntry, kEntry2, kEntry3);
+
+  InitializedMultiMagicKvs()
+      : flash_(internal::Entry::kMinAlignmentBytes),
+        partition_(&flash_),
+        kvs_(&partition_,
+             {{
+                 {.magic = kMagic, .checksum = &checksum},
+                 {.magic = kAltMagic, .checksum = &alt_checksum},
+                 {.magic = kNoChecksumMagic, .checksum = nullptr},
+             }},
+             kNoGcOptions) {
+    partition_.Erase();
+    std::memcpy(flash_.buffer().data(),
+                kInitialContents.data(),
+                kInitialContents.size());
+
+    EXPECT_EQ(Status::OK, kvs_.Init());
+  }
+
+  FakeFlashBuffer<512, 4, 3> flash_;
+  FlashPartition partition_;
+  KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 3> kvs_;
+};
+
+#define ASSERT_CONTAINS_ENTRY(key, str_value)                          \
+  do {                                                                 \
+    char val[sizeof(str_value)] = {};                                  \
+    StatusWithSize stat = kvs_.Get(key, as_writable_bytes(span(val))); \
+    ASSERT_EQ(Status::OK, stat.status());                              \
+    ASSERT_EQ(sizeof(str_value) - 1, stat.size());                     \
+    ASSERT_STREQ(str_value, val);                                      \
+  } while (0)
+
+TEST_F(InitializedMultiMagicKvs, AllEntriesArePresent) {
+  ASSERT_CONTAINS_ENTRY("key1", "value1");
+  ASSERT_CONTAINS_ENTRY("k2", "value2");
+  ASSERT_CONTAINS_ENTRY("k3y", "value3");
+  ASSERT_CONTAINS_ENTRY("A Key", "XD");
+  ASSERT_CONTAINS_ENTRY("kee", "O_o");
+}
+
+TEST_F(InitializedMultiMagicKvs, PutNewEntry_UsesFirstFormat) {
+  EXPECT_EQ(Status::OK, kvs_.Put("new key", ByteStr("abcd?")));
+
+  constexpr auto kNewEntry =
+      MakeValidEntry(kMagic, 65, "new key", ByteStr("abcd?"));
+  EXPECT_EQ(0,
+            std::memcmp(kNewEntry.data(),
+                        flash_.buffer().data() + kInitialContents.size(),
+                        kNewEntry.size()));
+  ASSERT_CONTAINS_ENTRY("new key", "abcd?");
+}
+
+TEST_F(InitializedMultiMagicKvs, PutExistingEntry_UsesFirstFormat) {
+  EXPECT_EQ(Status::OK, kvs_.Put("A Key", ByteStr("New value!")));
+
+  constexpr auto kNewEntry =
+      MakeValidEntry(kMagic, 65, "A Key", ByteStr("New value!"));
+  EXPECT_EQ(0,
+            std::memcmp(kNewEntry.data(),
+                        flash_.buffer().data() + kInitialContents.size(),
+                        kNewEntry.size()));
+  ASSERT_CONTAINS_ENTRY("A Key", "New value!");
+}
+
 }  // namespace
 }  // namespace pw::kvs
diff --git a/pw_kvs/key_value_store_fuzz_test.cc b/pw_kvs/key_value_store_fuzz_test.cc
index ea30bd9..05384bd 100644
--- a/pw_kvs/key_value_store_fuzz_test.cc
+++ b/pw_kvs/key_value_store_fuzz_test.cc
@@ -30,11 +30,11 @@
 FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
 
 ChecksumCrc16 checksum;
-constexpr EntryFormat kFormat{.magic = 0xBAD'C0D3, .checksum = &checksum};
 
 class EmptyInitializedKvs : public ::testing::Test {
  protected:
-  EmptyInitializedKvs() : kvs_(&test_partition, kFormat) {
+  EmptyInitializedKvs()
+      : kvs_(&test_partition, {.magic = 0xBAD'C0D3, .checksum = &checksum}) {
     test_partition.Erase(0, test_partition.sector_count());
     ASSERT_EQ(Status::OK, kvs_.Init());
   }
diff --git a/pw_kvs/key_value_store_map_test.cc b/pw_kvs/key_value_store_map_test.cc
index 99b76d2..a6b2555 100644
--- a/pw_kvs/key_value_store_map_test.cc
+++ b/pw_kvs/key_value_store_map_test.cc
@@ -78,15 +78,12 @@
 template <const TestParameters& kParams>
 class KvsTester {
  public:
-  static constexpr EntryFormat kFormat{.magic = 0xBAD'C0D3,
-                                       .checksum = nullptr};
-
   KvsTester()
       : partition_(&flash_,
                    kParams.partition_start_sector,
                    kParams.partition_sector_count,
                    kParams.partition_alignment),
-        kvs_(&partition_, kFormat) {
+        kvs_(&partition_, {.magic = 0xBAD'C0D3, .checksum = nullptr}) {
     EXPECT_EQ(Status::OK, partition_.Erase());
     Status result = kvs_.Init();
     EXPECT_EQ(Status::OK, result);
diff --git a/pw_kvs/public/pw_kvs/format.h b/pw_kvs/public/pw_kvs/format.h
index 8a206c9..a2d843a 100644
--- a/pw_kvs/public/pw_kvs/format.h
+++ b/pw_kvs/public/pw_kvs/format.h
@@ -13,7 +13,15 @@
 // the License.
 #pragma once
 
+#include <cstdint>
+
+#include "pw_kvs/checksum.h"
+#include "pw_span/span.h"
+
 namespace pw::kvs {
+
+struct EntryFormat;
+
 namespace internal {
 
 // Disk format of the header used for each key-value entry.
@@ -44,6 +52,26 @@
 
 static_assert(sizeof(EntryHeader) == 16, "EntryHeader must not have padding");
 
+// This class wraps EntryFormat instances to support having multiple
+// simultaneously supported formats.
+class EntryFormats {
+ public:
+  explicit constexpr EntryFormats(span<const EntryFormat> formats)
+      : formats_(formats) {}
+
+  explicit constexpr EntryFormats(const EntryFormat& format)
+      : formats_(&format, 1) {}
+
+  const EntryFormat& primary() const { return formats_.front(); }
+
+  bool KnownMagic(uint32_t magic) const { return Find(magic) != nullptr; }
+
+  const EntryFormat* Find(uint32_t magic) const;
+
+ private:
+  const span<const EntryFormat> formats_;
+};
+
 }  // namespace internal
 
 // The EntryFormat defines properties of KVS entries that use a particular magic
diff --git a/pw_kvs/public/pw_kvs/internal/entry.h b/pw_kvs/public/pw_kvs/internal/entry.h
index 89b98e9..773e4c3 100644
--- a/pw_kvs/public/pw_kvs/internal/entry.h
+++ b/pw_kvs/public/pw_kvs/internal/entry.h
@@ -47,7 +47,10 @@
   //   NOT_FOUND: read the header, but the data appears to be erased
   //   DATA_LOSS: read the header, but it contained invalid data
   //
-  static Status Read(FlashPartition& partition, Address address, Entry* entry);
+  static Status Read(FlashPartition& partition,
+                     Address address,
+                     const internal::EntryFormats& formats,
+                     Entry* entry);
 
   // Reads a key into a buffer, which must be at least key_length bytes.
   static Status ReadKey(FlashPartition& partition,
@@ -112,11 +115,10 @@
   StatusWithSize ReadValue(span<std::byte> buffer,
                            size_t offset_bytes = 0) const;
 
-  Status VerifyChecksum(ChecksumAlgorithm* algorithm,
-                        std::string_view key,
+  Status VerifyChecksum(std::string_view key,
                         span<const std::byte> value) const;
 
-  Status VerifyChecksumInFlash(ChecksumAlgorithm* algorithm) const;
+  Status VerifyChecksumInFlash() const;
 
   // Calculates the total size of an entry, including padding.
   static size_t size(const FlashPartition& partition,
@@ -176,15 +178,18 @@
 
   constexpr Entry(FlashPartition* partition,
                   Address address,
+                  const EntryFormat& format,
                   EntryHeader header)
-      : partition_(partition), address_(address), header_(header) {}
+      : partition_(partition),
+        address_(address),
+        checksum_(format.checksum),
+        header_(header) {}
 
   span<const std::byte> checksum_bytes() const {
     return as_bytes(span(&header_.checksum, 1));
   }
 
-  span<const std::byte> CalculateChecksum(ChecksumAlgorithm* algorithm,
-                                          std::string_view key,
+  span<const std::byte> CalculateChecksum(std::string_view key,
                                           span<const std::byte> value) const;
 
   static constexpr uint8_t alignment_bytes_to_units(size_t alignment_bytes) {
@@ -193,6 +198,7 @@
 
   FlashPartition* partition_;
   Address address_;
+  ChecksumAlgorithm* checksum_;
   EntryHeader header_;
 };
 
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 5451bc4..8b6cecf 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -297,7 +297,7 @@
   KeyValueStore(FlashPartition* partition,
                 Vector<KeyDescriptor>& key_descriptor_list,
                 Vector<SectorDescriptor>& sector_descriptor_list,
-                const EntryFormat& format,
+                span<const EntryFormat> format,
                 const Options& options);
 
  private:
@@ -426,7 +426,7 @@
   void LogKeyDescriptor() const;
 
   FlashPartition& partition_;
-  const EntryFormat entry_header_format_;
+  const internal::EntryFormats formats_;
 
   // Unordered list of KeyDescriptors. Finding a key requires scanning and
   // verifying a match by reading the actual entry.
@@ -457,20 +457,42 @@
   std::array<std::byte, kWorkingBufferSizeBytes> working_buffer_;
 };
 
-template <size_t kMaxEntries, size_t kMaxUsableSectors>
+template <size_t kMaxEntries,
+          size_t kMaxUsableSectors,
+          size_t kEntryFormats = 1>
 class KeyValueStoreBuffer : public KeyValueStore {
  public:
+  // Constructs a KeyValueStore on the partition, with support for one
+  // EntryFormat (kEntryFormats must be 1).
   KeyValueStoreBuffer(FlashPartition* partition,
                       const EntryFormat& format,
                       const Options& options = {})
-      : KeyValueStore(partition, key_descriptors_, sectors_, format, options) {}
+      : KeyValueStoreBuffer(
+            partition,
+            span(reinterpret_cast<const EntryFormat (&)[1]>(format)),
+            options) {
+    static_assert(kEntryFormats == 1,
+                  "kEntryFormats EntryFormats must be specified");
+  }
+
+  // Constructs a KeyValueStore on the partition. Supports multiple entry
+  // formats. The first EntryFormat is used for new entries.
+  KeyValueStoreBuffer(FlashPartition* partition,
+                      span<const EntryFormat, kEntryFormats> formats,
+                      const Options& options = {})
+      : KeyValueStore(
+            partition, key_descriptors_, sectors_, formats_, options) {
+    std::copy(formats.begin(), formats.end(), formats_.begin());
+  }
 
  private:
   static_assert(kMaxEntries > 0u);
   static_assert(kMaxUsableSectors > 0u);
+  static_assert(kEntryFormats > 0u);
 
   Vector<KeyDescriptor, kMaxEntries> key_descriptors_;
   Vector<SectorDescriptor, kMaxUsableSectors> sectors_;
+  std::array<EntryFormat, kEntryFormats> formats_;
 };
 
 }  // namespace pw::kvs