pw_kvs: Expand tests for error handling

- Add error injection features to InMemoryFakeFlash. Reads or Writes can
  be programmed to fail.
- Introduce key_value_store_error_handling_test.cc, which focuses on
  corruption and read/write failure cases.
- Add functions for creating binary KVS entries at compile time.

Change-Id: Ie52ba5eb13eb60244835ef43314282beacc3a659
diff --git a/.gitignore b/.gitignore
index 9c7800a..a2708bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
 .vscode
 .clangd/
 *.swp
+*.swo
 
 # Python
 python*-env/
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index 01b63ea..4083387 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -123,7 +123,19 @@
         ":crc16",
         ":pw_kvs",
         ":test_utils",
-        "//pw_checksum",
+        "//pw_log",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_error_handling_test",
+    srcs = [
+        "key_value_store_error_handling_test.cc",
+    ],
+    deps = [
+        ":crc16",
+        ":pw_kvs",
+        ":test_utils",
         "//pw_log",
     ],
 )
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index 5f899b3..3256401 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -55,6 +55,7 @@
   friend = [
     ":entry_test",
     ":key_value_store_test",
+    ":key_value_store_error_handling_test",
     ":key_value_store_map_test",
   ]
 }
@@ -101,6 +102,7 @@
     ":checksum_test",
     ":entry_test",
     ":key_value_store_test",
+    ":key_value_store_error_handling_test",
     ":key_value_store_fuzz_test",
     ":key_value_store_map_test",
   ]
@@ -150,6 +152,18 @@
   ]
 }
 
