pw_kvs: Add alignment check to FlashPartition

In FlashPartition add checks to verify Write and Erase operations have
proper alignment.
Update comments in flash_memory.h to reflect current standards.

Change-Id: Iae270c2f6fdacf1fbaca135edc2472775913290e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/15620
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: David Rogers <davidrogers@google.com>
diff --git a/pw_kvs/entry_test.cc b/pw_kvs/entry_test.cc
index a1afab2..71e6d2e 100644
--- a/pw_kvs/entry_test.cc
+++ b/pw_kvs/entry_test.cc
@@ -208,12 +208,12 @@
   FlashPartition partition(&flash, 0, flash.sector_count(), 32);
 
   Entry entry = Entry::Valid(
-      partition, 53, kFormatWithChecksum, "key45", kValue1, kTransactionId1);
+      partition, 64, kFormatWithChecksum, "key45", kValue1, kTransactionId1);
 
   auto result = entry.Write("key45", kValue1);
   EXPECT_EQ(Status::OK, result.status());
   EXPECT_EQ(32u, result.size());
-  EXPECT_EQ(std::memcmp(&flash.buffer()[53], kEntry1.data(), kEntry1.size()),
+  EXPECT_EQ(std::memcmp(&flash.buffer()[64], kEntry1.data(), kEntry1.size()),
             0);
 }
 
diff --git a/pw_kvs/flash_memory.cc b/pw_kvs/flash_memory.cc
index aba923b..f08e3d2 100644
--- a/pw_kvs/flash_memory.cc
+++ b/pw_kvs/flash_memory.cc
@@ -47,6 +47,9 @@
   }
 
   TRY(CheckBounds(address, num_sectors * sector_size_bytes()));
+  const size_t address_sector_offset = address % sector_size_bytes();
+  PW_CHECK_UINT_EQ(address_sector_offset, 0u);
+
   return flash_.Erase(PartitionToFlashAddress(address), num_sectors);
 }
 
@@ -61,6 +64,10 @@
     return StatusWithSize::PERMISSION_DENIED;
   }
   TRY_WITH_SIZE(CheckBounds(address, data.size()));
+  const size_t address_alignment_offset = address % alignment_bytes();
+  PW_CHECK_UINT_EQ(address_alignment_offset, 0u);
+  const size_t size_alignment_offset = data.size() % alignment_bytes();
+  PW_CHECK_UINT_EQ(size_alignment_offset, 0u);
   return flash_.Write(PartitionToFlashAddress(address), data);
 }
 
diff --git a/pw_kvs/flash_partition_test.cc b/pw_kvs/flash_partition_test.cc
index 760f814..dd56254 100644
--- a/pw_kvs/flash_partition_test.cc
+++ b/pw_kvs/flash_partition_test.cc
@@ -186,11 +186,55 @@
   const size_t sector_size_bytes = test_partition.sector_size_bytes();
 
   EXPECT_LE(alignment, kMaxFlashAlignment);
+  EXPECT_GT(alignment, 0u);
   EXPECT_EQ(kMaxFlashAlignment % alignment, 0U);
   EXPECT_LE(kMaxFlashAlignment, sector_size_bytes);
   EXPECT_LE(sector_size_bytes % kMaxFlashAlignment, 0U);
 }
 
