pw_kvs: Check for values that are too large

- Reject values that are too large to fit in one sector.
- Clean up KeyDescriptor usage.

Change-Id: I8575bd0f3b7e9c6c2a5c5e9ef5ba1bc0dd81da7b
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index b18d4f2..98a31d1 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -43,8 +43,7 @@
       entry_header_format_(format),
       options_(options),
       sectors_(partition_.sector_count()),
-      last_new_sector_(sectors_.data()),
-      working_buffer_{} {}
+      last_new_sector_(sectors_.data()) {}
 
 Status KeyValueStore::Init() {
   if (kMaxUsableSectors < sectors_.size()) {
@@ -186,36 +185,26 @@
   // With the new key descriptor, either add it to the descriptor table or
   // overwrite an existing entry with an older version of the key.
   KeyDescriptor* existing_descriptor = FindDescriptor(key_descriptor.key_hash);
-  if (existing_descriptor) {
-    if (existing_descriptor->key_version < key_descriptor.key_version) {
-      // Existing entry is old; replace the existing entry with the new one.
-      *existing_descriptor = key_descriptor;
-    } else {
-      // Otherwise, check for data integrity and leave the existing entry.
-      if (existing_descriptor->key_version == key_descriptor.key_version) {
-        ERR("Data loss: Duplicated old(=%zu) and new(=%zu) version",
-            size_t(existing_descriptor->key_version),
-            size_t(key_descriptor.key_version));
-        return Status::DATA_LOSS;
-      }
-      DBG("Found stale entry when appending; ignoring");
-    }
-    return Status::OK;
-  }
-  // Write new entry.
-  KeyDescriptor* newly_allocated_key_descriptor;
-  TRY(AppendEmptyDescriptor(&newly_allocated_key_descriptor));
-  *newly_allocated_key_descriptor = key_descriptor;
-  return Status::OK;
-}
 
-// TODO: Need a better name.
-Status KeyValueStore::AppendEmptyDescriptor(KeyDescriptor** new_descriptor) {
-  if (key_descriptors_.full()) {
-    return Status::RESOURCE_EXHAUSTED;
+  // Write a new entry.
+  if (existing_descriptor == nullptr) {
+    if (key_descriptors_.full()) {
+      return Status::RESOURCE_EXHAUSTED;
+    }
+    key_descriptors_.push_back(key_descriptor);
+  } else if (existing_descriptor->key_version < key_descriptor.key_version) {
+    // Existing entry is old; replace the existing entry with the new one.
+    *existing_descriptor = key_descriptor;
+  } else {
+    // Otherwise, check for data integrity and leave the existing entry.
+    if (existing_descriptor->key_version == key_descriptor.key_version) {
+      ERR("Data loss: Duplicated old(=%zu) and new(=%zu) version",
+          size_t(existing_descriptor->key_version),
+          size_t(key_descriptor.key_version));
+      return Status::DATA_LOSS;
+    }
+    DBG("Found stale entry when appending; ignoring");
   }
-  key_descriptors_.emplace_back();
-  *new_descriptor = &key_descriptors_.back();
   return Status::OK;
 }
 
@@ -262,8 +251,11 @@
 
   TRY(CheckOperation(key));
 
-  if (value.size() > (1 << 24)) {
-    // TODO: Reject sizes that are larger than the maximum?
+  if (Entry::size(partition_, key, value) > partition_.sector_size_bytes()) {
+    DBG("%zu B value with %zu B key cannot fit in one sector",
+        value.size(),
+        key.size());
+    return Status::INVALID_ARGUMENT;
   }
 
   KeyDescriptor* key_descriptor;
@@ -450,8 +442,8 @@
   SectorDescriptor* old_sector = SectorFromAddress(key_descriptor->address);
 
   SectorDescriptor* sector;
-  TRY(FindOrRecoverSectorWithSpace(
-      &sector, Entry::size(partition_.alignment_bytes(), key, value)));
+  TRY(FindOrRecoverSectorWithSpace(&sector,
+                                   Entry::size(partition_, key, value)));
   DBG("Writing existing entry; found sector: %zu", SectorIndex(sector));
 
   if (old_sector != SectorFromAddress(key_descriptor->address)) {
@@ -482,8 +474,8 @@
   KeyDescriptor key_descriptor(key, 0, 0);
 
   SectorDescriptor* sector;
-  TRY(FindOrRecoverSectorWithSpace(
-      &sector, Entry::size(partition_.alignment_bytes(), key, value)));
+  TRY(FindOrRecoverSectorWithSpace(&sector,
+                                   Entry::size(partition_, key, value)));
   DBG("Writing new entry; found sector: %zu", SectorIndex(sector));
   TRY(AppendEntry(sector, &key_descriptor, key, value, KeyDescriptor::kValid));
 
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 1644e7c..0194b81 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -282,6 +282,22 @@
   }
 }
 
+TEST_F(EmptyInitializedKvs, Put_MaxValueSize) {
+  size_t max_value_size =
+      test_partition.sector_size_bytes() - sizeof(EntryHeader) - 1;
+
+  // Use the large_test_flash as a big chunk of data for the Put statement.
+  ASSERT_GT(sizeof(large_test_flash), max_value_size + 2 * sizeof(EntryHeader));
+  auto big_data = as_bytes(span(&large_test_flash, 1));
+
+  EXPECT_EQ(Status::OK, kvs_.Put("K", big_data.subspan(0, max_value_size)));
+
+  // Larger than maximum is rejected.
+  EXPECT_EQ(Status::INVALID_ARGUMENT,
+            kvs_.Put("K", big_data.subspan(0, max_value_size + 1)));
+  EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Put("K", big_data));
+}
+
 TEST_F(EmptyInitializedKvs, Delete_GetDeletedKey_ReturnsNotFound) {
   ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
   ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index b47041e..72dd1a4 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -236,8 +236,6 @@
   struct KeyDescriptor {
     enum State { kValid, kDeleted };
 
-    KeyDescriptor() = default;
-
     KeyDescriptor(std::string_view key,
                   uint32_t version,
                   Address addr,
@@ -401,7 +399,7 @@
   // Working buffer is a general purpose buffer for operations (such as init or
   // relocate) to use for working space to remove the need to allocate temporary
   // space.
-  std::array<char, kWorkingBufferSizeBytes> working_buffer_;
+  std::array<std::byte, kWorkingBufferSizeBytes> working_buffer_;
 };
 
 }  // namespace pw::kvs
diff --git a/pw_kvs/pw_kvs_private/entry.h b/pw_kvs/pw_kvs_private/entry.h
index 4cae4bb..3741ea1 100644
--- a/pw_kvs/pw_kvs_private/entry.h
+++ b/pw_kvs/pw_kvs_private/entry.h
@@ -15,6 +15,7 @@
 // This file defines classes for managing the in-flash format for KVS entires.
 #pragma once
 
+#include <algorithm>
 #include <array>
 #include <cstddef>
 #include <cstdint>
@@ -60,7 +61,6 @@
  public:
   static constexpr size_t kMinAlignmentBytes = sizeof(EntryHeader);
   static constexpr size_t kMaxKeyLength = 0b111111;
-  static constexpr size_t kMaxValueSize = 0xFFFF;
 
   using Address = FlashPartition::Address;
 
@@ -144,11 +144,11 @@
   Status VerifyChecksumInFlash(ChecksumAlgorithm* algorithm) const;
 
   // Calculates the total size of an entry, including padding.
-  static constexpr size_t size(size_t alignment_bytes,
-                               std::string_view key,
-                               span<const std::byte> value) {
+  static size_t size(const FlashPartition& partition,
+                     std::string_view key,
+                     span<const std::byte> value) {
     return AlignUp(sizeof(EntryHeader) + key.size() + value.size(),
-                   alignment_bytes);
+                   std::max(partition.alignment_bytes(), kMinAlignmentBytes));
   }
 
   // The address at which the next possible entry could be located.