+pw_test("key_value_store_error_handling_test") {
+  deps = [
+    ":crc16",
+    ":pw_kvs",
+    ":test_utils",
+    dir_pw_log,
+  ]
+  sources = [
+    "key_value_store_error_handling_test.cc",
+  ]
+}
+
 pw_test("key_value_store_fuzz_test") {
   deps = [
     ":crc16",
diff --git a/pw_kvs/in_memory_fake_flash.cc b/pw_kvs/in_memory_fake_flash.cc
index 70f3890..0b91965 100644
--- a/pw_kvs/in_memory_fake_flash.cc
+++ b/pw_kvs/in_memory_fake_flash.cc
@@ -18,6 +18,42 @@
 
 namespace pw::kvs {
 
+FlashError::Result FlashError::Check(span<FlashError> errors,
+                                     FlashMemory::Address address,
+                                     size_t size) {
+  for (auto& error : errors) {
+    if (Result result = error.Check(address, size); !result.status.ok()) {
+      return result;
+    }
+  }
+
+  return {Status::OK, true};
+}
+
+FlashError::Result FlashError::Check(FlashMemory::Address start_address,
+                                     size_t size) {
+  // Check if the event overlaps with this address range.
+  if (begin_ != kAnyAddress &&
+      (start_address >= end_ || (start_address + size) < begin_)) {
+    return {Status::OK, true};
+  }
+
+  if (delay_ > 0u) {
+    delay_ -= 1;
+    return {Status::OK, true};
+  }
+
+  if (remaining_ == 0u) {
+    return {Status::OK, true};
+  }
+
+  if (remaining_ != kAlways) {
+    remaining_ -= 1;
+  }
+
+  return {status_, mode_ != kAbort};
+}
+
 Status InMemoryFakeFlash::Erase(Address address, size_t num_sectors) {
   if (address % sector_size_bytes() != 0) {
     PW_LOG_ERROR(
@@ -45,8 +81,14 @@
   if (address + output.size() >= sector_count() * size_bytes()) {
     return StatusWithSize(Status::OUT_OF_RANGE);
   }
-  std::memcpy(output.data(), &buffer_[address], output.size());
-  return StatusWithSize(output.size());
+
+  // Check for injected read errors
+  auto [status, finish_operation] =
+      FlashError::Check(read_errors_, address, output.size());
+  if (finish_operation) {
+    std::memcpy(output.data(), &buffer_[address], output.size());
+  }
+  return StatusWithSize(status, output.size());
 }
 
 StatusWithSize InMemoryFakeFlash::Write(Address address,
@@ -84,7 +126,14 @@
       return StatusWithSize(Status::UNKNOWN);
     }
   }
-  std::memcpy(&buffer_[address], data.data(), data.size());
-  return StatusWithSize(data.size());
+
+  // Check for any injected write errors
+  auto [status, finish_operation] =
+      FlashError::Check(write_errors_, address, data.size());
+  if (finish_operation) {
+    std::memcpy(&buffer_[address], data.data(), data.size());
+  }
+  return StatusWithSize(status, data.size());
 }
+
 }  // namespace pw::kvs
diff --git a/pw_kvs/key_value_store_error_handling_test.cc b/pw_kvs/key_value_store_error_handling_test.cc
new file mode 100644
index 0000000..cf4fa38
--- /dev/null
+++ b/pw_kvs/key_value_store_error_handling_test.cc
@@ -0,0 +1,169 @@
+// 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 <string_view>
+
+#include "gtest/gtest.h"
+#include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/in_memory_fake_flash.h"
+#include "pw_kvs/internal/hash.h"
+#include "pw_kvs/key_value_store.h"
+#include "pw_kvs_private/byte_utils.h"
+
+namespace pw::kvs {
+namespace {
+
+using std::byte;
+using std::string_view;
+
+constexpr size_t kMaxEntries = 256;
+constexpr size_t kMaxUsableSectors = 256;
+
+constexpr uint32_t SimpleChecksum(span<const byte> data, uint32_t state = 0) {
+  for (byte b : data) {
+    state += uint32_t(b);
+  }
+  return state;
+}
+
+class SimpleChecksumAlgorithm final : public ChecksumAlgorithm {
+ public:
+  SimpleChecksumAlgorithm()
+      : ChecksumAlgorithm(as_bytes(span(&checksum_, 1))) {}
+
+  void Reset() override { checksum_ = 0; }
+
+  void Update(span<const byte> data) override {
+    checksum_ = SimpleChecksum(data, checksum_);
+  }
+
+ private:
+  uint32_t checksum_;
+} checksum;
+
+// Returns a buffer containing the necessary padding for an entry.
+template <size_t kAlignmentBytes, size_t kKeyLength, size_t kValueSize>
+constexpr auto EntryPadding() {
+  constexpr size_t content =
+      sizeof(internal::EntryHeader) + kKeyLength + kValueSize;
+  return std::array<byte, Padding(content, kAlignmentBytes)>{};
+}
+
+// Creates a buffer containing a valid entry at compile time.
+template <size_t kAlignmentBytes = sizeof(internal::EntryHeader),
+          size_t kKeyLengthWithNull,
+          size_t kValueSize>
+constexpr auto MakeValidEntry(uint32_t magic,
+                              uint32_t id,
+                              const char (&key)[kKeyLengthWithNull],
+                              const std::array<byte, kValueSize>& value) {
+  constexpr size_t kKeyLength = kKeyLengthWithNull - 1;
+
+  auto data = AsBytes(magic,
+                      uint32_t(0),
+                      uint8_t(kAlignmentBytes / 16 - 1),
+                      uint8_t(kKeyLength),
+                      uint16_t(kValueSize),
+                      id,
+                      ByteStr(key),
+                      span(value),
+                      EntryPadding<kAlignmentBytes, kKeyLength, kValueSize>());
+
+  // Calculate the checksum
+  uint32_t checksum = SimpleChecksum(data);
+  for (size_t i = 0; i < sizeof(checksum); ++i) {
+    data[4 + i] = byte(checksum & 0xff);
+    checksum >>= 8;
+  }
+
+  return data;
+}
+
+constexpr uint32_t kMagic = 0xc001beef;
+constexpr EntryFormat kFormat{.magic = kMagic, .checksum = &checksum};
+
+constexpr auto kEntry1 = MakeValidEntry(kMagic, 1, "key1", ByteStr("value1"));
+constexpr auto kEntry2 = MakeValidEntry(kMagic, 3, "k2", ByteStr("value2"));
+
+class KvsErrorHandling : public ::testing::Test {
+ protected:
+  KvsErrorHandling()
+      : flash_(internal::Entry::kMinAlignmentBytes),
+        partition_(&flash_),
+        kvs_(&partition_, kFormat) {}
+
+  void InitFlashTo(span<const byte> contents) {
+    partition_.Erase();
+    std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
+  }
+
+  FakeFlashBuffer<512, 4> flash_;
+  FlashPartition partition_;
+  KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
+};
+
+TEST_F(KvsErrorHandling, Init_Ok) {
+  InitFlashTo(AsBytes(kEntry1, kEntry2));
+
+  EXPECT_EQ(Status::OK, kvs_.Init());
+  byte buffer[64];
+  EXPECT_EQ(Status::OK, kvs_.Get("key1", buffer).status());
+  EXPECT_EQ(Status::OK, kvs_.Get("k2", buffer).status());
+}
+
+TEST_F(KvsErrorHandling, Init_DuplicateEntries_ReturnsDataLossButReadsEntry) {
+  InitFlashTo(AsBytes(kEntry1, kEntry1));
+
+  EXPECT_EQ(Status::DATA_LOSS, kvs_.Init());
+  byte buffer[64];
+  EXPECT_EQ(Status::OK, kvs_.Get("key1", buffer).status());
+  EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("k2", buffer).status());
+}
+
+TEST_F(KvsErrorHandling, Init_CorruptEntry_FindsSubsequentValidEntry) {
+  // Corrupt each byte in the first entry once.
+  for (size_t i = 0; i < kEntry1.size(); ++i) {
+    InitFlashTo(AsBytes(kEntry1, kEntry2));
+    flash_.buffer()[i] = byte(int(flash_.buffer()[i]) + 1);
+
+    ASSERT_EQ(Status::DATA_LOSS, kvs_.Init());
+    byte buffer[64];
+    ASSERT_EQ(Status::NOT_FOUND, kvs_.Get("key1", buffer).status());
+    ASSERT_EQ(Status::OK, kvs_.Get("k2", buffer).status());
+  }
+}
+
+TEST_F(KvsErrorHandling, Init_ReadError_IsNotInitialized) {
+  InitFlashTo(AsBytes(kEntry1, kEntry2));
+
+  flash_.InjectReadError(
+      FlashError::InRange(Status::UNAUTHENTICATED, kEntry1.size()));
+
+  EXPECT_EQ(Status::UNKNOWN, kvs_.Init());
+  EXPECT_FALSE(kvs_.initialized());
+}
+
+TEST_F(KvsErrorHandling, Put_WriteFailure_EntryNotAdded) {
+  ASSERT_EQ(Status::OK, kvs_.Init());
+  flash_.InjectWriteError(FlashError::Unconditional(Status::UNAVAILABLE));
+
+  EXPECT_EQ(Status::UNAVAILABLE, kvs_.Put("key1", ByteStr("value1")));
+
+  byte buffer[64];
+  EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("key1", buffer).status());
+  ASSERT_TRUE(kvs_.empty());
+}
+
+}  // namespace
+}  // 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 761e57c..2bb385d 100644
--- a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
+++ b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
@@ -18,16 +18,86 @@
 #include <cstddef>
 #include <cstring>
 
