pw_kvs: Implement AlignedChecksum

- Provide AlignedChecksum class that uses AlignedWriter to ensure that
  checksum updates are aligned as required.
- Make Finish() non-virtual and call an optional Finalize() operation
  instead.

Change-Id: I5b16468b6a55c0c2c9f2c06e37988970af147a11
diff --git a/pw_kvs/alignment_test.cc b/pw_kvs/alignment_test.cc
index 2b0771b..232a8e3 100644
--- a/pw_kvs/alignment_test.cc
+++ b/pw_kvs/alignment_test.cc
@@ -94,6 +94,8 @@
 TEST(AlignedWriter, VaryingLengthWriteCalls) {
   static constexpr size_t kAlignment = 10;
 
+  // The output function checks that the data is properly aligned and matches
+  // the expected value (should always be 123456789_...).
   OutputToFunction output([](span<const byte> data) {
     EXPECT_EQ(data.size() % kAlignment, 0u);
     EXPECT_EQ(kData.substr(0, data.size()),
@@ -102,19 +104,26 @@
     return StatusWithSize(data.size());
   });
 
-  AlignedWriterBuffer<64> writer(kAlignment, output);
+  AlignedWriterBuffer<32> writer(kAlignment, output);
 
+  // Write values smaller than the alignment.
   EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(0, 1)));
   EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(1, 9)));
+
+  // Write values larger than the alignment but smaller than the buffer.
   EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(10, 11)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(21, 20)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(41, 9)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(50, 10)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(60, 30)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(90, 5)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(95, 0)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(95, 4)));
-  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(99, 1)));
+
+  // Exactly fill the remainder of the buffer.
+  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(21, 11)));
+
+  // Fill the buffer more than once.
+  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(32, 66)));
+
+  // Write nothing.
+  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(98, 0)));
+
+  // Write the remaining data.
+  EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(98, 2)));
 
   auto result = writer.Flush();
   EXPECT_EQ(Status::OK, result.status());
diff --git a/pw_kvs/checksum_test.cc b/pw_kvs/checksum_test.cc
index 1939ff3..205defb 100644
--- a/pw_kvs/checksum_test.cc
+++ b/pw_kvs/checksum_test.cc
@@ -67,5 +67,64 @@
   EXPECT_EQ(state[1], byte{0xFF});
 }
 
+constexpr size_t kAlignment = 10;
+
+constexpr std::string_view kData =
+    "123456789_123456789_123456789_123456789_123456789_"   //  50
+    "123456789_123456789_123456789_123456789_123456789_";  // 100
+const span<const byte> kBytes = as_bytes(span(kData));
+
+class PickyChecksum final : public AlignedChecksum<kAlignment, 32> {
+ public:
+  PickyChecksum() : AlignedChecksum(data_), data_{}, size_(0) {}
+
+  void Reset() override {}
+
+  void FinalizeAligned() override { EXPECT_EQ(kData.size(), size_); }
+
+  void UpdateAligned(span<const std::byte> data) override {
+    ASSERT_EQ(data.size() % kAlignment, 0u);
+    EXPECT_EQ(kData.substr(0, data.size()),
+              std::string_view(reinterpret_cast<const char*>(data.data()),
+                               data.size()));
+
+    std::memcpy(&data_[size_], data.data(), data.size());
+    size_ += data.size();
+  }
+
+ private:
+  std::byte data_[kData.size()];
+  size_t size_;
+};
+
+TEST(AlignedChecksum, MaintainsAlignment) {
+  PickyChecksum checksum;
+
+  // Write values smaller than the alignment.
+  checksum.Update(kBytes.subspan(0, 1));
+  checksum.Update(kBytes.subspan(1, 9));
+
+  // Write values larger than the alignment but smaller than the buffer.
+  checksum.Update(kBytes.subspan(10, 11));
+
+  // Exactly fill the remainder of the buffer.
+  checksum.Update(kBytes.subspan(21, 11));
+
+  // Fill the buffer more than once.
+  checksum.Update(kBytes.subspan(32, 66));
+
+  // Write nothing.
+  checksum.Update(kBytes.subspan(98, 0));
+
+  // Write the remaining data.
+  checksum.Update(kBytes.subspan(98, 2));
+
+  auto state = checksum.Finish();
+  EXPECT_EQ(std::string_view(reinterpret_cast<const char*>(state.data()),
+                             state.size()),
+            kData);
+  EXPECT_EQ(Status::OK, checksum.Verify(kBytes));
+}
+
 }  // namespace
 }  // namespace pw::kvs
diff --git a/pw_kvs/format.cc b/pw_kvs/format.cc
index cb37591..2c09250 100644
--- a/pw_kvs/format.cc
+++ b/pw_kvs/format.cc
@@ -36,10 +36,10 @@
       value_length_bytes_(value_length_bytes),
       key_version_(key_version) {
   if (algorithm != nullptr) {
-    CalculateChecksum(algorithm, key, value);
+    span<const byte> checksum = CalculateChecksum(algorithm, key, value);
     std::memcpy(&checksum_,
-                algorithm->Finish().data(),
-                std::min(algorithm->size_bytes(), sizeof(checksum_)));
+                checksum.data(),
+                std::min(checksum.size(), sizeof(checksum_)));
   }
 
   // TODO: 0 is an invalid alignment value. There should be an assert for this.
@@ -53,7 +53,6 @@
     return checksum() == kNoChecksum ? Status::OK : Status::DATA_LOSS;
   }
   CalculateChecksum(algorithm, key, value);
-  algorithm->Finish();
   return algorithm->Verify(checksum_bytes());
 }
 
@@ -104,19 +103,21 @@
     address += read_size;
     bytes_to_read -= read_size;
   }
