pw_kvs: FlashMemory updates; test utilities

- Update comments for FlashMemory class.
- Split InMemoryFakeFlash to move the fixed-size buffer into the
  derived FakeFlashBuffer class.
- Support initializing FakeFlashBuffer to data provided at construction.
- Allow direct access to the underlying fake flash buffer for testing.
- Create utilities for working with byte arrays in byte_utils.h.

Change-Id: I90d33621cb91da079d7213fe7d33823494120e48
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
index 5d8f558..93ee1eb 100644
--- a/pw_kvs/public/pw_kvs/flash_memory.h
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -58,17 +58,19 @@
 
   // 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
+  //
+  //                OK: success
+  // DEADLINE_EXCEEDED: timeout
+  //  INVALID_ARGUMENT: address is not sector-aligned
+  //      OUT_OF_RANGE: erases past the end of the memory
+  //
   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
+  //
+  //                OK: success
+  // DEADLINE_EXCEEDED: timeout
+  //      OUT_OF_RANGE: write does not fit in the flash memory
   virtual StatusWithSize Read(Address address, span<std::byte> output) = 0;
 
   StatusWithSize Read(Address address, void* buffer, size_t len) {
@@ -76,10 +78,12 @@
   }
 
   // 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
+  //
+  //                OK: success
+  // DEADLINE_EXCEEDED: timeout
+  //  INVALID_ARGUMENT: address or data size are not aligned
+  //      OUT_OF_RANGE: write does not fit in the memory
+  //
   virtual StatusWithSize Write(Address destination_flash_address,
                                span<const std::byte> data) = 0;
 
@@ -150,6 +154,11 @@
                                               : alignment_bytes),
         permission_(permission) {}
 
+  // Creates a FlashPartition that uses the entire flash with its alignment.
+  constexpr FlashPartition(FlashMemory* flash)
+      : FlashPartition(
+            flash, 0, flash->sector_count(), flash->alignment_bytes()) {}
+
   FlashPartition(const FlashPartition&) = delete;
   FlashPartition& operator=(const FlashPartition&) = delete;
 
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 4037161..761e57c 100644
--- a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
+++ b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
@@ -13,94 +13,80 @@
 // the License.
 #pragma once
 
+#include <algorithm>
 #include <array>
+#include <cstddef>
 #include <cstring>
 
-// TODO: Push/pop log module name due to logging in header.
-//       Alternately: Push implementation into .cc
 #include "pw_kvs/flash_memory.h"
-#include "pw_log/log.h"
+#include "pw_span/span.h"
 #include "pw_status/status.h"
 
 namespace pw::kvs {
 
-// 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>
+// 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 {
  public:
-  InMemoryFakeFlash(uint8_t alignment_bytes = 1)  // default 8 bit alignment
-      : FlashMemory(kSectorSize, kSectorCount, alignment_bytes) {}
+  // Default to 8-bit alignment.
+  static constexpr size_t kDefaultAlignmentBytes = 1;
 
-  // Always enabled
+  static constexpr std::byte kErasedValue = std::byte{0xff};
+
+  InMemoryFakeFlash(span<std::byte> buffer,
+                    size_t sector_size,
+                    size_t sector_count,
+                    size_t alignment_bytes = kDefaultAlignmentBytes)
+      : FlashMemory(sector_size, sector_count, alignment_bytes),
+        buffer_(buffer) {}
+
+  // The fake flash is 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 address, size_t num_sectors) override {
-    if (address % sector_size_bytes() != 0) {
-      PW_LOG_ERROR(
-          "Attempted to erase sector at non-sector aligned boundary: %zx",
-          size_t(address));
-      return Status::INVALID_ARGUMENT;
-    }
-    size_t sector_id = address / sector_size_bytes();
-    if (address / sector_size_bytes() + num_sectors > sector_count()) {
-      PW_LOG_ERROR(
-          "Tried to erase a sector at an address past partition end; "
-          "address: %zx, sector implied: %zu",
-          size_t(address),
-          sector_id);
-      return Status::UNKNOWN;
-    }
-    if (address % alignment_bytes() != 0) {
-      return Status::INVALID_ARGUMENT;
-    }
-    std::memset(&buffer_[address], 0xFF, sector_size_bytes() * num_sectors);
-    return Status::OK;
-  }
+  // Erase num_sectors starting at a given address.
+  Status Erase(Address address, size_t num_sectors) override;
 
-  // Reads bytes from flash into buffer. Blocking call.
-  // Returns: OK, on success.
-  //          INVALID_ARGUMENT, if address or length is invalid.
-  //          UNKNOWN, on HAL error
-  StatusWithSize Read(Address address, span<std::byte> output) override {
-    if (address + output.size() >= sector_count() * size_bytes()) {
-      return StatusWithSize(Status::INVALID_ARGUMENT);
-    }
-    std::memcpy(output.data(), &buffer_[address], output.size());
-    return StatusWithSize(output.size());
-  }
+  // Reads bytes from flash into buffer.
+  StatusWithSize Read(Address address, span<std::byte> output) override;
 
-  // Writes bytes to flash. Blocking call.
-  // Returns: OK, on success.
-  //          INVALID_ARGUMENT, if address or length is invalid.
-  //          UNKNOWN, on HAL error
-  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 StatusWithSize(Status::INVALID_ARGUMENT);
-    }
-    // Check in erased state
-    for (unsigned i = 0; i < data.size(); i++) {
-      if (buffer_[address + i] != 0xFF) {
-        PW_LOG_ERROR("Writing to previously written address: %zx",
-                     size_t(address));
-        return StatusWithSize(Status::UNKNOWN);
-      }
-    }
-    std::memcpy(&buffer_[address], data.data(), data.size());
-    return StatusWithSize(data.size());
+  // Writes bytes to flash.
+  StatusWithSize Write(Address address, span<const std::byte> data) override;
+
+  // Access the underlying buffer for testing purposes. Not part of the
+  // FlashMemory API.
+  span<std::byte> buffer() const { return buffer_; }
+
+ private:
+  const span<std::byte> buffer_;
+};
+
+// 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>
+class FakeFlashBuffer : public InMemoryFakeFlash {
+ public:
+  // Creates a flash memory with no data written.
+  FakeFlashBuffer(size_t alignment_bytes = kDefaultAlignmentBytes)
+      : FakeFlashBuffer(std::array<std::byte, 0>{}, alignment_bytes) {}
+
+  // 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) {
+    std::memset(buffer_.data(), int(kErasedValue), buffer_.size());
+    std::memcpy(buffer_.data(),
+                contents.data(),
+                std::min(contents.size(), buffer_.size()));
   }
 
  private:
-  std::array<uint8_t, kSectorCount * kSectorSize> buffer_;
+  std::array<std::byte, kSectorCount * kSectorSize> buffer_;
 };
 
 }  // namespace pw::kvs