+#include "pw_containers/vector.h"
 #include "pw_kvs/flash_memory.h"
 #include "pw_span/span.h"
 #include "pw_status/status.h"
 
 namespace pw::kvs {
 
+class FlashError {
+ public:
+  enum Mode {
+    kAbort,
+    kContinueButReportError,
+  };
+
+  static constexpr FlashMemory::Address kAnyAddress = FlashMemory::Address(-1);
+  static constexpr size_t kAlways = size_t(-1);
+
+  // Creates a FlashError that always triggers on the next operation.
+  static constexpr FlashError Unconditional(Status status,
+                                            Mode mode = kAbort,
+                                            size_t times = kAlways,
+                                            size_t delay = 0) {
+    return FlashError(status, kAnyAddress, 0, mode, times, delay);
+  }
+
+  // Creates a FlashError that triggers for particular addresses.
+  static constexpr FlashError InRange(Status status,
+                                      FlashMemory::Address address,
+                                      size_t size = 1,
+                                      Mode mode = kAbort,
+                                      size_t times = kAlways,
+                                      size_t delay = 0) {
+    return FlashError(status, address, size, mode, times, delay);
+  }
+
+  struct Result {
+    Status status;          // what to return from the operation
+    bool finish_operation;  // whether to complete the operation
+  };
+
+  // Determines if this FlashError applies to the operation.
+  Result Check(FlashMemory::Address start_address, size_t size);
+
+  // Determines if any of a series of FlashErrors applies to the operation.
+  static Result Check(span<FlashError> errors,
+                      FlashMemory::Address address,
+                      size_t size);
+
+ private:
+  constexpr FlashError(Status status,
+                       FlashMemory::Address address,
+                       size_t size,  // not used if address is kAnyAddress
+                       Mode mode,
+                       size_t times,
+                       size_t delay)
+      : status_(status),
+        begin_(address),
+        end_(address + size),  // not used if address is kAnyAddress
+        mode_(mode),
+        delay_(delay),
+        remaining_(times) {}
+
+  const Status status_;
+
+  const FlashMemory::Address begin_;
+  const FlashMemory::Address end_;  // exclusive
+
+  const Mode mode_;
+
+  size_t delay_;
+  size_t remaining_;
+};
+
 // This uses a buffer to mimic the behaviour of flash (requires erase before
 // write, checks alignments, and is addressed in sectors). The underlying buffer
 // is not initialized.
 class InMemoryFakeFlash : public FlashMemory {
+ private:
+  static Vector<FlashError, 0> no_errors_;
+
  public:
   // Default to 8-bit alignment.
   static constexpr size_t kDefaultAlignmentBytes = 1;
@@ -37,9 +107,13 @@
   InMemoryFakeFlash(span<std::byte> buffer,
                     size_t sector_size,
                     size_t sector_count,
-                    size_t alignment_bytes = kDefaultAlignmentBytes)
+                    size_t alignment_bytes = kDefaultAlignmentBytes,
+                    Vector<FlashError>& read_errors = no_errors_,
+                    Vector<FlashError>& write_errors = no_errors_)
       : FlashMemory(sector_size, sector_count, alignment_bytes),
-        buffer_(buffer) {}
+        buffer_(buffer),
+        read_errors_(read_errors),
+        write_errors_(write_errors) {}
 
   // The fake flash is always enabled.
   Status Enable() override { return Status::OK; }
@@ -57,18 +131,38 @@
   // Writes bytes to flash.
   StatusWithSize Write(Address address, span<const std::byte> data) override;
 
+  // Testing API
+
   // Access the underlying buffer for testing purposes. Not part of the
   // FlashMemory API.
   span<std::byte> buffer() const { return buffer_; }
 
+  bool InjectReadError(const FlashError& error) {
+    if (read_errors_.full()) {
+      return false;
+    }
+    read_errors_.push_back(error);
+    return true;
+  }
+
+  bool InjectWriteError(const FlashError& error) {
+    if (write_errors_.full()) {
+      return false;
+    }
+    write_errors_.push_back(error);
+    return true;
+  }
+
  private:
   const span<std::byte> buffer_;
+  Vector<FlashError>& read_errors_;
+  Vector<FlashError>& write_errors_;
 };
 
 // Creates an InMemoryFakeFlash backed by a std::array. The array is initialized
 // to the erased value. A byte array to which to initialize the memory may be
 // provided.