-  algorithm->Finish();
 
+  algorithm->Finish();
   return algorithm->Verify(checksum_bytes());
 }
 
-void EntryHeader::CalculateChecksum(ChecksumAlgorithm* algorithm,
-                                    const string_view key,
-                                    span<const byte> value) const {
+span<const byte> EntryHeader::CalculateChecksum(ChecksumAlgorithm* algorithm,
+                                                const string_view key,
+                                                span<const byte> value) const {
   algorithm->Reset();
   algorithm->Update(reinterpret_cast<const byte*>(this) + checked_data_offset(),
                     sizeof(*this) - checked_data_offset());
   algorithm->Update(as_bytes(span(key)));
   algorithm->Update(value);
+
+  return algorithm->Finish();
 }
 
 }  // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/checksum.h b/pw_kvs/public/pw_kvs/checksum.h
index c7c883a..3acb5a4 100644
--- a/pw_kvs/public/pw_kvs/checksum.h
+++ b/pw_kvs/public/pw_kvs/checksum.h
@@ -15,6 +15,7 @@
 
 #include <cstddef>
 
+#include "pw_kvs/alignment.h"
 #include "pw_span/span.h"
 #include "pw_status/status.h"
 
@@ -28,21 +29,28 @@
   // Updates the checksum with the provided data.
   virtual void Update(span<const std::byte> data) = 0;
 
-  // Update the checksum from a pointer and size.
+  // Updates the checksum from a pointer and size.
   void Update(const void* data, size_t size_bytes) {
     return Update(span(static_cast<const std::byte*>(data), size_bytes));
   }
 
   // Returns the final result of the checksum. Update() can no longer be called
   // after this. The returned span is valid until a call to Reset().
-  virtual span<const std::byte> Finish() = 0;
+  //
+  // Finish MUST be called before calling Verify.
+  span<const std::byte> Finish() {
+    Finalize();  // Implemented by derived classes, if required.
+    return state();
+  }
 
   // Returns the size of the checksum state.
   constexpr size_t size_bytes() const { return state_.size(); }
 
-  // Compares a calculated checksum to this checksum's current state. The
-  // checksum must be at least as large as size_bytes(). If it is larger, bytes
-  // beyond size_bytes() are ignored.
+  // Compares a calculated checksum to this checksum's state. The checksum must
+  // be at least as large as size_bytes(). If it is larger, bytes beyond
+  // size_bytes() are ignored.
+  //
+  // Finish MUST be called before calling Verify.
   Status Verify(span<const std::byte> checksum) const;
 
  protected:
@@ -57,7 +65,40 @@
   constexpr span<const std::byte> state() const { return state_; }
 
  private:
+  // Checksums that require finalizing operations may override this method.
+  virtual void Finalize() {}
+
   span<const std::byte> state_;
 };
 
+// Calculates a checksum in kAlignmentBytes chunks. Checksum classes can inherit
+// from this and implement UpdateAligned and FinalizeAligned instead of Update
+// and Finalize.
+template <size_t kAlignmentBytes, size_t kBufferSize = kAlignmentBytes>
+class AlignedChecksum : public ChecksumAlgorithm {
+ public:
+  void Update(span<const std::byte> data) final { writer_.Write(data); }
+
+ protected:
+  constexpr AlignedChecksum(span<const std::byte> state)
+      : ChecksumAlgorithm(state),
+        output_(this),
+        writer_(kAlignmentBytes, output_) {}
+
+  ~AlignedChecksum() = default;
+
+ private:
+  void Finalize() final {
+    writer_.Flush();
+    FinalizeAligned();
+  }
+
+  virtual void UpdateAligned(span<const std::byte> data) = 0;
+
+  virtual void FinalizeAligned() = 0;
+
+  OutputToMethod<&AlignedChecksum::UpdateAligned> output_;
+  AlignedWriterBuffer<kBufferSize> writer_;
+};
+
 }  // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/crc16_checksum.h b/pw_kvs/public/pw_kvs/crc16_checksum.h
index c2a0c7c..51b096b 100644
--- a/pw_kvs/public/pw_kvs/crc16_checksum.h
+++ b/pw_kvs/public/pw_kvs/crc16_checksum.h
@@ -23,14 +23,12 @@
  public:
   ChecksumCrc16() : ChecksumAlgorithm(as_bytes(span(&crc_, 1))) {}
 
-  void Reset() final { crc_ = checksum::kCcittCrc16DefaultInitialValue; }
+  void Reset() override { crc_ = checksum::kCcittCrc16DefaultInitialValue; }
 
-  void Update(span<const std::byte> data) final {
+  void Update(span<const std::byte> data) override {
     crc_ = checksum::CcittCrc16(data, crc_);
   }
 
-  span<const std::byte> Finish() final { return state(); }
-
  private:
   uint16_t crc_ = checksum::kCcittCrc16DefaultInitialValue;
 };
diff --git a/pw_kvs/pw_kvs_private/format.h b/pw_kvs/pw_kvs_private/format.h
index db42b12..e837238 100644
--- a/pw_kvs/pw_kvs_private/format.h
+++ b/pw_kvs/pw_kvs_private/format.h
@@ -137,9 +137,9 @@
     return as_bytes(span(&checksum_, 1));
   }
 
-  void CalculateChecksum(ChecksumAlgorithm* algorithm,
-                         std::string_view key,
-                         span<const std::byte> value) const;
+  span<const std::byte> CalculateChecksum(ChecksumAlgorithm* algorithm,
+                                          std::string_view key,
+                                          span<const std::byte> value) const;
 
   static constexpr uint8_t alignment_bytes_to_units(size_t alignment_bytes) {
     return (alignment_bytes + 15) / 16 - 1;  // An alignment of 0 is invalid.