pw_blob_store: Add blob open and read

- Add support to open blobs saved to flash
- Add support to stream read blobs

Change-Id: I97c587df34c98b6be03dba582837cd637e8c6448
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/18300
Commit-Queue: David Rogers <davidrogers@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_blob_store/blob_store_chunk_write_test.cc b/pw_blob_store/blob_store_chunk_write_test.cc
new file mode 100644
index 0000000..42dac62
--- /dev/null
+++ b/pw_blob_store/blob_store_chunk_write_test.cc
@@ -0,0 +1,171 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <array>
+#include <cstddef>
+#include <cstring>
+#include <span>
+
+#include "gtest/gtest.h"
+#include "pw_blob_store/blob_store.h"
+#include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/fake_flash_memory.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/test_key_value_store.h"
+#include "pw_log/log.h"
+#include "pw_random/xor_shift.h"
+
+namespace pw::blob_store {
+namespace {
+
+class BlobStoreChunkTest : public ::testing::Test {
+ protected:
+  BlobStoreChunkTest() : flash_(kFlashAlignment), partition_(&flash_) {}
+
+  void InitFlashTo(std::span<const std::byte> contents) {
+    partition_.Erase();
+    std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
+  }
+
+  void InitSourceBufferToRandom(uint64_t seed) {
+    partition_.Erase();
+    random::XorShiftStarRng64 rng(seed);
+    rng.Get(source_buffer_);
+  }
+
+  void InitSourceBufferToFill(char fill) {
+    partition_.Erase();
+    std::memset(source_buffer_.data(), fill, source_buffer_.size());
+  }
+
+  // Fill the source buffer with random pattern based on given seed, written to
+  // BlobStore in specified chunk size.
+  void ChunkWriteTest(size_t chunk_size) {
+    constexpr size_t kBufferSize = 256;
+    kvs::ChecksumCrc16 checksum;
+
+    char name[16] = {};
+    snprintf(name, sizeof(name), "Blob%u", static_cast<unsigned>(chunk_size));
+
+    BlobStoreBuffer<kBufferSize> blob(
+        name, &partition_, &checksum, kvs::TestKvs());
+    EXPECT_EQ(Status::OK, blob.Init());
+
+    BlobStore::BlobWriter writer(blob);
+    EXPECT_EQ(Status::OK, writer.Open());
+    EXPECT_EQ(Status::OK, writer.Erase());
+
+    ByteSpan source = source_buffer_;
+    while (source.size_bytes() > 0) {
+      const size_t write_size = std::min(source.size_bytes(), chunk_size);
+
+      PW_LOG_DEBUG("Do write of %u bytes, %u bytes remain",
+                   static_cast<unsigned>(write_size),
+                   static_cast<unsigned>(source.size_bytes()));
+
+      ASSERT_EQ(Status::OK, writer.Write(source.first(write_size)));
+
+      source = source.subspan(write_size);
+    }
+
+    EXPECT_EQ(Status::OK, writer.Close());
+
+    // Use reader to check for valid data.
+    BlobStore::BlobReader reader(blob);
+    ASSERT_EQ(Status::OK, reader.Open());
+    Result<ByteSpan> result = reader.GetMemoryMappedBlob();
+    ASSERT_TRUE(result.ok());
+    VerifyFlash(result.value());
+    EXPECT_EQ(Status::OK, reader.Close());
+  }
+
+  void VerifyFlash(ByteSpan verify_bytes) {
+    // Should be defined as same size.
+    EXPECT_EQ(source_buffer_.size(), flash_.buffer().size_bytes());
+
+    // Can't allow it to march off the end of source_buffer_.
+    ASSERT_LE(verify_bytes.size_bytes(), source_buffer_.size());
+
+    for (size_t i = 0; i < verify_bytes.size_bytes(); i++) {
+      EXPECT_EQ(source_buffer_[i], verify_bytes[i]);
+    }
+  }
+
+  static constexpr size_t kFlashAlignment = 16;
+  static constexpr size_t kSectorSize = 2048;
+  static constexpr size_t kSectorCount = 2;
+  static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
+
+  kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
+  kvs::FlashPartition partition_;
+  std::array<std::byte, kBlobDataSize> source_buffer_;
+};
+
+TEST_F(BlobStoreChunkTest, ChunkWrite1) {
+  InitSourceBufferToRandom(0x8675309);
+  ChunkWriteTest(1);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite2) {
+  InitSourceBufferToRandom(0x8675);
+  ChunkWriteTest(2);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite3) {
+  InitSourceBufferToFill(0);
+  ChunkWriteTest(3);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite4) {
+  InitSourceBufferToFill(1);
+  ChunkWriteTest(4);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite5) {
+  InitSourceBufferToFill(0xff);
+  ChunkWriteTest(5);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite16) {
+  InitSourceBufferToRandom(0x86);
+  ChunkWriteTest(16);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite64) {
+  InitSourceBufferToRandom(0x9);
+  ChunkWriteTest(64);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite256) {
+  InitSourceBufferToRandom(0x12345678);
+  ChunkWriteTest(256);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite512) {
+  InitSourceBufferToRandom(0x42);
+  ChunkWriteTest(512);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWrite4096) {
+  InitSourceBufferToRandom(0x89);
+  ChunkWriteTest(4096);
+}
+
+TEST_F(BlobStoreChunkTest, ChunkWriteSingleFull) {
+  InitSourceBufferToRandom(0x98765);
+  ChunkWriteTest(kBlobDataSize);
+}
+
+}  // namespace
+}  // namespace pw::blob_store