pw_kvs: Allow specifying an offset in Get

Change-Id: Id0b27a3b7cf3d7fe53e934e2a4822f00f21cb517
diff --git a/pw_kvs/entry.cc b/pw_kvs/entry.cc
index 458a12c..33e86a8 100644
--- a/pw_kvs/entry.cc
+++ b/pw_kvs/entry.cc
@@ -89,14 +89,20 @@
       {as_bytes(span(&header_, 1)), as_bytes(span(key)), value});
 }
 
-StatusWithSize Entry::ReadValue(span<byte> value) const {
-  const size_t read_size = std::min(value_size(), value.size());
-  StatusWithSize result =
-      partition().Read(address_ + sizeof(EntryHeader) + key_length(),
-                       value.subspan(0, read_size));
+StatusWithSize Entry::ReadValue(span<byte> buffer, size_t offset_bytes) const {
+  if (offset_bytes > value_size()) {
+    return StatusWithSize(Status::OUT_OF_RANGE);
+  }
+
+  const size_t remaining_bytes = value_size() - offset_bytes;
+  const size_t read_size = std::min(buffer.size(), remaining_bytes);
+
+  StatusWithSize result = partition().Read(
+      address_ + sizeof(EntryHeader) + key_length() + offset_bytes,
+      buffer.subspan(0, read_size));
   TRY_WITH_SIZE(result);
 
-  if (read_size != value_size()) {
+  if (read_size != remaining_bytes) {
     return StatusWithSize(Status::RESOURCE_EXHAUSTED, read_size);
   }
   return StatusWithSize(read_size);
diff --git a/pw_kvs/entry_test.cc b/pw_kvs/entry_test.cc
index 00a5bd9..5022034 100644
--- a/pw_kvs/entry_test.cc
+++ b/pw_kvs/entry_test.cc
@@ -133,7 +133,7 @@
   EXPECT_STREQ(value, "VALUE!");
 }
 
-TEST_F(ValidEntryInFlash, ReadValue_ResourceExhaustedIfBufferFills) {
+TEST_F(ValidEntryInFlash, ReadValue_BufferTooSmall) {
   char value[3] = {};
   auto result = entry_.ReadValue(as_writable_bytes(span(value)));
 
@@ -144,6 +144,43 @@
   EXPECT_EQ(value[2], 'L');
 }
 
+TEST_F(ValidEntryInFlash, ReadValue_WithOffset) {
+  char value[3] = {};
+  auto result = entry_.ReadValue(as_writable_bytes(span(value)), 3);
+
+  ASSERT_EQ(Status::OK, result.status());
+  EXPECT_EQ(3u, result.size());
+  EXPECT_EQ(value[0], 'U');
+  EXPECT_EQ(value[1], 'E');
+  EXPECT_EQ(value[2], '!');
+}
+
+TEST_F(ValidEntryInFlash, ReadValue_WithOffset_BufferTooSmall) {
+  char value[1] = {};
+  auto result = entry_.ReadValue(as_writable_bytes(span(value)), 4);
+
+  ASSERT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
+  EXPECT_EQ(1u, result.size());
+  EXPECT_EQ(value[0], 'E');
+}
+
+TEST_F(ValidEntryInFlash, ReadValue_WithOffset_EmptyRead) {
+  char value[16] = {'?'};
+  auto result = entry_.ReadValue(as_writable_bytes(span(value)), 6);
+
+  ASSERT_EQ(Status::OK, result.status());
+  EXPECT_EQ(0u, result.size());
+  EXPECT_EQ(value[0], '?');
+}
+
+TEST_F(ValidEntryInFlash, ReadValue_WithOffset_PastEnd) {
+  char value[16] = {};
+  auto result = entry_.ReadValue(as_writable_bytes(span(value)), 7);
+
+  EXPECT_EQ(Status::OUT_OF_RANGE, result.status());
+  EXPECT_EQ(0u, result.size());
+}
+
 TEST(ValidEntry, Write) {
   FakeFlashBuffer<1024, 4> flash;
   FlashPartition partition(&flash);
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 72fc21f..32fbfc2 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -233,7 +233,8 @@
 }
 
 StatusWithSize KeyValueStore::Get(string_view key,
-                                  span<byte> value_buffer) const {
+                                  span<byte> value_buffer,
+                                  size_t offset_bytes) const {
   TRY_WITH_SIZE(CheckOperation(key));
 
   const KeyDescriptor* key_descriptor;
@@ -242,8 +243,8 @@
   Entry entry;
   TRY_WITH_SIZE(Entry::Read(partition_, key_descriptor->address, &entry));
 
-  StatusWithSize result = entry.ReadValue(value_buffer);
-  if (result.ok() && options_.verify_on_read) {
+  StatusWithSize result = entry.ReadValue(value_buffer, offset_bytes);
+  if (result.ok() && options_.verify_on_read && offset_bytes == 0u) {
     Status verify_result =
         entry.VerifyChecksum(entry_header_format_.checksum,
                              key,
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 0194b81..8a4a6d8 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -298,6 +298,46 @@
   EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Put("K", big_data));
 }
 
+TEST_F(EmptyInitializedKvs, Get_Simple) {
+  ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
+
+  char value[16];
+  auto result = kvs_.Get("Charles", as_writable_bytes(span(value)));
+  EXPECT_EQ(Status::OK, result.status());
+  EXPECT_EQ(sizeof("Mingus"), result.size());
+  EXPECT_STREQ("Mingus", value);
+}
+
+TEST_F(EmptyInitializedKvs, Get_WithOffset) {
+  ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
+
+  char value[16];
+  auto result = kvs_.Get("Charles", as_writable_bytes(span(value)), 4);
+  EXPECT_EQ(Status::OK, result.status());
+  EXPECT_EQ(sizeof("Mingus") - 4, result.size());
+  EXPECT_STREQ("us", value);
+}
+
+TEST_F(EmptyInitializedKvs, Get_WithOffset_FillBuffer) {
+  ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
+
+  char value[4] = {};
+  auto result = kvs_.Get("Charles", as_writable_bytes(span(value, 3)), 1);
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
+  EXPECT_EQ(3u, result.size());
+  EXPECT_STREQ("ing", value);
+}
+
+TEST_F(EmptyInitializedKvs, Get_WithOffset_PastEnd) {
+  ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
+
+  char value[16];
+  auto result =
+      kvs_.Get("Charles", as_writable_bytes(span(value)), sizeof("Mingus") + 1);
+  EXPECT_EQ(Status::OUT_OF_RANGE, result.status());
+  EXPECT_EQ(0u, result.size());
+}
+
 TEST_F(EmptyInitializedKvs, Delete_GetDeletedKey_ReturnsNotFound) {
   ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
   ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
@@ -385,16 +425,26 @@
 }
 
 TEST_F(EmptyInitializedKvs, Iteration_OneItem) {
-  char value[] = "123";
-
-  ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span(value))));
+  ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
 
   for (KeyValueStore::Item entry : kvs_) {
     EXPECT_STREQ(entry.key(), "kEy");  // Make sure null-terminated.
 
-    char buffer[sizeof(value)] = {};
+    char buffer[sizeof("123")] = {};
     EXPECT_EQ(Status::OK, entry.Get(&buffer));
-    EXPECT_STREQ(value, buffer);
+    EXPECT_STREQ("123", buffer);
+  }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetWithOffset) {
+  ASSERT_EQ(Status::OK, kvs_.Put("key", as_bytes(span("not bad!"))));
+
+  for (KeyValueStore::Item entry : kvs_) {
+    char buffer[5];
+    auto result = entry.Get(as_writable_bytes(span(buffer)), 4);
+    EXPECT_EQ(Status::OK, result.status());
+    EXPECT_EQ(5u, result.size());
+    EXPECT_STREQ("bad!", buffer);
   }
 }
 
@@ -843,7 +893,7 @@
     // The value we read back should be the last value we set
     std::memset(get_buf, 0, sizeof(get_buf));
     result = kvs_.Get("const_entry", span(get_buf));
-    ASSERT_TRUE(result.ok());
+    ASSERT_EQ(Status::OK, result.status());
     ASSERT_EQ(result.size(), test_iteration);
     for (size_t j = 0; j < test_iteration; j++) {
       EXPECT_EQ(set_buf[j], get_buf[j]);
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 72dd1a4..5a43bca 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -100,7 +100,9 @@
 
   bool initialized() const { return initialized_; }
 
-  StatusWithSize Get(std::string_view key, span<std::byte> value) const;
+  StatusWithSize Get(std::string_view key,
+                     span<std::byte> value,
+                     size_t offset_bytes = 0) const;
 
   // This overload of Get accepts a pointer to a trivially copyable object.
   // const T& is used instead of T* to prevent arrays from satisfying this
@@ -155,8 +157,9 @@
     // The key as a null-terminated string.
     const char* key() const { return key_buffer_.data(); }
 
-    StatusWithSize Get(span<std::byte> value_buffer) const {
-      return kvs_.Get(key(), value_buffer);
+    StatusWithSize Get(span<std::byte> value_buffer,
+                       size_t offset_bytes = 0) const {
+      return kvs_.Get(key(), value_buffer, offset_bytes);
     }
 
     template <typename Pointer,
diff --git a/pw_kvs/pw_kvs_private/entry.h b/pw_kvs/pw_kvs_private/entry.h
index 3741ea1..f42df7c 100644
--- a/pw_kvs/pw_kvs_private/entry.h
+++ b/pw_kvs/pw_kvs_private/entry.h
@@ -135,7 +135,8 @@
         ReadKey(partition(), address_, key_length(), key.data()), key_length());
   }
 
-  StatusWithSize ReadValue(span<std::byte> value) const;
+  StatusWithSize ReadValue(span<std::byte> buffer,
+                           size_t offset_bytes = 0) const;
 
   Status VerifyChecksum(ChecksumAlgorithm* algorithm,
                         std::string_view key,