-template <size_t kSectorSize, size_t kSectorCount>
+template <size_t kSectorSize, size_t kSectorCount, size_t kInjectedErrors = 8>
 class FakeFlashBuffer : public InMemoryFakeFlash {
  public:
   // Creates a flash memory with no data written.
@@ -78,7 +172,12 @@
   // Creates a flash memory initialized to the provided contents.
   FakeFlashBuffer(span<const std::byte> contents,
                   size_t alignment_bytes = kDefaultAlignmentBytes)
-      : InMemoryFakeFlash(buffer_, kSectorSize, kSectorCount, alignment_bytes) {
+      : InMemoryFakeFlash(buffer_,
+                          kSectorSize,
+                          kSectorCount,
+                          alignment_bytes,
+                          read_errors_,
+                          write_errors_) {
     std::memset(buffer_.data(), int(kErasedValue), buffer_.size());
     std::memcpy(buffer_.data(),
                 contents.data(),
@@ -87,6 +186,8 @@
 
  private:
   std::array<std::byte, kSectorCount * kSectorSize> buffer_;
+  Vector<FlashError, kInjectedErrors> read_errors_;
+  Vector<FlashError, kInjectedErrors> write_errors_;
 };
 
 }  // namespace pw::kvs
diff --git a/pw_kvs/pw_kvs_private/byte_utils.h b/pw_kvs/pw_kvs_private/byte_utils.h
index 8eeafbc..8ed74ba 100644
--- a/pw_kvs/pw_kvs_private/byte_utils.h
+++ b/pw_kvs/pw_kvs_private/byte_utils.h
@@ -43,10 +43,20 @@
   }
 }
 
-// Converts a series of integers to a std::byte array at compile time.
+template <typename T>
+constexpr size_t SizeOfBytes(const T& arg) {
+  if constexpr (std::is_integral_v<T>) {
+    return sizeof(arg);
+  } else {
+    return arg.size();
+  }
+}
+
+// Converts a series of integers or byte arrays to a std::byte array at compile
+// time.
 template <typename... Args>
 constexpr auto AsBytes(Args... args) {
-  std::array<std::byte, (sizeof(args) + ...)> bytes{};
+  std::array<std::byte, (SizeOfBytes(args) + ...)> bytes{};
 
   auto iterator = bytes.begin();
   CopyBytes(iterator, args...);