pw_kvs: Initial commit of key value store module

This commit does not build or pass presubmit checks.

Change-Id: I3d4dd393ede1c778888c3cd8be9f12dfbf92fb88
diff --git a/pw_kvs/public/pw_kvs/flash.h b/pw_kvs/public/pw_kvs/flash.h
new file mode 100644
index 0000000..5cb80cd
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/flash.h
@@ -0,0 +1,33 @@
+// 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/devices/flash_memory.h"
+
+namespace pw {
+
+// Writes a buffer which is not guaranteed to be aligned, pads remaining
+// bytes with 0.
+Status PaddedWrite(FlashPartition* partition,
+                   FlashPartition::Address address,
+                   const uint8_t* buffer,
+                   uint16_t size);
+
+// Read into a buffer when size is not guaranteed to be aligned.
+Status UnalignedRead(FlashPartition* partition,
+                     uint8_t* buffer,
+                     FlashPartition::Address address,
+                     uint16_t size);
+
+}  // namespace pw
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
new file mode 100644
index 0000000..707f128
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -0,0 +1,377 @@
+// 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 "pw_kvs/assert.h"
+#include "pw_kvs/logging.h"
+#include "pw_kvs/peripherals/partition_table_entry.h"
+#include "pw_kvs/status.h"
+#include "pw_kvs/status_macros.h"
+
+namespace pw {
+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,
+                        uint32_t start_address = 0,
+                        uint32_t sector_start = 0,
+                        uint8_t erased_memory_content = 0xFF)
+      : sector_size_(sector_size),
+        flash_sector_count_(sector_count),
+        alignment_(alignment),
+        start_address_(start_address),
+        sector_start_(sector_start),
+        erased_memory_content_(erased_memory_content) {}
+
+  virtual Status Enable() = 0;
+  virtual Status Disable() = 0;
+  virtual bool IsEnabled() const = 0;
+  virtual Status SelfTest() { return Status::UNIMPLEMENTED; }
+
+  // Erase num_sectors starting at a given address. Blocking call.
+  // Address should be on a sector boundary.
+  // Returns: OK, on success.
+  //          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;
+
+  // 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;
+
+  // 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;
+
+  // 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 address) const {
+    return nullptr;
+  }
+
+  // GetStartSector() 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 {
+    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 {
+    return erased_memory_content_;
+  }
+
+ private:
+  const uint32_t sector_size_;
+  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_;
+};
+
+// Exposes a sub-sector sized region of flash memory that cannot be erased.
+// It can be thought of as one pseudo-sector that is sized exactly as provided.
+//
+// TODO(b/117553777): This makes a little more sense as a SubSectorPartition,
+// but PartitionTableEntry currently assumes all partitions fill entire sectors.
+// Revisit when PartitionTable is refactored.
+class FlashMemorySubSector : public FlashMemory {
+ public:
+  constexpr FlashMemorySubSector(FlashMemory* flash,
+                                 uint32_t start_address,
+                                 uint32_t size)
+      : FlashMemory(size,
+                    1,  // Round up to "1" sector.
+                    flash->GetAlignmentBytes(),
+                    start_address,
+                    // Calculate the sector for this start address.
+                    flash->GetStartSector() +
+                        ((start_address - flash->GetStartAddress()) /
+                         flash->GetSectorSizeBytes())),
+        flash_(*CHECK_NOTNULL(flash)),
+        base_offset_(start_address - flash->GetStartAddress()) {
+    // Make sure we're not specifying a region of flash larger than
+    // that which the underlying FlashMemory supports.
+    CHECK(start_address >= flash->GetStartAddress());
+    CHECK(size <= flash->GetSectorSizeBytes());
+    CHECK(start_address + size <=
+          flash->GetStartAddress() + flash->GetSizeBytes());
+    CHECK_EQ(0, start_address % flash->GetAlignmentBytes());
+    CHECK_EQ(0, size % flash->GetAlignmentBytes());
+  }
+
+  Status Enable() override { return flash_.Enable(); }
+  Status Disable() override { return flash_.Disable(); }
+  bool IsEnabled() const override { return flash_.IsEnabled(); }
+  Status SelfTest() override { return flash_.SelfTest(); }
+
+  Status Erase(Address flash_address, uint32_t num_sectors) override {
+    return Status::UNIMPLEMENTED;
+  }
+
+  Status Read(uint8_t* destination_ram_address,
+              Address source_flash_address,
+              uint32_t len) override {
+    return flash_.Read(destination_ram_address, source_flash_address, len);
+  }
+
+  Status Write(Address destination_flash_address,
+               const uint8_t* source_ram_address,
+               uint32_t len) override {
+    return flash_.Write(destination_flash_address, source_ram_address, len);
+  }
+
+  uint8_t* FlashAddressToMcuAddress(Address address) const override {
+    return flash_.FlashAddressToMcuAddress(base_offset_ + address);
+  }
+
+ private:
+  FlashMemory& flash_;
+  // Value to add to addresses to get to the underlying flash_ address.
+  const Address base_offset_;
+};
+
+class FlashPartition {
+ public:
+  // The flash address is in the range of: 0 to PartitionSize.
+  typedef uint32_t Address;
+
+  constexpr FlashPartition(
+      FlashMemory* flash,
+      uint32_t start_sector_index,
+      uint32_t sector_count,
+      PartitionPermission permission = PartitionPermission::kReadAndWrite)
+      : flash_(*flash),
+        start_sector_index_(start_sector_index),
+        sector_count_(sector_count),
+        permission_(permission) {}
+
+  constexpr FlashPartition(FlashMemory* flash, PartitionTableEntry entry)
+      : 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) {}
+
+  // Erase num_sectors starting at a given address. Blocking call.
+  // Address should be on a sector boundary.
+  // Returns: OK, on success.
+  //          TIMEOUT, on timeout.
+  //          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) {
+    RETURN_STATUS_IF(permission_ == PartitionPermission::kReadOnly,
+                     Status::PERMISSION_DENIED);
+    RETURN_IF_ERROR(CheckBounds(address, num_sectors * GetSectorSizeBytes()));
+    return flash_.Erase(PartitionToFlashAddress(address), 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) {
+    RETURN_IF_ERROR(CheckBounds(source_flash_address, len));
+    return flash_.Read(destination_ram_address,
+                       PartitionToFlashAddress(source_flash_address),
+                       len);
+  }
+
+  // Writes bytes to flash. Blocking call.
+  // Returns: OK, on success.
+  //          TIMEOUT, on timeout.
+  //          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) {
+    RETURN_STATUS_IF(permission_ == PartitionPermission::kReadOnly,
+                     Status::PERMISSION_DENIED);
+    RETURN_IF_ERROR(CheckBounds(destination_flash_address, len));
+    return flash_.Write(PartitionToFlashAddress(destination_flash_address),
+                        source_ram_address,
+                        len);
+  }
+
+  // Check to see if chunk of flash memory is erased. Address and len need to
+  // be aligned with FlashMemory.
+  // Returns: OK, on success.
+  //          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.
+    RETURN_STATUS_IF(!is_erased, Status::INVALID_ARGUMENT);
+    uint8_t alignment = GetAlignmentBytes();
+    RETURN_STATUS_IF(alignment > kMaxAlignment, Status::INVALID_ARGUMENT);
+    RETURN_STATUS_IF(kMaxAlignment % alignment, Status::INVALID_ARGUMENT);
+    RETURN_STATUS_IF(len % alignment, Status::INVALID_ARGUMENT);
+
+    uint8_t buffer[kMaxAlignment];
+    uint8_t erased_pattern_buffer[kMaxAlignment];
+    size_t offset = 0;
+    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);
+      RETURN_IF_ERROR(Read(buffer, source_flash_address + offset, read_size));
+      if (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 GetSectorSizeBytes() const {
+    return flash_.GetSectorSizeBytes();
+  }
+
+  uint32_t GetSizeBytes() const {
+    return GetSectorCount() * GetSectorSizeBytes();
+  }
+
+  virtual uint8_t GetAlignmentBytes() const {
+    return flash_.GetAlignmentBytes();
+  }
+
+  virtual uint32_t GetSectorCount() 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 {
+    return flash_.FlashAddressToMcuAddress(PartitionToFlashAddress(address));
+  }
+
+  FlashMemory::Address PartitionToFlashAddress(Address address) const {
+    return flash_.GetStartAddress() +
+           (start_sector_index_ - flash_.GetStartSector()) *
+               GetSectorSizeBytes() +
+           address;
+  }
+
+ protected:
+  Status CheckBounds(Address address, uint32_t len) const {
+    if (address + len > GetSizeBytes()) {
+      LOG(ERROR) << "Attempted out-of-bound flash memory access (address:"
+                 << address << " length:" << len << ")";
+      return Status::INVALID_ARGUMENT;
+    }
+    return Status::OK;
+  }
+
+ private:
+  FlashMemory& flash_;
+  const uint32_t start_sector_index_;
+  const uint32_t sector_count_;
+  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 {
+    RETURN_IF_ERROR(CheckBounds(address, num_sectors * GetSectorSizeBytes()));
+    return partition_->Erase(ParentAddress(address), num_sectors);
+  }
+
+  Status Read(uint8_t* destination_ram_address,
+              Address source_flash_address,
+              uint32_t len) override {
+    RETURN_IF_ERROR(CheckBounds(source_flash_address, len));
+    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 {
+    RETURN_IF_ERROR(CheckBounds(destination_flash_address, len));
+    return partition_->Write(
+        ParentAddress(destination_flash_address), source_ram_address, len);
+  }
+
+  Status IsChunkErased(Address source_flash_address,
+                       uint32_t len,
+                       bool* is_erased) override {
+    RETURN_IF_ERROR(CheckBounds(source_flash_address, len));
+    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
diff --git a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
new file mode 100644
index 0000000..a67c770
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
@@ -0,0 +1,92 @@
+// 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 <array>
+
+#include "pw_kvs/devices/flash_memory.h"
+#include "pw_kvs/status.h"
+#include "pw_kvs/status_macros.h"
+
+namespace pw {
+
+// This creates a buffer which mimics the behaviour of flash (requires erase,
+// before write, checks alignments, and is addressed in sectors).
+template <uint32_t kSectorSize, uint16_t kSectorCount>
+class InMemoryFakeFlash : public FlashMemory {
+ public:
+  InMemoryFakeFlash(uint8_t alignment = 1)  // default 8 bit alignment
+      : FlashMemory(kSectorSize, kSectorCount, alignment) {}
+
+  // Always enabled
+  Status Enable() override { return Status::OK; }
+  Status Disable() override { return Status::OK; }
+  bool IsEnabled() const override { return true; }
+
+  // Erase num_sectors starting at a given address. Blocking call.
+  // Address should be on a sector boundary.
+  // 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 {
+    RETURN_STATUS_IF(addr % GetSectorSizeBytes() != 0,
+                     Status::INVALID_ARGUMENT);
+    RETURN_STATUS_IF(
+        addr / GetSectorSizeBytes() + num_sectors > GetSectorCount(),
+        Status::UNKNOWN);
+    RETURN_STATUS_IF(addr % GetAlignmentBytes() != 0, Status::INVALID_ARGUMENT);
+    memset(&buffer_[addr], 0xFF, GetSectorSizeBytes() * num_sectors);
+    return Status::OK;
+  }
+
+  // Reads bytes from flash into buffer. Blocking call.
+  // 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 {
+    RETURN_STATUS_IF(
+        (source_flash_addr + len) >= GetSectorCount() * GetSizeBytes(),
+        Status::INVALID_ARGUMENT);
+    memcpy(dest_ram_addr, &buffer_[source_flash_addr], len);
+    return Status::OK;
+  }
+
+  // Writes bytes to flash. Blocking call.
+  // 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 {
+    RETURN_STATUS_IF(
+        (dest_flash_addr + len) >= GetSectorCount() * GetSizeBytes(),
+        Status::INVALID_ARGUMENT);
+    RETURN_STATUS_IF(dest_flash_addr % GetAlignmentBytes() != 0,
+                     Status::INVALID_ARGUMENT);
+    RETURN_STATUS_IF(len % GetAlignmentBytes() != 0, Status::INVALID_ARGUMENT);
+    // Check in erased state
+    for (unsigned i = 0; i < len; i++) {
+      RETURN_STATUS_IF(buffer_[dest_flash_addr + i] != 0xFF, Status::UNKNOWN);
+    }
+    memcpy(&buffer_[dest_flash_addr], source_ram_addr, len);
+    return Status::OK;
+  }
+
+ private:
+  std::array<uint8_t, kSectorCount * kSectorSize> buffer_;
+};
+
+}  // namespace pw
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
new file mode 100644
index 0000000..dca6507
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -0,0 +1,366 @@
+// 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>
+
+#include "pw_kvs/devices/flash_memory.h"
+#include "pw_kvs/logging.h"
+#include "pw_kvs/os/mutex.h"
+#include "pw_kvs/status.h"
+
+namespace pw {
+
+// This object is very large and should not be placed on the stack.
+class KeyValueStore {
+ public:
+  KeyValueStore(FlashPartition* partition) : partition_(*partition) {}
+
+  // 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();
+  bool IsEnabled() const { return enabled_; }
+  void Disable() {
+    if (enabled_ == false) {
+      return;
+    }
+    os::MutexLock lock(&lock_);
+    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 char* 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 char* key, void* value, uint16_t size, 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 char* 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");
+    uint16_t value_size = 0;
+    RETURN_IF_ERROR(GetValueSize(key, &value_size));
+    RETURN_STATUS_IF(value_size != sizeof(T), Status::INVALID_ARGUMENT);
+    return Get(key, value, sizeof(T));
+  }
+
+  // Writes the key value store to the partition. If the key already exists it
+  // will be deleted before writing the new value.
+  // key is a null terminated c string.
+  // Returns OK if successful
+  //         RESOURCE_EXHAUSTED if there is not enough available space
+  Status Put(const char* key, const void* value, uint16_t size);
+
+  // Writes the value to the partition. If the key already exists it will be
+  // deleted before writing the new value.
+  // key is a null terminated c string.
+  // Returns OK if successful
+  //         RESOURCE_EXHAUSTED if there is not enough available space
+  template <typename T>
+  Status Put(const char* key, const T& value) {
+    static_assert(std::is_trivially_copyable<T>(), "KVS values must copyable");
+    static_assert(!std::is_pointer<T>(), "KVS values cannot be pointers");
+    return Put(key, &value, sizeof(T));
+  }
+
+  // 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.
+  Status GetValueSize(const char* key, uint16_t* value_size);
+
+  // 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));
+  }
+
+  // CleanAll cleans each sector which is currently marked for cleaning.
+  // Note: if any data is invalid/corrupt it could be lost.
+  Status CleanAll() {
+    os::MutexLock lock(&lock_);
+    return CleanAllInternal();
+  }
+  size_t PendingCleanCount() {
+    os::MutexLock lock(&lock_);
+    size_t ret = 0;
+    for (size_t i = 0; i < SectorCount(); i++) {
+      ret += sector_space_remaining_[i] == 0 ? 1 : 0;
+    }
+    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);
+
+  // 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.
+  uint8_t KeyCount() const;
+  const char* GetKey(uint8_t idx) const;
+  uint16_t GetValueSize(uint8_t idx) const;
+  size_t GetMaxKeys() const { return kListCapacityMax; }
+  bool HasEmptySector() const { return HaveEmptySectorImpl(); }
+
+  static constexpr size_t kHeaderSize = 8;  // Sector and KVS Header size
+  static constexpr uint16_t MaxValueLength() { return kChunkValueLengthMax; }
+  KeyValueStore(const KeyValueStore&) = delete;
+  KeyValueStore& operator=(const KeyValueStore&) = delete;
+
+ private:
+  using KeyIndex = uint8_t;
+  using SectorIndex = uint32_t;
+
+  static constexpr uint16_t kVersion = 4;
+  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;
+
+  // TODO: Use BitPacker if/when have more flags.
+  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.
+  } __attribute__((__packed__));
+  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;
+  } __attribute__((__packed__));
+  static_assert(sizeof(KvsHeader) == kHeaderSize, "Invalid KvsHeader size");
+
+  struct KeyMap {
+    char key[kChunkKeyLengthMax + 1];  // + 1 for null terminator
+    FlashPartition::Address address;
+    uint16_t chunk_len;
+    bool is_erased;
+  };
+
+  // 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(uint16_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 uint8_t* value,
+                      uint16_t size,
+                      bool is_erased = false);
+
+  bool ValueMatches(KeyIndex key_index,
+                    const uint8_t* value,
+                    uint16_t size,
+                    bool is_erased);
+
+  // ResetSector erases the sector and writes the sector header.
+  Status ResetSector(SectorIndex sector_index);
+  Status WriteKeyValue(FlashPartition::Address address,
+                       const char* key,
+                       const uint8_t* value,
+                       uint16_t size,
+                       bool is_erased = false);
+  uint32_t SectorSpaceRemaining(SectorIndex sector_index) const;
+
+  // Returns idx if key is found, otherwise kListCapacityMax.
+  KeyIndex FindKeyInMap(const char* key) const;
+  bool IsKeyInMap(const char* key) const {
+    return FindKeyInMap(key) != kListCapacityMax;
+  }
+
+  void RemoveFromMap(KeyIndex key_index);
+  Status AppendToMap(const char* key,
+                     FlashPartition::Address address,
+                     uint16_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 char* key,
+                        uint16_t key_size,
+                        const uint8_t* value,
+                        uint16_t value_size) const;
+
+  // Calculates the CRC by reading the value from flash in chunks.
+  Status CalculateCrcFromValueAddress(const char* key,
+                                      uint16_t key_size,
+                                      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_;
+    }
+    return size;
+  }
+
+  // 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());
+  }
+
+  // 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);
+
+  // Size of a chunk including header, key, value, and alignment padding.
+  uint16_t ChunkSize(uint16_t key_len, uint16_t chunk_len) const {
+    return RoundUpForAlignment(sizeof(KvsHeader)) +
+           RoundUpForAlignment(key_len) + RoundUpForAlignment(chunk_len);
+  }
+
+  // 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 SectorSpaceAvailableWhenEmpty() const {
+    return partition_.GetSectorSizeBytes() -
+           RoundUpForAlignment(sizeof(KvsSectorHeaderMeta)) -
+           RoundUpForAlignment(sizeof(KvsSectorHeaderCleaning));
+  }
+
+  bool HaveEmptySectorImpl(SectorIndex skip_sector = kSectorInvalid) const {
+    for (SectorIndex i = 0; i < SectorCount(); i++) {
+      if (i != skip_sector &&
+          sector_space_remaining_[i] == SectorSpaceAvailableWhenEmpty()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool IsInLastFreeSector(FlashPartition::Address address) {
+    return sector_space_remaining_[AddressToSectorIndex(address)] ==
+               SectorSpaceAvailableWhenEmpty() &&
+           !HaveEmptySectorImpl(AddressToSectorIndex(address));
+  }
+
+  FlashPartition& partition_;
+  os::Mutex lock_;
+  bool enabled_ = false;
+  uint8_t alignment_bytes_ = 0;
+  uint64_t next_sector_clean_order_ = 0;
+
+  // 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] = {{{0}}};
+  KeyIndex map_size_ = 0;
+
+  // +1 for nul-terminator since keys are stored as Length + Value and no nul
+  // termination but we are using them as nul-terminated strings through
+  // loading-up and comparing the keys.
+  char temp_key_buffer_[kChunkKeyLengthMax + 1u] = {0};
+  uint8_t temp_buffer_[cfg::kKvsBufferSize] = {0};
+};
+
+}  // namespace pw
diff --git a/pw_kvs/public/pw_kvs/partition_table_entry.h b/pw_kvs/public/pw_kvs/partition_table_entry.h
new file mode 100644
index 0000000..9aa111d
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/partition_table_entry.h
@@ -0,0 +1,51 @@
+// 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