+#if CHECK_TEST_CRASHES
+
+// TODO: Ensure that this test triggers an assert.
+TEST(FlashPartitionTest, BadWriteAddressAlignment) {
+  FlashPartition& test_partition = FlashTestPartition();
+
+  // Can't get bad alignment with alignment of 1.
+  if (test_partition.alignment_bytes() == 1) {
+    return;
+  }
+
+  std::array<std::byte, kMaxFlashAlignment> source_data;
+  test_partition.Write(1, source_data);
+}
+
+// TODO: Ensure that this test triggers an assert.
+TEST(FlashPartitionTest, BadWriteSizeAlignment) {
+  FlashPartition& test_partition = FlashTestPartition();
+
+  // Can't get bad alignment with alignment of 1.
+  if (test_partition.alignment_bytes() == 1) {
+    return;
+  }
+
+  std::array<std::byte, 1> source_data;
+  test_partition.Write(0, source_data);
+}
+
+// TODO: Ensure that this test triggers an assert.
+TEST(FlashPartitionTest, BadEraseAddressAlignment) {
+  FlashPartition& test_partition = FlashTestPartition();
+
+  // Can't get bad alignment with sector size of 1.
+  if (test_partition.sector_size_bytes() == 1) {
+    return;
+  }
+
+  // Try Erase at address 1 for 1 sector.
+  test_partition.Erase(1, 1);
+}
+
+#endif  // CHECK_TEST_CRASHES
+
 TEST(FlashPartitionTest, IsErased) {
   FlashPartition& test_partition = FlashTestPartition();
   const size_t alignment = test_partition.alignment_bytes();
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
index a9ad8b2..2ba5ea1 100644
--- a/pw_kvs/public/pw_kvs/flash_memory.h
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -52,38 +52,39 @@
   virtual ~FlashMemory() = default;
 
   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.
+  // Address should be on a sector boundary. Returns:
   //
-  //                OK: success
-  // DEADLINE_EXCEEDED: timeout
-  //  INVALID_ARGUMENT: address is not sector-aligned
-  //      OUT_OF_RANGE: erases past the end of the memory
-  //
+  // 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.
+  // Reads bytes from flash into buffer. Blocking call. Returns:
   //
-  //                OK: success
-  // DEADLINE_EXCEEDED: timeout
-  //      OUT_OF_RANGE: write does not fit in the flash memory
+  // OK - success
+  // DEADLINE_EXCEEDED - timeout
+  // OUT_OF_RANGE - write does not fit in the flash memory
   virtual StatusWithSize Read(Address address, std::span<std::byte> output) = 0;
 
   StatusWithSize Read(Address address, void* buffer, size_t len) {
     return Read(address, std::span(static_cast<std::byte*>(buffer), len));
   }
 
-  // Writes bytes to flash. Blocking call.
+  // Writes bytes to flash. Blocking call. Returns:
   //
-  //                OK: success
-  // DEADLINE_EXCEEDED: timeout
-  //  INVALID_ARGUMENT: address or data size are not aligned
-  //      OUT_OF_RANGE: write does not fit in the memory
-  //
+  // 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,
                                std::span<const std::byte> data) = 0;
 
@@ -102,14 +103,20 @@
   // sector start is not 0. (ex.: cases where there are portions of flash
   // that should be handled independently).
   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 start_address() const { return start_address_; }
+
   constexpr std::byte erased_memory_content() const {
     return erased_memory_content_;
   }
@@ -190,42 +197,47 @@
   virtual Status Init() { return Status::OK; }
 
   // 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
+  // Address must be on a sector boundary. Returns:
+  //
+  // OK - success.
+  // TIMEOUT - on timeout.
+  // INVALID_ARGUMENT - address or sector count is invalid.
+  // PERMISSION_DENIED - partition is read only.
+  // UNKNOWN - HAL error
   virtual Status Erase(Address address, size_t num_sectors);
 
   Status Erase() { return Erase(0, this->sector_count()); }
 
-  // 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
+  // Reads bytes from flash into buffer. Blocking call. Returns:
+  //
+  // OK - success.
+  // TIMEOUT - on timeout.
+  // INVALID_ARGUMENT - address or length is invalid.
+  // UNKNOWN - HAL error
   virtual StatusWithSize Read(Address address, std::span<std::byte> output);
 
   StatusWithSize Read(Address address, size_t length, void* output) {
     return Read(address, std::span(static_cast<std::byte*>(output), length));
   }
 
-  // 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
+  // Writes bytes to flash. Address and data.size_bytes() must both be a
+  // multiple of alignment_bytes(). Blocking call. Returns:
+  //
+  // OK - success.
+  // TIMEOUT - on timeout.
+  // INVALID_ARGUMENT - address or length is invalid.
+  // PERMISSION_DENIED - partition is read only.
+  // UNKNOWN - HAL error
   virtual StatusWithSize Write(Address address,
                                std::span<const std::byte> data);
 
   // Check to see if chunk of flash partition 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
+  // be aligned with FlashMemory. Returns:
+  //
+  // OK - success.
+  // TIMEOUT - on timeout.
+  // INVALID_ARGUMENT - address or length is invalid.
+  // UNKNOWN - HAL error
   // TODO: Result<bool>
   virtual Status IsRegionErased(Address source_flash_address,
                                 size_t len,
@@ -251,6 +263,7 @@
 
   size_t size_bytes() const { return sector_count() * sector_size_bytes(); }
 
+  // Alignment required for write address and write size.
   size_t alignment_bytes() const { return alignment_bytes_; }
 
   size_t sector_count() const { return sector_count_; }
@@ -278,6 +291,7 @@
 
  protected:
   Status CheckBounds(Address address, size_t len) const;
+
   FlashMemory& flash() const { return flash_; }
 
  private: