pw_kvs: Implement checksum validation

- Enable checksum validation.
- Fix some uses of string_view that were assuming null-termination.
- Tests for checksumming are not yet enabled.

Change-Id: I7ba700c20b1e36c3f2877a8aad22a33003d7979f
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index a245dec..a778851 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -14,6 +14,7 @@
 
 #include "pw_kvs/key_value_store.h"
 
+#include <algorithm>
 #include <cstring>
 #include <type_traits>
 
@@ -29,6 +30,13 @@
 
 namespace {
 
+using Address = FlashPartition::Address;
+
+constexpr union {
+  byte bytes[4];
+  uint32_t value;
+} kNoChecksum{};
+
 // constexpr uint32_t SixFiveFiveNineNine(std::string_view string) {
 constexpr uint32_t HashKey(std::string_view string) {
   uint32_t hash = 0;
@@ -42,15 +50,10 @@
   return hash;
 }
 
-// TODO: Remove this when checksums in test are implemented.
-const uint32_t kNoChecksum = 0x12345678;
-
 constexpr size_t EntrySize(string_view key, span<const byte> value) {
   return sizeof(EntryHeader) + key.size() + value.size();
 }
 
-typedef FlashPartition::Address Address;
-
 }  // namespace
 
 Status KeyValueStore::Init() {
@@ -166,9 +169,10 @@
   // Read the key from flash & validate the entry (which reads the value).
   KeyBuffer key_buffer;
   TRY(ReadEntryKey(key_descriptor, header.key_length(), key_buffer.data()));
-  TRY(ValidateEntryChecksum(
-      header, string_view(key_buffer.data()), key_descriptor));
-  key_descriptor.key_hash = HashKey(string_view(key_buffer.data()));
+  const string_view key(key_buffer.data(), header.key_length());
+
+  TRY(ValidateEntryChecksumInFlash(header, key, key_descriptor));
+  key_descriptor.key_hash = HashKey(key);
 
   DBG("Key hash: %zx (%zu)",
       size_t(key_descriptor.key_hash),
@@ -284,13 +288,14 @@
 }
 
 const KeyValueStore::Entry& KeyValueStore::Iterator::operator*() {
-  const KeyDescriptor& key_descriptor =
-      entry_.kvs_.key_descriptor_list_[index_];
+  const KeyDescriptor& descriptor = entry_.kvs_.key_descriptor_list_[index_];
+
+  std::memset(entry_.key_buffer_.data(), 0, sizeof(entry_.key_buffer_));
 
   EntryHeader header;
-  if (entry_.kvs_.ReadEntryHeader(key_descriptor, &header).ok()) {
+  if (entry_.kvs_.ReadEntryHeader(descriptor, &header).ok()) {
     entry_.kvs_.ReadEntryKey(
-        key_descriptor, header.key_length(), entry_.key_buffer_.data());
+        descriptor, header.key_length(), entry_.key_buffer_.data());
   }
 
   return entry_;
@@ -308,28 +313,34 @@
   return StatusWithSize(header.value_length());
 }
 
-Status KeyValueStore::ValidateEntryChecksum(const EntryHeader& header,
-                                            string_view key,
-                                            const KeyDescriptor& entry) const {
-  // TODO: Remove this block once the checksums are implemented in test.
+Status KeyValueStore::ValidateEntryChecksumInFlash(
+    const EntryHeader& header,
+    const string_view key,
+    const KeyDescriptor& entry) const {
   if (entry_header_format_.checksum == nullptr) {
-    const auto header_checksum = header.checksum();
-    const auto fixed_checksum = as_bytes(span(&kNoChecksum, 1));
-    if (std::memcmp(header_checksum.data(),
-                    fixed_checksum.data(),
-                    header_checksum.size()) == 0) {
-      return Status::OK;
-    }
-    return Status::DATA_LOSS;
-
-    // TODO: It's unclear why the below version doesn't work.
-    // return header.checksum() == as_bytes(span(&kNoChecksum, 1));
+    return header.checksum_as_uint32() == 0 ? Status::OK : Status::DATA_LOSS;
   }
-  (void)(header);
-  (void)(key);
-  (void)(entry);
-  // TODO: Do incremental checksum verification by reading the value from
-  // flash a few blocks at a time.
+
+  auto& checksum = *entry_header_format_.checksum;
+  checksum.Reset();
+  checksum.Update(header.DataForChecksum());
+  checksum.Update(as_bytes(span(key)));
+
+  // Read the value piece-by-piece into a small buffer.
+  // TODO: This read may be unaligned. The partition can handle this, but
+  // consider creating a API that skips the intermediate buffering.
+  byte buffer[32];
+
+  size_t bytes_to_read = header.value_length();
+  Address address = entry.address + sizeof(header) + header.key_length();
+
+  while (bytes_to_read > 0u) {
+    const size_t read_size = std::min(sizeof(buffer), bytes_to_read);
+    TRY(partition_.Read(address, read_size, buffer));
+    address += read_size;
+    checksum.Update(buffer, read_size);
+  }
+
   return Status::OK;
 }
 
@@ -404,8 +415,10 @@
 Status KeyValueStore::ValidateEntryChecksum(const EntryHeader& header,
                                             string_view key,
                                             span<const byte> value) const {
-  // TODO: Re-enable this when we have checksum implementations.
-  return Status::OK;
+  if (entry_header_format_.checksum == nullptr) {
+    return header.checksum_as_uint32() == kNoChecksum.value ? Status::OK
+                                                            : Status::DATA_LOSS;
+  }
 
   CalculateEntryChecksum(header, key, value);
   return entry_header_format_.checksum->Verify(header.checksum());
@@ -619,10 +632,10 @@
     const string_view key,
     span<const byte> value) const {
   if (entry_header_format_.checksum == nullptr) {
-    return as_bytes(span(&kNoChecksum, 1));
+    return kNoChecksum.bytes;
   }
-  auto& checksum = *entry_header_format_.checksum;
 
+  auto& checksum = *entry_header_format_.checksum;
   checksum.Reset();
   checksum.Update(header.DataForChecksum());
   checksum.Update(as_bytes(span(key)));
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
index 48a0d69..01c0120 100644
--- a/pw_kvs/public/pw_kvs/flash_memory.h
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -81,6 +81,10 @@
   //          UNKNOWN, on HAL error
   virtual StatusWithSize Read(Address address, span<std::byte> output) = 0;
 
+  StatusWithSize Read(Address address, void* buffer, size_t len) {
+    return Read(address, span(static_cast<std::byte*>(buffer), len));
+  }
+
   // Writes bytes to flash. Blocking call.
   // Returns: OK, on success.
   //          TIMEOUT, on timeout.
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index a6cf7c0..8e9c9c6 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -71,7 +71,7 @@
   static constexpr size_t kUsableSectors = 64;
 
   // +1 for null-terminator.
-  typedef std::array<char, kMaxKeyLength + 1> KeyBuffer;
+  using KeyBuffer = std::array<char, kMaxKeyLength + 1>;
 
   // In the future, will be able to provide additional EntryHeaderFormats for
   // backwards compatibility.
@@ -149,7 +149,7 @@
     constexpr Entry(const KeyValueStore& kvs) : kvs_(kvs), key_buffer_{} {}
 
     const KeyValueStore& kvs_;
-    std::array<char, kMaxKeyLength + 1> key_buffer_;  // +1 for null-terminator
+    KeyBuffer key_buffer_;
   };
 
   class Iterator {
@@ -253,10 +253,9 @@
       const KeyDescriptor& key_descriptor);
   Status AppendEmptyDescriptor(KeyDescriptor** new_descriptor);
 
-  // This version reads from flash.
-  Status ValidateEntryChecksum(const EntryHeader& header,
-                               std::string_view key,
-                               const KeyDescriptor& entry) const;
+  Status ValidateEntryChecksumInFlash(const EntryHeader& header,
+                                      std::string_view key,
+                                      const KeyDescriptor& entry) const;
 
   Status WriteEntryForExistingKey(KeyDescriptor* key_descriptor,
                                   std::string_view key,