pw_kvs: Initial commit of new KVS design

Builds, but lots more work needed.

Fun fact: this code was written collaboratively in a Google Doc.

Change-Id: I8a89c5d0fdc71ec28cf432350e65d17e24a6f25c
diff --git a/pw_kvs/public/pw_kvs/assert.h b/pw_kvs/public/pw_kvs/assert.h
deleted file mode 100644
index cc20a61..0000000
--- a/pw_kvs/public/pw_kvs/assert.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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.
-#pragma once
-
-#include <algorithm>
-#include <type_traits>
-
-// Compares the provided value to nullptr and returns it. This is intended to be
-// used as part of another statement.
-#define CHECK_NOTNULL(value) \
-  ::pw::log::CheckNotNull("", __LINE__, #value " != nullptr", value)
-
-// In release builds, DCHECK_NOTNULL simply passes along the value.
-// DCHECK_NOTNULL must not be used as a standalone expression, since the result
-// would be unused on release builds. Use DCHECK_NE instead.
-//#define DCHECK_NOTNULL(value) value
-
-#define DCHECK_NOTNULL(value) \
-  ::pw::log::DCheckNotNull("", __LINE__, #value " != nullptr", value)
-
-namespace pw::log {
-
-template <typename T>
-constexpr T CheckNotNull(const char* /* file */,
-                         unsigned /* line */,
-                         const char* /* message */,
-                         T&& value) {
-  static_assert(!std::is_null_pointer<T>(),
-                "CHECK_NOTNULL statements cannot be passed nullptr");
-  if (value == nullptr) {
-    // std::exit(1);
-  }
-  return std::forward<T>(value);
-}
-
-// DCHECK_NOTNULL cannot be used in standalone expressions, so add a
-// [[nodiscard]] attribute to prevent this in debug builds. Standalone
-// DCHECK_NOTNULL statements in release builds trigger an unused-value warning.
-template <typename T>
-[[nodiscard]] constexpr T DCheckNotNull(const char* file,
-                                        unsigned line,
-                                        const char* message,
-                                        T&& value) {
-  return CheckNotNull<T>(file, line, message, std::forward<T>(value));
-}
-
-}  // namespace pw::log
-
-// Assert stubs
-#define DCHECK CHECK
-#define DCHECK_EQ CHECK_EQ
-
-#define CHECK(...)
-#define CHECK_EQ(...)
-#define CHECK_GE(...)
-#define CHECK_GT(...)
-#define CHECK_LE(...)
-#define CHECK_LT(...)
diff --git a/pw_kvs/public/pw_kvs/checksum.h b/pw_kvs/public/pw_kvs/checksum.h
new file mode 100644
index 0000000..f7b01a1
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/checksum.h
@@ -0,0 +1,57 @@
+// 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.
+#pragma once
+
+#include <cstddef>
+
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+
+namespace pw::kvs {
+
+class ChecksumAlgorithm {
+ public:
+  // Resets the checksum to its initial state.
+  virtual void Reset() = 0;
+
+  // Updates the checksum with the provided data.
+  virtual Status Update(span<const std::byte> data_to_checksum) = 0;
+
+  // Convnenience wrapper.
+  Status Update(const void* data, size_t size) {
+    return Update(span(static_cast<const std::byte*>(data), size));
+  }
+
+  // Returns the current state of the checksum algorithm.
+  constexpr const span<const std::byte>& state() const { return state_; }
+
+  // Returns the size of the checksum's state.
+  constexpr size_t size_bytes() const { return state_.size(); }
+
+  // Compares a calculated checksum to this checksum's data.
+  Status Verify(span<const std::byte> calculated_checksum) const;
+
+ protected:
+  // Derived class provides a span of its state buffer.
+  constexpr ChecksumAlgorithm(span<const std::byte> state) : state_(state) {}
+
+  // Protected destructor prevents deleting ChecksumAlgorithms from the base
+  // class, so that it is safe to have a non-virtual destructor.
+  ~ChecksumAlgorithm() = default;
+
+ private:
+  span<const std::byte> state_;
+};
+
+}  // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/flash.h b/pw_kvs/public/pw_kvs/flash.h
deleted file mode 100644
index 819f8e2..0000000
--- a/pw_kvs/public/pw_kvs/flash.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.
-#pragma once
-
-#include "pw_kvs/flash_memory.h"
-
-namespace pw::kvs {
-
-// Writes a buffer which is not guaranteed to be aligned, pads remaining
-// bytes with 0.
-Status PaddedWrite(FlashPartition* partition,
-                   FlashPartition::Address address,
-                   const void* buffer,
-                   uint16_t size);
-
-// Read into a buffer when size is not guaranteed to be aligned.
-Status UnalignedRead(FlashPartition* partition,
-                     void* buffer,
-                     FlashPartition::Address address,
-                     uint16_t size);
-
-}  // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
index b69eeb2..5495141 100644
--- a/pw_kvs/public/pw_kvs/flash_memory.h
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -13,32 +13,50 @@
 // the License.
 #pragma once
 
-#include <algorithm>
-#include <cinttypes>
-#include <cstring>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
 
-#include "pw_kvs/assert.h"
-#include "pw_kvs/partition_table_entry.h"
-#include "pw_log/log.h"
+#include "pw_span/span.h"
 #include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw {
+
+// TODO: These are general-purpose utility functions that should be moved
+//       elsewhere.
+constexpr size_t AlignDown(size_t value, size_t alignment) {
+  return (value / alignment) * alignment;
+}
+
+constexpr size_t AlignUp(size_t value, size_t alignment) {
+  return (value + alignment - 1) / alignment * alignment;
+}
+
+}  // namespace pw
 
 namespace pw::kvs {
 
+enum class PartitionPermission : bool {
+  kReadOnly,
+  kReadAndWrite,
+};
+
 class FlashMemory {
  public:
   // The flash address is in the range of: 0 to FlashSize.
   typedef uint32_t Address;
-  constexpr FlashMemory(uint32_t sector_size,
-                        uint32_t sector_count,
-                        uint8_t alignment,
+  constexpr FlashMemory(size_t sector_size,
+                        size_t sector_count,
+                        size_t alignment,
                         uint32_t start_address = 0,
                         uint32_t sector_start = 0,
-                        uint8_t erased_memory_content = 0xFF)
+                        std::byte erased_memory_content = std::byte{0xFF})
       : sector_size_(sector_size),
         flash_sector_count_(sector_count),
         alignment_(alignment),
         start_address_(start_address),
-        sector_start_(sector_start),
+        start_sector_(sector_start),
         erased_memory_content_(erased_memory_content) {}
 
   virtual ~FlashMemory() = default;
@@ -54,43 +72,40 @@
   //          TIMEOUT, on timeout.
   //          INVALID_ARGUMENT, if address or sector count is invalid.
   //          UNKNOWN, on HAL error
-  virtual Status Erase(Address flash_address, uint32_t num_sectors) = 0;
+  virtual Status Erase(Address flash_address, size_t num_sectors) = 0;
 
   // Reads bytes from flash into buffer. Blocking call.
   // Returns: OK, on success.
   //          TIMEOUT, on timeout.
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          UNKNOWN, on HAL error
-  virtual Status Read(uint8_t* destination_ram_address,
-                      Address source_flash_address,
-                      uint32_t len) = 0;
+  virtual StatusWithSize Read(Address address, span<std::byte> output) = 0;
 
   // Writes bytes to flash. Blocking call.
   // Returns: OK, on success.
   //          TIMEOUT, on timeout.
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          UNKNOWN, on HAL error
-  virtual Status Write(Address destination_flash_address,
-                       const uint8_t* source_ram_address,
-                       uint32_t len) = 0;
+  virtual StatusWithSize Write(Address destination_flash_address,
+                               span<const std::byte> data) = 0;
 
   // Convert an Address to an MCU pointer, this can be used for memory
   // mapped reads. Return NULL if the memory is not memory mapped.
-  virtual uint8_t* FlashAddressToMcuAddress(Address) const { return nullptr; }
+  virtual std::byte* FlashAddressToMcuAddress(Address) const { return nullptr; }
 
-  // GetStartSector() is useful for FlashMemory instances where the
+  // start_sector() is useful for FlashMemory instances where the
   // sector start is not 0. (ex.: cases where there are portions of flash
   // that should be handled independently).
-  constexpr uint32_t GetStartSector() const { return sector_start_; }
-  constexpr uint32_t GetSectorSizeBytes() const { return sector_size_; }
-  constexpr uint32_t GetSectorCount() const { return flash_sector_count_; }
-  constexpr uint8_t GetAlignmentBytes() const { return alignment_; }
-  constexpr uint32_t GetSizeBytes() const {
+  constexpr uint32_t start_sector() const { return start_sector_; }
+  constexpr size_t sector_size_bytes() const { return sector_size_; }
+  constexpr size_t sector_count() const { return flash_sector_count_; }
+  constexpr size_t alignment_bytes() const { return alignment_; }
+  constexpr size_t size_bytes() const {
     return sector_size_ * flash_sector_count_;
   }
   // Address of the start of flash (the address of sector 0)
-  constexpr uint32_t GetStartAddress() const { return start_address_; }
-  constexpr uint8_t GetErasedMemoryContent() const {
+  constexpr uint32_t start_address() const { return start_address_; }
+  constexpr std::byte erased_memory_content() const {
     return erased_memory_content_;
   }
 
@@ -99,14 +114,14 @@
   const uint32_t flash_sector_count_;
   const uint8_t alignment_;
   const uint32_t start_address_;
-  const uint32_t sector_start_;
-  const uint8_t erased_memory_content_;
+  const uint32_t start_sector_;
+  const std::byte erased_memory_content_;
 };
 
 class FlashPartition {
  public:
   // The flash address is in the range of: 0 to PartitionSize.
-  typedef uint32_t Address;
+  using Address = uint32_t;
 
   constexpr FlashPartition(
       FlashMemory* flash,
@@ -118,12 +133,17 @@
         sector_count_(sector_count),
         permission_(permission) {}
 
-  constexpr FlashPartition(FlashMemory* flash, PartitionTableEntry entry)
+#if 0
+  constexpr FlashPartition(
+      FlashMemory* flash,
+      uint32_t start_sector_index,
+      uint32_t end_sector_index,
+      PartitionPermission permission = PartitionPermission::kReadAndWrite)
       : flash_(*flash),
-        start_sector_index_(entry.partition_start_sector_index),
-        sector_count_(entry.partition_end_sector_index -
-                      entry.partition_start_sector_index + 1),
-        permission_(entry.partition_permission) {}
+        start_sector_index_(start_sector_index),
+        sector_count_(end_sector_index - start_sector_index + 1),
+        permission_(permission) {}
+#endif
 
   virtual ~FlashPartition() = default;
 
@@ -134,32 +154,17 @@
   //          INVALID_ARGUMENT, if address or sector count is invalid.
   //          PERMISSION_DENIED, if partition is read only.
   //          UNKNOWN, on HAL error
-  virtual Status Erase(Address address, uint32_t num_sectors) {
-    if (permission_ == PartitionPermission::kReadOnly) {
-      return Status::PERMISSION_DENIED;
-    }
-    if (Status status =
-            CheckBounds(address, num_sectors * GetSectorSizeBytes());
-        !status.ok()) {
-      return status;
-    }
-    return flash_.Erase(PartitionToFlashAddress(address), num_sectors);
-  }
+  virtual Status Erase(Address address, size_t num_sectors);
 
   // Reads bytes from flash into buffer. Blocking call.
   // Returns: OK, on success.
   //          TIMEOUT, on timeout.
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          UNKNOWN, on HAL error
-  virtual Status Read(uint8_t* destination_ram_address,
-                      Address source_flash_address,
-                      uint32_t len) {
-    if (Status status = CheckBounds(source_flash_address, len); !status.ok()) {
-      return status;
-    }
-    return flash_.Read(destination_ram_address,
-                       PartitionToFlashAddress(source_flash_address),
-                       len);
+  virtual StatusWithSize Read(Address address, span<std::byte> output);
+
+  StatusWithSize Read(Address address, size_t length, void* output) {
+    return Read(address, span(static_cast<std::byte*>(output), length));
   }
 
   // Writes bytes to flash. Blocking call.
@@ -168,20 +173,10 @@
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          PERMISSION_DENIED, if partition is read only.
   //          UNKNOWN, on HAL error
-  virtual Status Write(Address destination_flash_address,
-                       const uint8_t* source_ram_address,
-                       uint32_t len) {
-    if (permission_ == PartitionPermission::kReadOnly) {
-      return Status::PERMISSION_DENIED;
-    }
-    if (Status status = CheckBounds(destination_flash_address, len);
-        !status.ok()) {
-      return status;
-    }
-    return flash_.Write(PartitionToFlashAddress(destination_flash_address),
-                        source_ram_address,
-                        len);
-  }
+  virtual StatusWithSize Write(Address address, span<const std::byte> data);
+
+  StatusWithSize Write(Address start_address,
+                       std::initializer_list<span<const std::byte>> data);
 
   // Check to see if chunk of flash memory is erased. Address and len need to
   // be aligned with FlashMemory.
@@ -189,87 +184,40 @@
   //          TIMEOUT, on timeout.
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          UNKNOWN, on HAL error
-  virtual Status IsChunkErased(Address source_flash_address,
-                               uint32_t len,
-                               bool* is_erased) {
-    // Max alignment is artifical to keep the stack usage low for this
-    // function. Using 16 because it's the alignment of encrypted flash.
-    const uint8_t kMaxAlignment = 16;
-    // Relying on Read() to check address and len arguments.
-    if (!is_erased) {
-      return Status::INVALID_ARGUMENT;
-    }
-    uint8_t alignment = GetAlignmentBytes();
-    if (alignment > kMaxAlignment || kMaxAlignment % alignment ||
-        len % alignment) {
-      return Status::INVALID_ARGUMENT;
-    }
+  // TODO: StatusWithBool
+  virtual Status IsRegionErased(Address source_flash_address,
+                                size_t len,
+                                bool* is_erased);
 
-    uint8_t buffer[kMaxAlignment];
-    uint8_t erased_pattern_buffer[kMaxAlignment];
-    size_t offset = 0;
-    std::memset(erased_pattern_buffer,
-                flash_.GetErasedMemoryContent(),
-                sizeof(erased_pattern_buffer));
-    *is_erased = false;
-    while (len > 0) {
-      // Check earlier that len is aligned, no need to round up
-      uint16_t read_size = std::min(static_cast<uint32_t>(sizeof(buffer)), len);
-      if (Status status =
-              Read(buffer, source_flash_address + offset, read_size);
-          !status.ok()) {
-        return status;
-      }
-      if (std::memcmp(buffer, erased_pattern_buffer, read_size)) {
-        // Detected memory chunk is not entirely erased
-        return Status::OK;
-      }
-      offset += read_size;
-      len -= read_size;
-    }
-    *is_erased = true;
-    return Status::OK;
+  constexpr uint32_t sector_size_bytes() const {
+    return flash_.sector_size_bytes();
   }
 
-  constexpr uint32_t GetSectorSizeBytes() const {
-    return flash_.GetSectorSizeBytes();
+  // Overridden by base classes which store metadata at the start of a sector.
+  virtual uint32_t sector_available_size_bytes() const {
+    return sector_size_bytes();
   }
 
-  uint32_t GetSizeBytes() const {
-    return GetSectorCount() * GetSectorSizeBytes();
-  }
+  size_t size_bytes() const { return sector_count() * sector_size_bytes(); }
 
-  virtual uint8_t GetAlignmentBytes() const {
-    return flash_.GetAlignmentBytes();
-  }
+  virtual size_t alignment_bytes() const { return flash_.alignment_bytes(); }
 
-  virtual uint32_t GetSectorCount() const { return sector_count_; }
+  virtual size_t sector_count() const { return sector_count_; }
 
   // Convert a FlashMemory::Address to an MCU pointer, this can be used for
   // memory mapped reads. Return NULL if the memory is not memory mapped.
-  uint8_t* PartitionAddressToMcuAddress(Address address) const {
+  std::byte* PartitionAddressToMcuAddress(Address address) const {
     return flash_.FlashAddressToMcuAddress(PartitionToFlashAddress(address));
   }
 
   FlashMemory::Address PartitionToFlashAddress(Address address) const {
-    return flash_.GetStartAddress() +
-           (start_sector_index_ - flash_.GetStartSector()) *
-               GetSectorSizeBytes() +
+    return flash_.start_address() +
+           (start_sector_index_ - flash_.start_sector()) * sector_size_bytes() +
            address;
   }
 
  protected:
-  Status CheckBounds(Address address, size_t len) const {
-    if (address + len > GetSizeBytes()) {
-      PW_LOG_ERROR(
-          "Attempted out-of-bound flash memory access (address: %" PRIu32
-          " length: %zu)",
-          address,
-          len);
-      return Status::INVALID_ARGUMENT;
-    }
-    return Status::OK;
-  }
+  Status CheckBounds(Address address, size_t len) const;
 
  private:
   FlashMemory& flash_;
@@ -278,74 +226,4 @@
   const PartitionPermission permission_;
 };
 
-// FlashSubPartition defines a new partition which maps itself as a smaller
-// piece of another partition. This can used when a partition has special
-// behaviours (for example encrypted flash).
-// For example, this will be the first sector of test_partition:
-//    FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
-class FlashSubPartition : public FlashPartition {
- public:
-  constexpr FlashSubPartition(FlashPartition* parent_partition,
-                              uint32_t start_sector_index,
-                              uint32_t sector_count)
-      : FlashPartition(*parent_partition),
-        partition_(parent_partition),
-        start_sector_index_(start_sector_index),
-        sector_count_(sector_count) {}
-
-  Status Erase(Address address, uint32_t num_sectors) override {
-    if (Status status =
-            CheckBounds(address, num_sectors * GetSectorSizeBytes());
-        !status.ok()) {
-      return status;
-    }
-    return partition_->Erase(ParentAddress(address), num_sectors);
-  }
-
-  Status Read(uint8_t* destination_ram_address,
-              Address source_flash_address,
-              uint32_t len) override {
-    if (Status status = CheckBounds(source_flash_address, len); !status.ok()) {
-      return status;
-    }
-    return partition_->Read(
-        destination_ram_address, ParentAddress(source_flash_address), len);
-  }
-
-  Status Write(Address destination_flash_address,
-               const uint8_t* source_ram_address,
-               uint32_t len) override {
-    if (Status status = CheckBounds(destination_flash_address, len);
-        !status.ok()) {
-      return status;
-    }
-    return partition_->Write(
-        ParentAddress(destination_flash_address), source_ram_address, len);
-  }
-
-  Status IsChunkErased(Address source_flash_address,
-                       uint32_t len,
-                       bool* is_erased) override {
-    if (Status status = CheckBounds(source_flash_address, len); !status.ok()) {
-      return status;
-    }
-    return partition_->IsChunkErased(
-        ParentAddress(source_flash_address), len, is_erased);
-  }
-
-  uint8_t GetAlignmentBytes() const override {
-    return partition_->GetAlignmentBytes();
-  }
-
-  uint32_t GetSectorCount() const override { return sector_count_; }
-
- private:
-  Address ParentAddress(Address address) const {
-    return address + start_sector_index_ * partition_->GetSectorSizeBytes();
-  }
-  FlashPartition* partition_;
-  const uint32_t start_sector_index_;
-  const uint32_t sector_count_;
-};
-
 }  // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
index d2ea67c..901684e 100644
--- a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
+++ b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
@@ -14,6 +14,7 @@
 #pragma once
 
 #include <array>
+#include <cstring>
 
 #include "pw_kvs/flash_memory.h"
 #include "pw_status/status.h"
@@ -38,17 +39,17 @@
   // Returns: OK, on success.
   //          INVALID_ARGUMENT, if address or sector count is invalid.
   //          UNKNOWN, on HAL error
-  Status Erase(Address addr, uint32_t num_sectors) override {
-    if (addr % GetSectorSizeBytes() != 0) {
+  Status Erase(Address addr, size_t num_sectors) override {
+    if (addr % sector_size_bytes() != 0) {
       return Status::INVALID_ARGUMENT;
     }
-    if (addr / GetSectorSizeBytes() + num_sectors > GetSectorCount()) {
+    if (addr / sector_size_bytes() + num_sectors > sector_count()) {
       return Status::UNKNOWN;
     }
-    if (addr % GetAlignmentBytes() != 0) {
+    if (addr % alignment_bytes() != 0) {
       return Status::INVALID_ARGUMENT;
     }
-    memset(&buffer_[addr], 0xFF, GetSectorSizeBytes() * num_sectors);
+    std::memset(&buffer_[addr], 0xFF, sector_size_bytes() * num_sectors);
     return Status::OK;
   }
 
@@ -56,13 +57,11 @@
   // Returns: OK, on success.
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          UNKNOWN, on HAL error
-  Status Read(uint8_t* dest_ram_addr,
-              Address source_flash_addr,
-              uint32_t len) override {
-    if ((source_flash_addr + len) >= GetSectorCount() * GetSizeBytes()) {
+  StatusWithSize Read(Address address, span<std::byte> output) override {
+    if (address + output.size() >= sector_count() * size_bytes()) {
       return Status::INVALID_ARGUMENT;
     }
-    memcpy(dest_ram_addr, &buffer_[source_flash_addr], len);
+    std::memcpy(output.data(), &buffer_[address], output.size());
     return Status::OK;
   }
 
@@ -70,22 +69,20 @@
   // Returns: OK, on success.
   //          INVALID_ARGUMENT, if address or length is invalid.
   //          UNKNOWN, on HAL error
-  Status Write(Address dest_flash_addr,
-               const uint8_t* source_ram_addr,
-               uint32_t len) override {
-    if ((dest_flash_addr + len) >= GetSectorCount() * GetSizeBytes() ||
-        dest_flash_addr % GetAlignmentBytes() != 0 ||
-        len % GetAlignmentBytes() != 0) {
+  StatusWithSize Write(Address address, span<const std::byte> data) override {
+    if ((address + data.size()) >= sector_count() * size_bytes() ||
+        address % alignment_bytes() != 0 ||
+        data.size() % alignment_bytes() != 0) {
       return Status::INVALID_ARGUMENT;
     }
     // Check in erased state
-    for (unsigned i = 0; i < len; i++) {
-      if (buffer_[dest_flash_addr + i] != 0xFF) {
+    for (unsigned i = 0; i < data.size(); i++) {
+      if (buffer_[address + i] != 0xFF) {
         return Status::UNKNOWN;
       }
     }
-    memcpy(&buffer_[dest_flash_addr], source_ram_addr, len);
-    return Status::OK;
+    std::memcpy(&buffer_[address], data.data(), data.size());
+    return StatusWithSize(data.size());
   }
 
  private:
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 2faa512..115bea0 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -13,402 +13,301 @@
 // the License.
 #pragma once
 
+#include <array>
 #include <cstddef>
 #include <cstdint>
-#include <limits>
 #include <string_view>
-#include <type_traits>
 
+#include "pw_kvs/checksum.h"
 #include "pw_kvs/flash_memory.h"
 #include "pw_span/span.h"
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 
 namespace pw::kvs {
-namespace cfg {
-
-// KVS requires a temporary buffer for some operations, this config allows
-// tuning the buffer size. This is a trade-off between a value which is large
-// and therefore requires more RAM, or having a value which is small which will
-// result in some operations taking longer, as the operations are broken into
-// smaller chunks.
-// NOTE: This value can not be smaller then the flash alignment, and it will
-//       round the size down to be a multiple of the flash alignment for all
-//       operations.
-inline constexpr size_t kKvsBufferSize = 64;
-
-// This represents the maximum amount of keys which can be in the KVS at any
-// given time.
-inline constexpr uint8_t kKvsMaxKeyCount = 50;
-
-// This is the maximum amount of sectors the KVS can operate on, an invalid
-// value will cause an error during enable.
-inline constexpr uint32_t kKvsMaxSectorCount = 20;
-
-}  // namespace cfg
-
 namespace internal {
 
+template <typename T, typename = decltype(span(std::declval<T>()))>
+constexpr bool ConvertsToSpan(int) {
+  return true;
+}
+
+// If the expression span(T) fails, then the type can't be converted to a span.
+template <typename T>
+constexpr bool ConvertsToSpan(...) {
+  return false;
+}
+
+}  // namespace internal
+
 // Traits class to detect if the type is a span. std::is_same is insufficient
 // because span is a class template. This is used to ensure that the correct
 // overload of the Put function is selected.
-template <typename>
-struct IsSpan : std::false_type {};
+template <typename T>
+using ConvertsToSpan =
+    std::bool_constant<internal::ConvertsToSpan<std::remove_reference_t<T>>(0)>;
 
-template <typename T, size_t kExtent>
-struct IsSpan<span<T, kExtent>> : std::true_type {};
+// Internal-only persistent storage header format.
+struct EntryHeader;
 
-}  // namespace internal
+struct EntryHeaderFormat {
+  uint32_t magic;  // unique identifier
+  ChecksumAlgorithm* checksum;
+};
 
-// This object is very large (can be over 1500 B, depending on configuration)
-// and should not typically be placed on the stack.
+// TODO: Select the appropriate defaults, add descriptions.
+struct Options {
+  bool partial_gc_on_write = true;
+  bool verify_on_read = true;
+  bool verify_on_write = true;
+};
+
 class KeyValueStore {
  public:
-  constexpr KeyValueStore(FlashPartition* partition) : partition_(*partition) {}
+  // TODO: Make these configurable
+  static constexpr size_t kMaxKeyLength = 64;
+  static constexpr size_t kMaxEntries = 64;
+  static constexpr size_t kUsableSectors = 64;
 
-  KeyValueStore(const KeyValueStore&) = delete;
-  KeyValueStore& operator=(const KeyValueStore&) = delete;
+  // In the future, will be able to provide additional EntryHeaderFormats for
+  // backwards compatibility.
+  constexpr KeyValueStore(FlashPartition* partition,
+                          const EntryHeaderFormat& format,
+                          const Options& options = {})
+      : partition_(*partition),
+        entry_header_format_(format),
+        options_(options),
+        key_map_{},
+        key_map_size_(0),
+        sector_map_{},
+        last_written_sector_(0) {}
 
-  // Enable the KVS, scans the sectors of the partition for any current KVS
-  // data. Erases and initializes any sectors which are not initialized.
-  // Checks the CRC of all data in the KVS; on failure the corrupted data is
-  // lost and Enable will return Status::DATA_LOSS, but the KVS will still load
-  // other data and will be enabled.
-  Status Enable();
+  Status Init();
+  bool initialized() const { return false; }  // TODO: Implement this
 
-  bool IsEnabled() const { return enabled_; }
+  StatusWithSize Get(std::string_view key, span<std::byte> value) const;
 
-  void Disable() {
-    if (enabled_ == false) {
-      return;
-    }
-    // TODO: LOCK MUTEX
-    enabled_ = false;
-  }
-
-  // Erase a key value element.
-  // key is a null terminated c string.
-  // Returns OK if erased
-  //         NOT_FOUND if key was not found.
-  Status Erase(const std::string_view& key);
-
-  // Copy the first size bytes of key's value into value buffer.
-  // key is a null terminated c string. Size is the amount of bytes to read,
-  // Optional offset argument supports reading size bytes starting at the
-  // specified offset.
-  // Returns OK if successful
-  //         NOT_FOUND if key was not found
-  //         DATA_LOSS if the value failed crc check
-  Status Get(const std::string_view& key,
-             const span<std::byte>& value,
-             uint16_t offset = 0);
-
-  // Interpret the key's value as the given type and return it.
-  // Returns OK if successful
-  //         NOT_FOUND if key was not found
-  //         DATA_LOSS if the value failed crc check
-  //         INVALID_ARGUMENT if size of value != sizeof(T)
   template <typename T>
   Status Get(const std::string_view& key, T* value) {
     static_assert(std::is_trivially_copyable<T>(), "KVS values must copyable");
     static_assert(!std::is_pointer<T>(), "KVS values cannot be pointers");
 
-    StatusWithSize result = GetValueSize(key);
+    StatusWithSize result = ValueSize(key);
     if (!result.ok()) {
       return result.status();
     }
     if (result.size() != sizeof(T)) {
       return Status::INVALID_ARGUMENT;
     }
-    return Get(key, as_writable_bytes(span(value, 1)));
+    return Get(key, as_writable_bytes(span(value, 1))).status();
   }
 
-  // Writes the key value store to the partition. If the key already exists it
-  // will be deleted before writing the new value.
-  //
-  // Returns OK if successful
-  //         RESOURCE_EXHAUSTED if there is not enough available space
-  Status Put(const std::string_view& key, const span<const std::byte>& value);
+  Status Put(std::string_view key, span<const std::byte> value);
 
-  // Alternate Put function that takes an object. The object must be trivially
-  // copyable and cannot be a pointer or span.
   template <typename T,
             typename = std::enable_if_t<std::is_trivially_copyable_v<T> &&
                                         !std::is_pointer_v<T> &&
-                                        !internal::IsSpan<T>()>>
+                                        !ConvertsToSpan<T>::value>>
   Status Put(const std::string_view& key, const T& value) {
     return Put(key, as_bytes(span(&value, 1)));
   }
 
-  // Gets the size of the value stored for provided key.
-  // Returns OK if successful
-  //         INVALID_ARGUMENT if args are invalid.
-  //         FAILED_PRECONDITION if KVS is not enabled.
-  //         NOT_FOUND if key was not found.
-  StatusWithSize GetValueSize(const std::string_view& key);
+  Status Delete(std::string_view key);
 
-  // Tests if the proposed key value entry can be stored in the KVS.
-  bool CanFitEntry(uint16_t key_len, uint16_t value_len) {
-    return kSectorInvalid != FindSpace(ChunkSize(key_len, value_len));
+  StatusWithSize ValueSize(std::string_view key) const {
+    (void)key;
+    return Status::UNIMPLEMENTED;
   }
 
-  // CleanAll cleans each sector which is currently marked for cleaning.
-  // Note: if any data is invalid/corrupt it could be lost.
-  Status CleanAll() {
-    // TODO: LOCK MUTEX
-    return CleanAllInternal();
-  }
-  size_t PendingCleanCount() {
-    // TODO: LOCK MUTEX
-    size_t ret = 0;
-    for (size_t i = 0; i < SectorCount(); i++) {
-      ret += sector_space_remaining_[i] == 0 ? 1 : 0;
+  // Classes and functions to support STL-style iteration.
+  class Iterator;
+
+  class Entry {
+   public:
+    // Guaranteed to be null-terminated
+    std::string_view key() const { return key_buffer_.data(); }
+
+    Status Get(span<std::byte> value_buffer) const {
+      return kvs_.Get(key(), value_buffer).status();
     }
-    return ret;
-  }
 
-  // Clean a single sector and return, if all sectors are clean it will set
-  // all_sectors_have_been_cleaned to true and return.
-  Status CleanOneSector(bool* all_sectors_have_been_cleaned);
+    template <typename T>
+    Status Get(T* value) const {
+      return kvs_.Get(key(), value);
+    }
 
-  // For debugging, logging, and testing. (Don't use in regular code)
-  // Note: a key_index is not valid after an element is erased or updated.
-  size_t KeyCount() const;
-  std::string_view GetKey(size_t idx) const;
-  size_t GetValueSize(size_t idx) const;
-  size_t GetMaxKeys() const { return kListCapacityMax; }
-  bool HasEmptySector() const { return HasEmptySectorImpl(kSectorInvalid); }
+    StatusWithSize ValueSize() const { return kvs_.ValueSize(key()); }
 
-  static constexpr size_t kHeaderSize = 8;  // Sector and KVS Header size
-  static constexpr uint16_t MaxValueLength() { return kChunkValueLengthMax; }
+   private:
+    friend class Iterator;
+
+    constexpr Entry(const KeyValueStore& kvs) : kvs_(kvs), key_buffer_{} {}
+
+    const KeyValueStore& kvs_;
+    std::array<char, kMaxKeyLength + 1> key_buffer_;  // +1 for null-terminator
+  };
+
+  class Iterator {
+   public:
+    Iterator& operator++() {
+      index_ += 1;
+      return *this;
+    }
+
+    Iterator& operator++(int) { return operator++(); }
+
+    // Reads the entry's key from flash.
+    const Entry& operator*();
+
+    const Entry* operator->() {
+      operator*();  // Read the key into the Entry object.
+      return &entry_;
+    }
+
+    constexpr bool operator==(const Iterator& rhs) const {
+      return index_ == rhs.index_;
+    }
+
+    constexpr bool operator!=(const Iterator& rhs) const {
+      return index_ != rhs.index_;
+    }
+
+   private:
+    friend class KeyValueStore;
+
+    constexpr Iterator(const KeyValueStore& kvs, size_t index)
+        : entry_(kvs), index_(index) {}
+
+    Entry entry_;
+    size_t index_;
+  };
+
+  // Standard aliases for iterator types.
+  using iterator = Iterator;
+  using const_iterator = Iterator;
+
+  Iterator begin() const { return Iterator(*this, 0); }
+  Iterator end() const { return Iterator(*this, empty() ? 0 : size() - 1); }
+
+  // Returns the number of valid entries in the KeyValueStore.
+  size_t size() const { return key_map_size_; }
+
+  static constexpr size_t max_size() { return kMaxKeyLength; }
+
+  size_t empty() const { return size() == 0u; }
 
  private:
-  using KeyIndex = uint8_t;
-  using SectorIndex = uint32_t;
+  using Address = FlashPartition::Address;
 
-  static constexpr uint16_t kVersion = 1;
-  static constexpr KeyIndex kListCapacityMax = cfg::kKvsMaxKeyCount;
-  static constexpr SectorIndex kSectorCountMax = cfg::kKvsMaxSectorCount;
-
-  // Number of bits for fields in KVSHeader:
-  static constexpr int kChunkHeaderKeyFieldNumBits = 4;
-  static constexpr int kChunkHeaderChunkFieldNumBits = 12;
-
-  static constexpr uint16_t kSectorReadyValue = 0xABCD;
-  static constexpr uint16_t kChunkSyncValue = 0x55AA;
-
-  static constexpr uint16_t kChunkLengthMax =
-      ((1 << kChunkHeaderChunkFieldNumBits) - 1);
-  static constexpr uint16_t kChunkKeyLengthMax =
-      ((1 << kChunkHeaderKeyFieldNumBits) - 1);
-  static constexpr uint16_t kChunkValueLengthMax =
-      (kChunkLengthMax - kChunkKeyLengthMax);
-
-  static constexpr SectorIndex kSectorInvalid = 0xFFFFFFFFul;
-  static constexpr FlashPartition::Address kAddressInvalid = 0xFFFFFFFFul;
-  static constexpr uint64_t kSectorCleanNotPending = 0xFFFFFFFFFFFFFFFFull;
-
-  static constexpr uint16_t kFlagsIsErasedMask = 0x0001;
-  static constexpr uint16_t kMaxAlignmentBytes = 128;
-
-  // This packs into 16 bytes.
-  struct KvsSectorHeaderMeta {
-    uint16_t synchronize_token;
-    uint16_t version;
-    uint16_t alignment_bytes;  // alignment used for each chunk in this sector.
-    uint16_t padding;          // padding to support uint64_t alignment.
-  };
-  static_assert(sizeof(KvsSectorHeaderMeta) == kHeaderSize,
-                "Invalid KvsSectorHeaderMeta size");
-
-  // sector_clean_pending is broken off from KvsSectorHeaderMeta to support
-  // larger than sizeof(KvsSectorHeaderMeta) flash write alignments.
-  struct KvsSectorHeaderCleaning {
-    // When this sector is not marked for cleaning this will be in the erased
-    // state. When marked for clean this value indicates the order the sectors
-    // were marked for cleaning, and therfore which data is the newest.
-    // To remain backwards compatible with v2 and v3 KVS this is 8 bytes, if the
-    // alignment is greater than 8 we will check the entire alignment is in the
-    // default erased state.
-    uint64_t sector_clean_order;
-  };
-  static_assert(sizeof(KvsSectorHeaderCleaning) == kHeaderSize,
-                "Invalid KvsSectorHeaderCleaning size");
-
-  // This packs into 8 bytes, to support uint64_t alignment.
-  struct KvsHeader {
-    uint16_t synchronize_token;
-    uint16_t crc;  // Crc of the key + data (Ignoring any padding bytes)
-    // flags is a Bitmask: bits 15-1 reserved = 0,
-    // bit 0: is_erased [0 when not erased, 1 when erased]
-    uint16_t flags;
-    // On little endian the length fields map to memory as follows:
-    // byte array: [ 0x(c0to3 k0to3) 0x(c8to11 c4to7) ]
-    // Key length does not include trailing zero. That is not stored.
-    uint16_t key_len : kChunkHeaderKeyFieldNumBits;
-    // Chunk length is the total length of the chunk before alignment.
-    // That way we can work out the length of the value as:
-    // (chunk length - key length - size of chunk header).
-    uint16_t chunk_len : kChunkHeaderChunkFieldNumBits;
-  };
-  static_assert(sizeof(KvsHeader) == kHeaderSize, "Invalid KvsHeader size");
-
-  struct KeyMap {
-    std::string_view key() const { return {key_buffer, key_length}; }
-
-    FlashPartition::Address address;
-    char key_buffer[kChunkKeyLengthMax + 1];  // +1 for null terminator
-    uint8_t key_length;
-    bool is_erased;
-    uint16_t chunk_len;
-
-    static_assert(kChunkKeyLengthMax <=
-                  std::numeric_limits<decltype(key_length)>::max());
+  struct KeyMapEntry {
+    uint32_t key_hash;
+    uint32_t key_version;
+    Address address;  // In partition address.
   };
 
-  static constexpr bool InvalidKey(const std::string_view& key) {
-    return key.empty() || key.size() > kChunkKeyLengthMax;
-  }
+  struct SectorMapEntry {
+    uint16_t tail_free_bytes;
+    uint16_t valid_bytes;  // sum of sizes of valid entries
 
-  // NOTE: All public APIs handle the locking, the internal methods assume the
-  // lock has already been acquired.
-
-  SectorIndex AddressToSectorIndex(FlashPartition::Address address) const {
-    return address / partition_.GetSectorSizeBytes();
-  }
-
-  FlashPartition::Address SectorIndexToAddress(SectorIndex index) const {
-    return index * partition_.GetSectorSizeBytes();
-  }
-
-  // Returns kAddressInvalid if no space is found, otherwise the address.
-  FlashPartition::Address FindSpace(size_t requested_size) const;
-
-  // Attempts to rewrite a key's value by appending the new value to the same
-  // sector. If the sector is full the value is written to another sector, and
-  // the sector is marked for cleaning.
-  // Returns RESOURCE_EXHAUSTED if no space is available, OK otherwise.
-  Status RewriteValue(KeyIndex key_index,
-                      const span<const std::byte>& value,
-                      bool is_erased = false);
-
-  bool ValueMatches(KeyIndex key_index,
-                    const span<const std::byte>& value,
-                    bool is_erased);
-
-  // ResetSector erases the sector and writes the sector header.
-  Status ResetSector(SectorIndex sector_index);
-  Status WriteKeyValue(FlashPartition::Address address,
-                       const std::string_view& key,
-                       const span<const std::byte>& value,
-                       bool is_erased = false);
-  uint32_t SectorSpaceRemaining(SectorIndex sector_index) const;
-
-  // Returns idx if key is found, otherwise kListCapacityMax.
-  KeyIndex FindKeyInMap(const std::string_view& key) const;
-  bool IsKeyInMap(const std::string_view& key) const {
-    return FindKeyInMap(key) != kListCapacityMax;
-  }
-
-  void RemoveFromMap(KeyIndex key_index);
-  Status AppendToMap(const std::string_view& key,
-                     FlashPartition::Address address,
-                     size_t chunk_len,
-                     bool is_erased = false);
-  void UpdateMap(KeyIndex key_index,
-                 FlashPartition::Address address,
-                 uint16_t chunk_len,
-                 bool is_erased = false);
-  uint16_t CalculateCrc(const std::string_view& key,
-                        const span<const std::byte>& value) const;
-
-  // Calculates the CRC by reading the value from flash in chunks.
-  Status CalculateCrcFromValueAddress(const std::string_view& key,
-                                      FlashPartition::Address value_address,
-                                      uint16_t value_size,
-                                      uint16_t* crc_ret);
-
-  uint16_t RoundUpForAlignment(uint16_t size) const {
-    if (size % alignment_bytes_ != 0) {
-      return size + alignment_bytes_ - size % alignment_bytes_;
+    bool HasSpace(size_t required_space) const {
+      return (tail_free_bytes >= required_space);
     }
-    return size;
+  };
+
+  Status InvalidOperation(std::string_view key) const;
+
+  static constexpr bool InvalidKey(std::string_view key) {
+    return key.empty() || (key.size() > kMaxKeyLength);
   }
 
-  // The buffer is chosen to be no larger then the config value, and
-  // be a multiple of the flash alignment.
-  size_t TempBufferAlignedSize() const {
-    CHECK_GE(sizeof(temp_buffer_), partition_.GetAlignmentBytes());
-    return sizeof(temp_buffer_) -
-           (sizeof(temp_buffer_) % partition_.GetAlignmentBytes());
+  Status FindKeyMapEntry(std::string_view key,
+                         const KeyMapEntry** result) const;
+
+  // Non-const version of FindKeyMapEntry.
+  Status FindKeyMapEntry(std::string_view key, KeyMapEntry** result) {
+    return static_cast<const KeyValueStore&>(*this).FindKeyMapEntry(
+        key, const_cast<const KeyMapEntry**>(result));
   }
 
-  // Moves a key/value chunk from one address to another, all fields expected to
-  // be aligned.
-  Status MoveChunk(FlashPartition::Address dest_address,
-                   FlashPartition::Address src_address,
-                   uint16_t size);
+  Status ReadEntryHeader(const KeyMapEntry& entry, EntryHeader* header) const;
+  Status ReadEntryKey(const KeyMapEntry& entry,
+                      size_t key_length,
+                      char* key) const;
 
-  // Size of a chunk including header, key, value, and alignment padding.
-  size_t ChunkSize(size_t key_length, size_t value_length) const {
-    return RoundUpForAlignment(sizeof(KvsHeader)) +
-           RoundUpForAlignment(key_length) + RoundUpForAlignment(value_length);
+  StatusWithSize ReadEntryValue(const KeyMapEntry& entry,
+                                const EntryHeader& header,
+                                span<std::byte> value) const;
+
+  Status ValidateEntryChecksum(const EntryHeader& header,
+                               std::string_view key,
+                               span<const std::byte> value) const;
+
+  Status WriteEntryForExistingKey(KeyMapEntry* key_map_entry,
+                                  std::string_view key,
+                                  span<const std::byte> value);
+
+  Status WriteEntryForNewKey(std::string_view key, span<const std::byte> value);
+
+  SectorMapEntry* FindSectorWithSpace(size_t size);
+
+  Status FindOrRecoverSectorWithSpace(SectorMapEntry** sector, size_t size);
+
+  Status GarbageCollectOneSector(SectorMapEntry** sector);
+
+  SectorMapEntry* FindSectorToGarbageCollect();
+
+  Status AppendEntry(SectorMapEntry* sector,
+                     KeyMapEntry* entry,
+                     std::string_view key,
+                     span<const std::byte> value);
+
+  Status VerifyEntry(SectorMapEntry* sector, KeyMapEntry* entry);
+
+  span<const std::byte> CalculateEntryChecksum(
+      const EntryHeader& header,
+      std::string_view key,
+      span<const std::byte> value) const;
+
+  Status RelocateEntry(KeyMapEntry* entry);
+
+  bool SectorEmpty(const SectorMapEntry& sector) const {
+    return (sector.tail_free_bytes == partition_.sector_available_size_bytes());
   }
 
-  // Sectors should be cleaned when full, every valid (Most recent, not erased)
-  // chunk of data is moved to another sector and the sector is erased.
-  // TODO: Clean sectors with lots of invalid data, without a rewrite
-  // or erase triggering it.
-  Status MarkSectorForClean(SectorIndex sector);
-  Status CleanSector(SectorIndex sector);
-  Status CleanAllInternal();
-  Status GarbageCollectImpl(bool clean_pending_sectors,
-                            bool exit_when_have_free_sector);
-  Status FullGarbageCollect();
-  Status EnforceFreeSector();
-
-  SectorIndex SectorCount() const {
-    return std::min(partition_.GetSectorCount(), kSectorCountMax);
+  size_t RecoverableBytes(const SectorMapEntry& sector) {
+    return partition_.sector_size_bytes() - sector.valid_bytes -
+           sector.tail_free_bytes;
   }
 
-  size_t SectorSpaceAvailableWhenEmpty() const {
-    return partition_.GetSectorSizeBytes() -
-           RoundUpForAlignment(sizeof(KvsSectorHeaderMeta)) -
-           RoundUpForAlignment(sizeof(KvsSectorHeaderCleaning));
+  Address SectorBaseAddress(SectorMapEntry* sector) const {
+    return (sector - sector_map_.data()) * partition_.sector_size_bytes();
   }
 
-  bool HasEmptySectorImpl(SectorIndex skip_sector) const {
-    for (SectorIndex i = 0; i < SectorCount(); i++) {
-      if (i != skip_sector &&
-          sector_space_remaining_[i] == SectorSpaceAvailableWhenEmpty()) {
-        return true;
-      }
-    }
-    return false;
+  Address NextWritableAddress(SectorMapEntry* sector) const {
+    return SectorBaseAddress(sector) + partition_.sector_size_bytes() -
+           sector->tail_free_bytes;
   }
 
-  bool IsInLastFreeSector(FlashPartition::Address address) {
-    return sector_space_remaining_[AddressToSectorIndex(address)] ==
-               SectorSpaceAvailableWhenEmpty() &&
-           !HasEmptySectorImpl(AddressToSectorIndex(address));
+  bool EntryMapFull() const { return key_map_size_ == kMaxEntries; }
+
+  span<KeyMapEntry> entries() { return span(key_map_.data(), key_map_size_); }
+
+  span<const KeyMapEntry> entries() const {
+    return span(key_map_.data(), key_map_size_);
   }
 
   FlashPartition& partition_;
-  // TODO: MUTEX
-  bool enabled_ = false;
-  uint8_t alignment_bytes_ = 0;
-  uint64_t next_sector_clean_order_ = 0;
+  EntryHeaderFormat entry_header_format_;
+  Options options_;
 
-  // Free space available in each sector, set to 0 when clean is pending/active
-  uint32_t sector_space_remaining_[kSectorCountMax] = {0};
-  uint64_t sector_clean_order_[kSectorCountMax] = {kSectorCleanNotPending};
-  KeyMap key_map_[kListCapacityMax] = {};
-  KeyIndex map_size_ = 0;
+  // Map is unordered; finding a key requires scanning and
+  // verifying a match by reading the actual entry.
+  std::array<KeyMapEntry, kMaxEntries> key_map_;
+  size_t key_map_size_;  // Number of valid entries in entry_map
 
-  // Add +1 for a null terminator. The keys are used as string_views, but the
-  // null-terminator provides additional safetly.
-  char temp_key_buffer_[kChunkKeyLengthMax + 1u] = {0};
-  uint8_t temp_buffer_[cfg::kKvsBufferSize] = {0};
+  // This is dense, so sector_id == indexof(SectorMapEntry) in sector_map
+  std::array<SectorMapEntry, kUsableSectors> sector_map_;
+  size_t last_written_sector_;  // TODO: this variable is not used!
 };
 
 }  // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/partition_table_entry.h b/pw_kvs/public/pw_kvs/partition_table_entry.h
deleted file mode 100644
index 9aa111d..0000000
--- a/pw_kvs/public/pw_kvs/partition_table_entry.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// 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.
-#pragma once
-
-#include <cstdint>
-
-namespace pw {
-
-namespace partition_table {
-
-enum DataType { kRawData, kKvs };
-
-}  // namespace partition_table
-
-enum class PartitionPermission : bool {
-  kReadOnly = true,
-  kReadAndWrite = false,
-};
-
-struct PartitionTableEntry {
-  constexpr PartitionTableEntry(
-      const uint32_t start_sector_index,
-      const uint32_t end_sector_index,
-      const uint8_t partition_identifier,
-      partition_table::DataType datatype,
-      PartitionPermission permission = PartitionPermission::kReadAndWrite)
-      : partition_start_sector_index(start_sector_index),
-        partition_end_sector_index(end_sector_index),
-        partition_id(partition_identifier),
-        data_type(datatype),
-        partition_permission(permission) {}
-
-  const uint32_t partition_start_sector_index;
-  const uint32_t partition_end_sector_index;
-  const uint8_t partition_id;
-  const partition_table::DataType data_type;
-  const PartitionPermission partition_permission;
-};
-
-}  // namespace pw