pw_blob_store: Add filename support

Allows BlobStore "blobs" to be created with a runtime-generated "file
name." This allows groups of blob stores to be used to store uniquely
identifiable data without explicitly requring a 1:1 blob store to
content type mapping.

Requires: pigweed-internal:15401
Change-Id: I1b2fb55b1d823befd1d968fe189f5470e1e487dd
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/59060
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Armando Montanez <amontanez@google.com>
diff --git a/pw_blob_store/BUILD.bazel b/pw_blob_store/BUILD.bazel
index c8b5894..5b8046f 100644
--- a/pw_blob_store/BUILD.bazel
+++ b/pw_blob_store/BUILD.bazel
@@ -27,13 +27,16 @@
     srcs = ["blob_store.cc"],
     hdrs = [
         "public/pw_blob_store/blob_store.h",
+        "public/pw_blob_store/internal/metadata_format.h",
     ],
     includes = ["public"],
     deps = [
+        "//pw_bytes",
         "//pw_checksum",
         "//pw_containers",
         "//pw_kvs",
         "//pw_log",
+        "//pw_preprocessor",
         "//pw_span",
         "//pw_status",
         "//pw_stream",
diff --git a/pw_blob_store/BUILD.gn b/pw_blob_store/BUILD.gn
index 236c8d1..a32a745 100644
--- a/pw_blob_store/BUILD.gn
+++ b/pw_blob_store/BUILD.gn
@@ -19,16 +19,22 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("default_config") {
+config("public_include_path") {
   include_dirs = [ "public" ]
+  visibility = [ ":*" ]
 }
 
 pw_source_set("pw_blob_store") {
-  public_configs = [ ":default_config" ]
-  public = [ "public/pw_blob_store/blob_store.h" ]
+  public_configs = [ ":public_include_path" ]
+  public = [
+    "public/pw_blob_store/blob_store.h",
+    "public/pw_blob_store/internal/metadata_format.h",
+  ]
   sources = [ "blob_store.cc" ]
   public_deps = [
+    dir_pw_bytes,
     dir_pw_kvs,
+    dir_pw_preprocessor,
     dir_pw_status,
     dir_pw_stream,
   ]
diff --git a/pw_blob_store/CMakeLists.txt b/pw_blob_store/CMakeLists.txt
index 8041124..f6a72b4 100644
--- a/pw_blob_store/CMakeLists.txt
+++ b/pw_blob_store/CMakeLists.txt
@@ -16,6 +16,7 @@
 
 pw_auto_add_simple_module(pw_blob_store
   PUBLIC_DEPS
+    pw_bytes
     pw_containers
     pw_kvs
     pw_span
diff --git a/pw_blob_store/blob_store.cc b/pw_blob_store/blob_store.cc
index ab7762c..7f2e7fc 100644
--- a/pw_blob_store/blob_store.cc
+++ b/pw_blob_store/blob_store.cc
@@ -17,11 +17,23 @@
 #include <algorithm>
 
 #include "pw_assert/check.h"
+#include "pw_blob_store/internal/metadata_format.h"
+#include "pw_bytes/byte_builder.h"
+#include "pw_bytes/span.h"
+#include "pw_kvs/checksum.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
 #include "pw_status/try.h"
+#include "pw_stream/stream.h"
 
 namespace pw::blob_store {
 
+using internal::BlobMetadataHeader;
+using internal::ChecksumValue;
+
 Status BlobStore::Init() {
   if (initialized_) {
     return OkStatus();
@@ -39,10 +51,6 @@
   initialized_ = true;
 
   if (LoadMetadata().ok()) {
-    valid_data_ = true;
-    write_address_ = metadata_.data_size_bytes;
-    flash_address_ = metadata_.data_size_bytes;
-
     PW_LOG_DEBUG("BlobStore init - Have valid blob of %u bytes",
                  static_cast<unsigned>(write_address_));
     return OkStatus();
@@ -64,18 +72,34 @@
 }
 
 Status BlobStore::LoadMetadata() {
-  if (!kvs_.Get(MetadataKey(), &metadata_).ok()) {
-    // If no metadata was read, make sure the metadata is reset.
-    metadata_.reset();
+  write_address_ = 0;
+  flash_address_ = 0;
+  file_name_length_ = 0;
+  valid_data_ = false;
+
+  BlobMetadataHeader metadata;
+  metadata.reset();
+
+  // For kVersion1 metadata versions, only the first member of
+  // BlobMetadataHeaderV2 will be populated.
+  if (!kvs_.Get(MetadataKey(), std::as_writable_bytes(std::span(&metadata, 1)))
+           .ok()) {
     return Status::NotFound();
   }
 
-  if (!ValidateChecksum().ok()) {
+  if (!ValidateChecksum(metadata.v1_metadata.data_size_bytes,
+                        metadata.v1_metadata.checksum)
+           .ok()) {
     PW_LOG_ERROR("BlobStore init - Invalidating blob with invalid checksum");
     Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
     return Status::DataLoss();
   }
 
+  write_address_ = metadata.v1_metadata.data_size_bytes;
+  flash_address_ = metadata.v1_metadata.data_size_bytes;
+  file_name_length_ = metadata.file_name_length;
+  valid_data_ = true;
+
   return OkStatus();
 }
 
@@ -96,11 +120,38 @@
 
   writer_open_ = true;
 
+  // Clear any existing contents.
   Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
 
   return OkStatus();
 }
 
+StatusWithSize BlobStore::GetFileName(std::span<char> dest) const {
+  if (!initialized_) {
+    return StatusWithSize(Status::FailedPrecondition(), 0);
+  }
+
+  if (file_name_length_ == 0) {
+    return StatusWithSize(Status::NotFound(), 0);
+  }
+
+  const size_t bytes_to_read =
+      std::min(dest.size_bytes(), static_cast<size_t>(file_name_length_));
+
+  Status status = bytes_to_read == file_name_length_
+                      ? OkStatus()
+                      : Status::ResourceExhausted();
+
+  // Read file name from KVS.
+  constexpr size_t kFileNameOffset = sizeof(BlobMetadataHeader);
+  const StatusWithSize kvs_read_sws =
+      kvs_.Get(MetadataKey(),
+               std::as_writable_bytes(dest.first(bytes_to_read)),
+               kFileNameOffset);
+  status.Update(kvs_read_sws.status());
+  return StatusWithSize(status, kvs_read_sws.size());
+}
+
 Status BlobStore::OpenRead() {
   if (!initialized_) {
     return Status::FailedPrecondition();
@@ -122,67 +173,6 @@
   return OkStatus();
 }
 
-Status BlobStore::CloseWrite() {
-  auto do_close_write = [&]() -> Status {
-    // If not valid to write, there was data loss and the close will result in a
-    // not valid blob. Don't need to flush any write buffered bytes.
-    if (!ValidToWrite()) {
-      return Status::DataLoss();
-    }
-
-    if (write_address_ == 0) {
-      return OkStatus();
-    }
-
-    PW_LOG_DEBUG(
-        "Blob writer close of %u byte blob, with %u bytes still in write "
-        "buffer",
-        static_cast<unsigned>(write_address_),
-        static_cast<unsigned>(WriteBufferBytesUsed()));
-
-    // Do a Flush of any flash_write_size_bytes_ sized chunks so any remaining
-    // bytes in the write buffer are less than flash_write_size_bytes_.
-    PW_TRY(Flush());
-
-    // If any bytes remain in buffer it is because it is a chunk less than
-    // flash_write_size_bytes_. Pad the chunk to flash_write_size_bytes_ and
-    // write it to flash.
-    if (!WriteBufferEmpty()) {
-      PW_TRY(FlushFinalPartialChunk());
-    }
-    PW_DCHECK(WriteBufferEmpty());
-
-    // If things are still good, save the blob metadata.
-    metadata_ = {.checksum = 0, .data_size_bytes = flash_address_};
-    if (checksum_algo_ != nullptr) {
-      ConstByteSpan checksum = checksum_algo_->Finish();
-      std::memcpy(&metadata_.checksum,
-                  checksum.data(),
-                  std::min(checksum.size(), sizeof(metadata_.checksum)));
-    }
-
-    if (!ValidateChecksum().ok()) {
-      Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      return Status::DataLoss();
-    }
-
-    if (!kvs_.Put(MetadataKey(), metadata_).ok()) {
-      return Status::DataLoss();
-    }
-
-    return OkStatus();
-  };
-
-  const Status status = do_close_write();
-  writer_open_ = false;
-
-  if (!status.ok()) {
-    valid_data_ = false;
-    return Status::DataLoss();
-  }
-  return OkStatus();
-}
-
 Status BlobStore::CloseRead() {
   PW_CHECK_UINT_GT(readers_open_, 0);
   readers_open_--;
@@ -443,7 +433,10 @@
     return OkStatus();
   }
 
-  Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  // If any writes have been performed, reset the state.
+  if (flash_address_ != 0) {
+    Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  }
 
   Status status = partition_.Erase();
 
@@ -458,28 +451,28 @@
 }
 
 Status BlobStore::Invalidate() {
-  metadata_.reset();
-
   // Blob data is considered if the flash is erased. Even though
   // there are 0 bytes written, they are valid.
   valid_data_ = flash_erased_;
   ResetChecksum();
   write_address_ = 0;
   flash_address_ = 0;
+  file_name_length_ = 0;
 
   Status status = kvs_.Delete(MetadataKey());
 
   return (status.ok() || status.IsNotFound()) ? OkStatus() : Status::Internal();
 }
 
-Status BlobStore::ValidateChecksum() {
-  if (metadata_.data_size_bytes == 0) {
+Status BlobStore::ValidateChecksum(size_t blob_size_bytes,
+                                   ChecksumValue expected) {
+  if (blob_size_bytes == 0) {
     PW_LOG_INFO("Blob unable to validate checksum of an empty blob");
     return Status::Unavailable();
   }
 
   if (checksum_algo_ == nullptr) {
-    if (metadata_.checksum != 0) {
+    if (expected != 0) {
       PW_LOG_ERROR(
           "Blob invalid to have a checkum value with no checksum algo");
       return Status::DataLoss();
@@ -489,12 +482,11 @@
   }
 
   PW_LOG_DEBUG("Validate checksum of 0x%08x in flash for blob of %u bytes",
-               static_cast<unsigned>(metadata_.checksum),
-               static_cast<unsigned>(metadata_.data_size_bytes));
-  PW_TRY(CalculateChecksumFromFlash(metadata_.data_size_bytes));
+               static_cast<unsigned>(expected),
+               static_cast<unsigned>(blob_size_bytes));
+  PW_TRY(CalculateChecksumFromFlash(blob_size_bytes));
 
-  Status status =
-      checksum_algo_->Verify(as_bytes(std::span(&metadata_.checksum, 1)));
+  Status status = checksum_algo_->Verify(as_bytes(std::span(&expected, 1)));
   PW_LOG_DEBUG("  checksum verify of %s", status.str());
 
   return status;
@@ -526,4 +518,146 @@
   return OkStatus();
 }
 
+Status BlobStore::BlobWriter::SetFileName(std::string_view file_name) {
+  PW_DCHECK(open_);
+  PW_DCHECK_NOTNULL(file_name.data());
+  PW_DCHECK(store_.writer_open_);
+
+  if (file_name.length() > MaxFileNameLength()) {
+    return Status::ResourceExhausted();
+  }
+
+  // Stage the file name to the encode buffer, just past the BlobMetadataHeader
+  // struct.
+  constexpr size_t kFileNameOffset = sizeof(BlobMetadataHeader);
+  const ByteSpan file_name_dest = metadata_buffer_.subspan(kFileNameOffset);
+  std::memcpy(file_name_dest.data(), file_name.data(), file_name.length());
+
+  store_.file_name_length_ = file_name.length();
+  return OkStatus();
+}
+
+Status BlobStore::BlobWriter::Open() {
+  PW_DCHECK(!open_);
+  PW_DCHECK_UINT_GE(metadata_buffer_.size_bytes(),
+                    sizeof(internal::BlobMetadataHeader));
+
+  const Status status = store_.OpenWrite();
+  if (status.ok()) {
+    open_ = true;
+  }
+  return status;
+}
+
+// Validates and commits BlobStore metadata to KVS.
+//
+// 1. Finalize checksum calculation.
+// 2. Check the calculated checksum against data actually committed to flash.
+// 3. Build the metadata header into the metadata buffer, placing it before the
+//    staged file name (if any).
+// 4. Commit the metadata to KVS.
+Status BlobStore::BlobWriter::WriteMetadata() {
+  // Finalize the in-progress checksum, if any.
+  ChecksumValue calculated_checksum = 0;
+  if (store_.checksum_algo_ != nullptr) {
+    ConstByteSpan checksum = store_.checksum_algo_->Finish();
+    std::memcpy(&calculated_checksum,
+                checksum.data(),
+                std::min(checksum.size(), sizeof(ChecksumValue)));
+  }
+
+  // Check the in-memory checksum against the data that was actually committed
+  // to flash.
+  if (!store_.ValidateChecksum(store_.flash_address_, calculated_checksum)
+           .ok()) {
+    PW_CHECK_OK(store_.Invalidate());
+    return Status::DataLoss();
+  }
+
+  // Encode the metadata header. This follows the latest struct behind
+  // BlobMetadataHeader. Currently, the order is as follows:
+  // - Encode checksum.
+  // - Encode stored data size.
+  // - Encode version magic.
+  // - Encode file name size.
+  // - File name, if present, is already staged at the end.
+  //
+  // Open() guarantees the metadata buffer is large enough to fit the metadata
+  // header.
+  ByteBuilder metadata_builder(metadata_buffer_);
+  metadata_builder.PutUint32(calculated_checksum);
+  metadata_builder.PutUint32(store_.flash_address_);
+  metadata_builder.PutUint32(internal::MetadataVersion::kLatest);
+  metadata_builder.PutUint8(store_.file_name_length_);
+  PW_DCHECK_INT_EQ(metadata_builder.size(), sizeof(BlobMetadataHeader));
+  PW_DCHECK_OK(metadata_builder.status());
+
+  // If a filename was provided, it is already written to the correct location
+  // in the buffer. When the file name was set, the metadata buffer was verified
+  // to fit the requested name in addition to the metadata header. If it doesn't
+  // fit now, something's very wrong.
+  const size_t bytes_to_write =
+      metadata_builder.size() + store_.file_name_length_;
+  PW_DCHECK(metadata_buffer_.size_bytes() >= bytes_to_write);
+
+  // Do final commit to KVS.
+  return store_.kvs_.Put(store_.MetadataKey(),
+                         metadata_buffer_.first(bytes_to_write));
+}
+
+Status BlobStore::BlobWriter::Close() {
+  PW_DCHECK(open_);
+  open_ = false;
+
+  // This is a lambda so the BlobWriter will be unconditionally closed even if
+  // the final flash commits fail. This lambda may early return to Close() if
+  // errors are encountered, but Close() will not return without updating both
+  // the BlobWriter and BlobStore such that neither are open for writes
+  // anymore.
+  auto do_close_write = [&]() -> Status {
+    // If not valid to write, there was data loss and the close will result in a
+    // not valid blob. Don't need to flush any write buffered bytes.
+    if (!store_.ValidToWrite()) {
+      return Status::DataLoss();
+    }
+
+    if (store_.write_address_ == 0) {
+      return OkStatus();
+    }
+
+    PW_LOG_DEBUG(
+        "Blob writer close of %u byte blob, with %u bytes still in write "
+        "buffer",
+        static_cast<unsigned>(store_.write_address_),
+        static_cast<unsigned>(store_.WriteBufferBytesUsed()));
+
+    // Do a Flush of any flash_write_size_bytes_ sized chunks so any remaining
+    // bytes in the write buffer are less than flash_write_size_bytes_.
+    PW_TRY(store_.Flush());
+
+    // If any bytes remain in buffer it is because it is a chunk less than
+    // flash_write_size_bytes_. Pad the chunk to flash_write_size_bytes_ and
+    // write it to flash.
+    if (!store_.WriteBufferEmpty()) {
+      PW_TRY(store_.FlushFinalPartialChunk());
+    }
+    PW_DCHECK(store_.WriteBufferEmpty());
+
+    if (!WriteMetadata().ok()) {
+      return Status::DataLoss();
+    }
+
+    return OkStatus();
+  };
+
+  const Status status = do_close_write();
+  store_.writer_open_ = false;
+
+  if (!status.ok()) {
+    store_.valid_data_ = false;
+    return Status::DataLoss();
+  }
+  return OkStatus();
+}
+
 }  // namespace pw::blob_store
diff --git a/pw_blob_store/blob_store_chunk_write_test.cc b/pw_blob_store/blob_store_chunk_write_test.cc
index becb23d..b789c8d 100644
--- a/pw_blob_store/blob_store_chunk_write_test.cc
+++ b/pw_blob_store/blob_store_chunk_write_test.cc
@@ -66,7 +66,7 @@
         name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
-    BlobStore::BlobWriter writer(blob);
+    BlobStore::BlobWriter writer(blob, metadata_buffer_);
     EXPECT_EQ(OkStatus(), writer.Open());
     EXPECT_EQ(OkStatus(), writer.Erase());
 
@@ -110,9 +110,12 @@
   static constexpr size_t kSectorSize = 2048;
   static constexpr size_t kSectorCount = 2;
   static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
+  static constexpr size_t kMetadataBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
 
   kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
   kvs::FlashPartition partition_;
+  std::array<std::byte, kMetadataBufferSize> metadata_buffer_;
   std::array<std::byte, kBlobDataSize> source_buffer_;
 };
 
diff --git a/pw_blob_store/blob_store_deferred_write_test.cc b/pw_blob_store/blob_store_deferred_write_test.cc
index 4edd993..161ea9f 100644
--- a/pw_blob_store/blob_store_deferred_write_test.cc
+++ b/pw_blob_store/blob_store_deferred_write_test.cc
@@ -67,7 +67,7 @@
         name, partition_, &checksum, kvs::TestKvs(), kWriteSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
-    BlobStore::DeferredWriter writer(blob);
+    BlobStore::DeferredWriter writer(blob, metadata_buffer_);
     EXPECT_EQ(OkStatus(), writer.Open());
 
     ByteSpan source = buffer_;
@@ -117,9 +117,12 @@
   static constexpr size_t kSectorSize = 1024;
   static constexpr size_t kSectorCount = 4;
   static constexpr size_t kBufferSize = 2 * kSectorSize;
+  static constexpr size_t kMetadataBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
 
   kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
   kvs::FlashPartition partition_;
+  std::array<std::byte, kMetadataBufferSize> metadata_buffer_;
   std::array<std::byte, kSectorCount * kSectorSize> buffer_;
 };
 
diff --git a/pw_blob_store/blob_store_test.cc b/pw_blob_store/blob_store_test.cc
index c6073ca..6c0b86e 100644
--- a/pw_blob_store/blob_store_test.cc
+++ b/pw_blob_store/blob_store_test.cc
@@ -32,6 +32,8 @@
 
 class BlobStoreTest : public ::testing::Test {
  protected:
+  static constexpr char kBlobTitle[] = "TestBlobBlock";
+
   BlobStoreTest() : flash_(kFlashAlignment), partition_(&flash_) {}
 
   void InitFlashTo(std::span<const std::byte> contents) {
@@ -71,14 +73,11 @@
     ConstByteSpan write_data =
         std::span(source_buffer_).first(write_size_bytes);
 
-    char name[16] = {};
-    snprintf(name, sizeof(name), "TestBlobBlock");
-
     BlobStoreBuffer<kBufferSize> blob(
-        name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+        kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
-    BlobStore::BlobWriter writer(blob);
+    BlobStore::BlobWriter writer(blob, metadata_buffer_);
     EXPECT_EQ(OkStatus(), writer.Open());
     ASSERT_EQ(OkStatus(), writer.Write(write_data));
     EXPECT_EQ(OkStatus(), writer.Close());
@@ -100,10 +99,9 @@
 
     VerifyFlash(flash_.buffer());
 
-    char name[16] = "TestBlobBlock";
     constexpr size_t kBufferSize = 16;
     BlobStoreBuffer<kBufferSize> blob(
-        name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+        kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
     // Use reader to check for valid data.
@@ -153,9 +151,12 @@
   static constexpr size_t kSectorSize = 2048;
   static constexpr size_t kSectorCount = 2;
   static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
+  static constexpr size_t kMetadataBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
 
   kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
   kvs::FlashPartition partition_;
+  std::array<std::byte, kMetadataBufferSize> metadata_buffer_;
   std::array<std::byte, kBlobDataSize> source_buffer_;
 };
 
@@ -174,13 +175,13 @@
       "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
   ASSERT_EQ(OkStatus(), blob.Init());
 
-  BlobStore::BlobWriter writer(blob);
+  BlobStore::BlobWriter writer(blob, metadata_buffer_);
   ASSERT_EQ(OkStatus(), writer.Open());
   EXPECT_EQ(writer.ConservativeReadLimit(), 0u);
   EXPECT_EQ(writer.ConservativeWriteLimit(), kSectorSize * kSectorCount);
   ASSERT_EQ(OkStatus(), writer.Close());
 
-  BlobStore::DeferredWriter deferred_writer(blob);
+  BlobStore::DeferredWriter deferred_writer(blob, metadata_buffer_);
   ASSERT_EQ(OkStatus(), deferred_writer.Open());
   EXPECT_EQ(deferred_writer.ConservativeReadLimit(), 0u);
   EXPECT_EQ(deferred_writer.ConservativeWriteLimit(), kBufferSize);
@@ -208,14 +209,14 @@
       "Blob_open", partition_, nullptr, kvs::TestKvs(), kBufferSize);
   EXPECT_EQ(OkStatus(), blob.Init());
 
-  BlobStore::DeferredWriter deferred_writer(blob);
+  BlobStore::DeferredWriter deferred_writer(blob, metadata_buffer_);
   EXPECT_EQ(false, deferred_writer.IsOpen());
   EXPECT_EQ(OkStatus(), deferred_writer.Open());
   EXPECT_EQ(true, deferred_writer.IsOpen());
   EXPECT_EQ(OkStatus(), deferred_writer.Close());
   EXPECT_EQ(false, deferred_writer.IsOpen());
 
-  BlobStore::BlobWriter writer(blob);
+  BlobStore::BlobWriter writer(blob, metadata_buffer_);
   EXPECT_EQ(false, writer.IsOpen());
   EXPECT_EQ(OkStatus(), writer.Open());
   EXPECT_EQ(true, writer.IsOpen());
@@ -234,6 +235,182 @@
   EXPECT_EQ(false, reader.IsOpen());
 }
 
+TEST_F(BlobStoreTest, FileName) {
+  InitSourceBufferToRandom(0x8675309);
+  WriteTestBlock();
+  constexpr std::string_view kFileName("my_file_1.bin");
+  constexpr size_t kEncodeBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(kFileName.size());
+  std::array<std::byte, kEncodeBufferSize> metadata_buffer = {};
+  std::array<std::byte, 64> tmp_buffer = {};
+  static_assert(kFileName.size() <= tmp_buffer.size());
+
+  kvs::ChecksumCrc16 checksum;
+  constexpr size_t kBufferSize = 256;
+  BlobStoreBuffer<kBufferSize> blob(
+      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+  EXPECT_EQ(OkStatus(), blob.Init());
+
+  BlobStore::BlobWriter writer(blob, metadata_buffer);
+
+  EXPECT_EQ(OkStatus(), writer.Open());
+  EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
+  EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
+  EXPECT_EQ(OkStatus(), writer.Close());
+  EXPECT_EQ(OkStatus(), kvs::TestKvs().Get(kBlobTitle, tmp_buffer).status());
+
+  // Ensure the file name can be read from a reader.
+  BlobStore::BlobReader reader(blob);
+  ASSERT_EQ(OkStatus(), reader.Open());
+
+  memset(tmp_buffer.data(), 0, tmp_buffer.size());
+  StatusWithSize sws = reader.GetFileName(
+      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
+
+  EXPECT_EQ(OkStatus(), sws.status());
+  ASSERT_EQ(kFileName.size(), sws.size());
+  EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), kFileName.size()), 0);
+}
+
+TEST_F(BlobStoreTest, FileNameUndersizedRead) {
+  InitSourceBufferToRandom(0x8675309);
+  WriteTestBlock();
+  constexpr std::string_view kFileName("my_file_1.bin");
+  constexpr size_t kEncodeBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(kFileName.size());
+  std::array<std::byte, kEncodeBufferSize> metadata_buffer = {};
+  std::array<std::byte, 4> tmp_buffer = {};
+  static_assert(kFileName.size() > tmp_buffer.size());
+
+  kvs::ChecksumCrc16 checksum;
+  constexpr size_t kBufferSize = 256;
+  BlobStoreBuffer<kBufferSize> blob(
+      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+  EXPECT_EQ(OkStatus(), blob.Init());
+
+  BlobStore::BlobWriter writer(blob, metadata_buffer);
+
+  EXPECT_EQ(OkStatus(), writer.Open());
+  EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
+  EXPECT_EQ(OkStatus(),
+            writer.Write(std::as_bytes(std::span("some interesting data"))));
+  EXPECT_EQ(OkStatus(), writer.Close());
+
+  // Ensure the file name can be read from a reader.
+  BlobStore::BlobReader reader(blob);
+  ASSERT_EQ(OkStatus(), reader.Open());
+
+  StatusWithSize sws = reader.GetFileName(
+      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
+  EXPECT_EQ(Status::ResourceExhausted(), sws.status());
+  ASSERT_EQ(tmp_buffer.size(), sws.size());
+  EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), sws.size()), 0);
+}
+
+TEST_F(BlobStoreTest, FileNameUndersizedSet) {
+  InitSourceBufferToRandom(0x8675309);
+  WriteTestBlock();
+  constexpr std::string_view kFileName("my_file_1.bin");
+  constexpr size_t kEncodeBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(2);
+  std::array<std::byte, kEncodeBufferSize> metadata_buffer = {};
+
+  kvs::ChecksumCrc16 checksum;
+  constexpr size_t kBufferSize = 256;
+  BlobStoreBuffer<kBufferSize> blob(
+      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+  EXPECT_EQ(OkStatus(), blob.Init());
+
+  BlobStore::BlobWriter writer(blob, metadata_buffer);
+
+  EXPECT_EQ(OkStatus(), writer.Open());
+  EXPECT_EQ(Status::ResourceExhausted(), writer.SetFileName(kFileName));
+  EXPECT_EQ(OkStatus(), writer.Close());
+}
+
+TEST_F(BlobStoreTest, FileNameInvalidation) {
+  InitSourceBufferToRandom(0x8675309);
+  WriteTestBlock();
+
+  constexpr std::string_view kFileName("sliced_cheese.png");
+  constexpr size_t kEncodeBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(kFileName.size());
+  std::array<std::byte, kEncodeBufferSize> metadata_buffer = {};
+  std::array<std::byte, 64> tmp_buffer = {};
+  static_assert(kFileName.size() <= tmp_buffer.size());
+
+  kvs::ChecksumCrc16 checksum;
+  constexpr size_t kBufferSize = 256;
+  BlobStoreBuffer<kBufferSize> blob(
+      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+  EXPECT_EQ(OkStatus(), blob.Init());
+
+  BlobStore::BlobWriter writer(blob, metadata_buffer);
+
+  EXPECT_EQ(OkStatus(), writer.Open());
+  EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
+  EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
+  EXPECT_EQ(OkStatus(), writer.Discard());
+  EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
+  EXPECT_EQ(OkStatus(), writer.Close());
+
+  // Check that the file name was discarded by Discard().
+  memset(tmp_buffer.data(), 0, tmp_buffer.size());
+  BlobStore::BlobReader reader(blob);
+  ASSERT_EQ(OkStatus(), reader.Open());
+  StatusWithSize sws = reader.GetFileName(
+      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
+  EXPECT_EQ(Status::NotFound(), sws.status());
+  ASSERT_EQ(0u, sws.size());
+}
+
+TEST_F(BlobStoreTest, NoFileName) {
+  InitSourceBufferToRandom(0x8675309);
+  WriteTestBlock();
+
+  std::array<std::byte, 64> tmp_buffer = {};
+  kvs::ChecksumCrc16 checksum;
+  constexpr size_t kBufferSize = 256;
+  BlobStoreBuffer<kBufferSize> blob(
+      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+  EXPECT_EQ(OkStatus(), blob.Init());
+
+  // Ensure blobs with no file names work as expected.
+  BlobStore::BlobReader reader(blob);
+  ASSERT_EQ(OkStatus(), reader.Open());
+
+  StatusWithSize sws = reader.GetFileName(
+      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
+  EXPECT_EQ(Status::NotFound(), sws.status());
+  ASSERT_EQ(0u, sws.size());
+}
+
+TEST_F(BlobStoreTest, V1MetadataBackwardsCompatible) {
+  constexpr size_t kWriteSize = 25;
+  WriteTestBlock(kWriteSize);
+
+  kvs::ChecksumCrc16 checksum;
+  constexpr size_t kBufferSize = 16;
+  BlobStoreBuffer<kBufferSize> blob(
+      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+  EXPECT_EQ(OkStatus(), blob.Init());
+
+  // Read the written data in the current format.
+  internal::BlobMetadataHeader current_metadata;
+  ASSERT_EQ(OkStatus(), kvs::TestKvs().Get(kBlobTitle, &current_metadata));
+
+  // Re-save only the V1 metadata contents.
+  ASSERT_EQ(OkStatus(),
+            kvs::TestKvs().Put(kBlobTitle, current_metadata.v1_metadata));
+
+  // Ensure the BlobStore's contents aren't invalid.
+  BlobStore::BlobReader reader(blob);
+  ASSERT_EQ(OkStatus(), reader.Open());
+  ASSERT_EQ(kWriteSize, reader.ConservativeReadLimit());
+  ASSERT_EQ(current_metadata.v1_metadata.data_size_bytes,
+            reader.ConservativeReadLimit());
+}
+
 TEST_F(BlobStoreTest, Discard) {
   InitSourceBufferToRandom(0x8675309);
   WriteTestBlock();
@@ -250,7 +427,7 @@
       blob_title, partition_, &checksum, kvs::TestKvs(), kBufferSize);
   EXPECT_EQ(OkStatus(), blob.Init());
 
-  BlobStore::BlobWriter writer(blob);
+  BlobStore::BlobWriter writer(blob, metadata_buffer_);
 
   EXPECT_EQ(OkStatus(), writer.Open());
   EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
@@ -276,7 +453,7 @@
       "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
   EXPECT_EQ(OkStatus(), blob.Init());
 
-  BlobStore::BlobWriter writer(blob);
+  BlobStore::BlobWriter writer(blob, metadata_buffer_);
   EXPECT_EQ(OkStatus(), writer.Open());
 
   EXPECT_EQ(OkStatus(), writer.Erase());
diff --git a/pw_blob_store/docs.rst b/pw_blob_store/docs.rst
index 325c0b2..f83d4e7 100644
--- a/pw_blob_store/docs.rst
+++ b/pw_blob_store/docs.rst
@@ -1,25 +1,80 @@
 .. _module-pw_blob_store:
 
--------------
+=============
 pw_blob_store
--------------
+=============
 ``pw_blob_store`` is a storage container library for storing a single blob of
-data. Blob_store is a flash-backed persistent storage system with integrated
+data. ``BlobStore`` is a flash-backed persistent storage system with integrated
 data integrity checking that serves as a lightweight alternative to a file
 system.
 
-Write and read are only done using the BlobWriter and BlobReader classes.
+=====
+Usage
+=====
+Most operations on a ``BlobStore`` are done using ``BlobReader`` and
+``BlobWriter`` objects that have been constructed using a ``BlobStore``. Though
+a ``BlobStore`` may have multiple open ``BlobReader`` objects, no other
+readers/writers may be active if a ``BlobWriter`` is opened on a blob store.
 
-Once a blob write is closed, reopening for write followed by a Discard(), Write(), or
-Erase() will discard the previous blob.
+----------------------
+Writing to a BlobStore
+----------------------
+``BlobWriter`` objects are ``pw::stream::Writer`` compatible, but do not support
+reading any of the blob's contents. Opening a ``BlobWriter`` on a ``BlobStore``
+that contains data will discard any existing data if ``Discard()``, ``Write
+()``, or ``Erase()`` are called. There is currently no mechanism to allow
+appending to existing data.
 
-Write blob:
-  0) Create BlobWriter instance
-  1) BlobWriter::Open().
-  2) Add data using BlobWriter::Write().
-  3) BlobWriter::Close().
+.. code-block:: cpp
 
-Read blob:
+  BlobStore::BlobWriter writer(my_blob_store);
+  writer.Open();
+  writer.Write(my_data);
+
+  // ...
+
+  // A close is implied when a BlobWriter is destroyed. Manually closing a
+  // BlobWriter enables error handling on Close() failure.
+  writer.Close();
+
+Erasing a BlobStore
+===================
+There are two distinctly different mechanisms to "erase" the contents of a BlobStore:
+
+#. ``Discard()``: Discards any ongoing writes and ensures ``BlobReader`` objects
+   see the ``BlobStore`` as empty. This is the fastest way to logically erase a
+   ``BlobStore``.
+#. ``Erase()``: Performs an explicit flash erase of the ``BlobStore``'s
+   underlying partition. This is useful for manually controlling when a flash
+   erase is performed before a ``BlobWriter`` starts to write data (as flash
+   erase operations may be time-consuming).
+
+Naming a BlobStore's contents
+=============================
+Data in a ``BlobStore`` May be named similarly to a file. This enables
+identification of a BlobStore's contents in cases where different data may be
+stored to a shared blob store. This requires an additional RAM buffer that can
+be used to encode the BlobStore's KVS metadata entry. Calling
+``MaxFileNameLength()`` on a ``BlobWriter`` will provide the max file name
+length based on the ``BlobWriter``'s metadata encode buffer size.
+
+``SetFileName()`` performs a copy of the provided file name, meaning it's safe
+for the ``std::string_view`` to be invalidated after the function returns.
+
+.. code-block:: cpp
+
+  std::array<std::byte, 48> metadata_encode_buffer;
+  BlobStore::BlobWriter writer(my_blob_store, metadata_encode_buffer);
+  writer.Open();
+  writer.SetFileName("stonks.jpg");
+  writer.Write(my_data);
+  // ...
+  writer.Close();
+
+------------------------
+Reading from a BlobStore
+------------------------
+
   0) Create BlobReader instance
   1) BlobReader::Open().
   2) Read data using BlobReader::Read() or
@@ -27,8 +82,9 @@
      BlobReader::Seek() to read from a desired offset.
   3) BlobReader::Close().
 
+===========
 Size report
------------
+===========
 The following size report showcases the memory usage of the blob store.
 
 .. include:: blob_size
diff --git a/pw_blob_store/public/pw_blob_store/blob_store.h b/pw_blob_store/public/pw_blob_store/blob_store.h
index 19454a4..b3b8495 100644
--- a/pw_blob_store/public/pw_blob_store/blob_store.h
+++ b/pw_blob_store/public/pw_blob_store/blob_store.h
@@ -13,22 +13,25 @@
 // the License.
 #pragma once
 
+#include <cstddef>
 #include <span>
 
 #include "pw_assert/assert.h"
-#include "pw_assert/check.h"
+#include "pw_blob_store/internal/metadata_format.h"
+#include "pw_bytes/span.h"
 #include "pw_kvs/checksum.h"
 #include "pw_kvs/flash_memory.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
 #include "pw_status/try.h"
 #include "pw_stream/seek.h"
 #include "pw_stream/stream.h"
 
 namespace pw::blob_store {
 
-// BlobStore is a storage container for a single blob of data. BlobStore is a
-// FlashPartition-backed persistent storage system with integrated data
+// BlobStore is a storage container for a single blob of data. BlobStore is
+// a FlashPartition-backed persistent storage system with integrated data
 // integrity checking that serves as a lightweight alternative to a file
 // system.
 //
@@ -58,7 +61,8 @@
   // Additionally, writters are unable to open if a reader is already open.
   class BlobWriter : public stream::NonSeekableWriter {
    public:
-    constexpr BlobWriter(BlobStore& store) : store_(store), open_(false) {}
+    constexpr BlobWriter(BlobStore& store, ByteSpan metadata_buffer)
+        : store_(store), metadata_buffer_(metadata_buffer), open_(false) {}
     BlobWriter(const BlobWriter&) = delete;
     BlobWriter& operator=(const BlobWriter&) = delete;
     virtual ~BlobWriter() {
@@ -67,21 +71,25 @@
       }
     }
 
+    static constexpr size_t RequiredMetadataBufferSize(
+        size_t max_file_name_size) {
+      return max_file_name_size + sizeof(internal::BlobMetadataHeader);
+    }
+
     // Open a blob for writing/erasing. Open will invalidate any existing blob
-    // that may be stored. Can not open when already open. Only one writer is
-    // allowed to be open at a time. Returns:
+    // that may be stored, and will not retain the previous file name. Can not
+    // open when already open. Only one writer is allowed to be open at a time.
+    // Returns:
+    //
+    // Preconditions:
+    // This writer must not already be open.
+    // This writer's metadata encode buffer must be at least the size of
+    // internal::BlobMetadataHeader.
     //
     // OK - success.
     // UNAVAILABLE - Unable to open, another writer or reader instance is
     //     already open.
-    Status Open() {
-      PW_DASSERT(!open_);
-      Status status = store_.OpenWrite();
-      if (status.ok()) {
-        open_ = true;
-      }
-      return status;
-    }
+    Status Open();
 
     // Finalize a blob write. Flush all remaining buffered data to storage and
     // store blob metadata. Close fails in the closed state, do NOT retry Close
@@ -90,11 +98,7 @@
     //
     // OK - success.
     // DATA_LOSS - Error writing data or fail to verify written data.
-    Status Close() {
-      PW_DASSERT(open_);
-      open_ = false;
-      return store_.CloseWrite();
-    }
+    Status Close();
 
     bool IsOpen() { return open_; }
 
@@ -120,23 +124,54 @@
       return store_.Invalidate();
     }
 
+    // Sets file name to be associated with the data written by this
+    // ``BlobWriter``. This may be changed any time before Close() is called.
+    //
+    // Calling Discard() or Erase() will clear any set file name.
+    //
+    // The underlying buffer behind file_name may be invalidated after this
+    // function returns as the string is copied to the internally managed encode
+    // buffer.
+    //
+    // Preconditions:
+    // This writer must be open.
+    //
+    // OK - successfully set file name.
+    // RESOURCE_EXHAUSTED - File name too large to fit in metadata encode
+    //   buffer, file name not set.
+    Status SetFileName(std::string_view file_name);
+
     size_t CurrentSizeBytes() {
       PW_DASSERT(open_);
       return store_.write_address_;
     }
 
+    // Max file name length, not including null terminator (null terminators
+    // are not stored).
+    size_t MaxFileNameLength() {
+      return metadata_buffer_.size_bytes() <
+                     sizeof(internal::BlobMetadataHeader)
+                 ? 0
+                 : metadata_buffer_.size_bytes() -
+                       sizeof(internal::BlobMetadataHeader);
+    }
+
    protected:
     Status DoWrite(ConstByteSpan data) override {
       PW_DASSERT(open_);
       return store_.Write(data);
     }
 
+    // Commits changes to KVS as a BlobStore metadata entry.
+    Status WriteMetadata();
+
     BlobStore& store_;
+    ByteSpan metadata_buffer_;
     bool open_;
 
    private:
     // Probable (not guaranteed) minimum number of bytes at this time that can
-    // be written. This is not necessarily the full number of bytes remaining in
+    // be written. This is not necessarily the full number of bytes remaining in
     // the blob. Returns zero if, in the current state, Write would return
     // status other than OK. See stream.h for additional details.
     size_t ConservativeLimit(LimitType limit) const override {
@@ -155,7 +190,8 @@
   // Additionally, writters are unable to open if a reader is already open.
   class DeferredWriter final : public BlobWriter {
    public:
-    constexpr DeferredWriter(BlobStore& store) : BlobWriter(store) {}
+    constexpr DeferredWriter(BlobStore& store, ByteSpan metadata_buffer)
+        : BlobWriter(store, metadata_buffer) {}
     DeferredWriter(const DeferredWriter&) = delete;
     DeferredWriter& operator=(const DeferredWriter&) = delete;
     virtual ~DeferredWriter() {}
@@ -169,7 +205,7 @@
     }
 
     // Probable (not guaranteed) minimum number of bytes at this time that can
-    // be written. This is not necessarily the full number of bytes remaining in
+    // be written. This is not necessarily the full number of bytes remaining in
     // the blob. Returns zero if, in the current state, Write would return
     // status other than OK. See stream.h for additional details.
     size_t ConservativeLimit(LimitType limit) const override {
@@ -237,6 +273,20 @@
       return store_.CloseRead();
     }
 
+    // Copies the file name of the stored data to `dest`, and returns the number
+    // of bytes written to the destination buffer. The string is not
+    // null-terminated.
+    //
+    // Returns:
+    //   OK - File name copied, size contains file name length.
+    //   RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains
+    //     first N bytes of the file name.
+    //   NOT_FOUND - No file name set for this blob.
+    StatusWithSize GetFileName(std::span<char> dest) {
+      PW_DASSERT(open_);
+      return store_.GetFileName(dest);
+    }
+
     bool IsOpen() { return open_; }
 
     // Probable (not guaranteed) minimum number of bytes at this time that can
@@ -323,9 +373,9 @@
         flash_erased_(false),
         writer_open_(false),
         readers_open_(0),
-        metadata_({}),
         write_address_(0),
-        flash_address_(0) {}
+        flash_address_(0),
+        file_name_length_(0) {}
 
   BlobStore(const BlobStore&) = delete;
   BlobStore& operator=(const BlobStore&) = delete;
@@ -340,8 +390,6 @@
   size_t MaxDataSizeBytes() const;
 
  private:
-  typedef uint32_t ChecksumValue;
-
   Status LoadMetadata();
 
   // Open to do a blob write. Returns:
@@ -363,7 +411,6 @@
   // OK - success, valid complete blob.
   // DATA_LOSS - Error during write (this close or previous write/flush). Blob
   //     is closed and marked as invalid.
-  Status CloseWrite();
   Status CloseRead();
 
   // Write/append data to the in-progress blob write. Data is written
@@ -476,28 +523,24 @@
     }
   }
 
-  Status ValidateChecksum();
+  Status ValidateChecksum(size_t blob_size_bytes,
+                          internal::ChecksumValue expected);
 
   Status CalculateChecksumFromFlash(size_t bytes_to_check);
 
-  const std::string_view MetadataKey() { return name_; }
+  const std::string_view MetadataKey() const { return name_; }
 
-  // Changes to the metadata format should also get a different key signature to
-  // avoid new code improperly reading old format metadata.
-  struct BlobMetadata {
-    // The checksum of the blob data stored in flash.
-    ChecksumValue checksum;
-
-    // Number of blob data bytes stored in flash.
-    size_t data_size_bytes;
-
-    constexpr void reset() {
-      *this = {
-          .checksum = 0,
-          .data_size_bytes = 0,
-      };
-    }
-  };
+  // Copies the file name of the stored data to `dest`, and returns the number
+  // of bytes written to the destination buffer. The string is not
+  // null-terminated.
+  //
+  // Returns:
+  //   OK - File name copied, size contains file name length.
+  //   RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains
+  //     first N bytes of the file name.
+  //   NOT_FOUND - No file name set for this blob.
+  //   FAILED_PRECONDITION - BlobStore has not been initialized.
+  StatusWithSize GetFileName(std::span<char> dest) const;
 
   std::string_view name_;
   kvs::FlashPartition& partition_;
@@ -532,23 +575,23 @@
   // Count of open BlobReader instances
   size_t readers_open_;
 
-  // Metadata for the blob.
-  BlobMetadata metadata_;
-
-  // Current index for end of overal blob data. Represents current byte size of
+  // Current index for end of overall blob data. Represents current byte size of
   // blob data since the FlashPartition starts at address 0.
   kvs::FlashPartition::Address write_address_;
 
   // Current index of end of data written to flash. Number of buffered data
   // bytes is write_address_ - flash_address_.
   kvs::FlashPartition::Address flash_address_;
+
+  // Length of the stored blob's filename.
+  size_t file_name_length_;
 };
 
 // Creates a BlobStore with the buffer of kBufferSizeBytes.
 //
 // kBufferSizeBytes - Size in bytes of write buffer to create.
 // name - Name of blob store, used for metadata KVS key
-// partition - Flash partiton to use for this blob. Blob uses the entire
+// partition - Flash partition to use for this blob. Blob uses the entire
 //     partition for blob data.
 // checksum_algo - Optional checksum for blob integrity checking. Use nullptr
 //     for no check.
diff --git a/pw_blob_store/public/pw_blob_store/internal/metadata_format.h b/pw_blob_store/public/pw_blob_store/internal/metadata_format.h
new file mode 100644
index 0000000..2d7a293
--- /dev/null
+++ b/pw_blob_store/public/pw_blob_store/internal/metadata_format.h
@@ -0,0 +1,72 @@
+// Copyright 2021 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.
+#pragma once
+
+#include <cstddef>
+
+#include "pw_preprocessor/compiler.h"
+
+namespace pw::blob_store::internal {
+
+enum MetadataVersion : uint32_t {
+  // Original metadata format does not include a version.
+  kVersion1 = 0,
+  kVersion2 = 0x1197851D,
+  kLatest = kVersion2
+};
+
+// Technically the original BlobMetadataV1 was not packed.
+PW_PACKED(struct) BlobMetadataV1 {
+  typedef uint32_t ChecksumValue;
+
+  // The checksum of the blob data stored in flash.
+  ChecksumValue checksum;
+
+  // Number of blob data bytes stored in flash.
+  // Technically this was originally size_t, but backwards compatibility for
+  // platform-specific sized types has been dropped.
+  uint32_t data_size_bytes;
+};
+
+// Changes to the metadata format should also get a different key signature to
+// avoid new code improperly reading old format metadata.
+PW_PACKED(struct) BlobMetadataHeaderV2 {
+  BlobMetadataV1 v1_metadata;
+
+  // Metadata encoding version stored in flash.
+  MetadataVersion version;
+
+  // Length of the file name stored in the metadata entry.
+  uint8_t file_name_length;
+
+  // Following this struct is file_name_length chars of file name. Note that
+  // the string of characters is NOT null terminated.
+
+  constexpr void reset() {
+    *this = {
+        .v1_metadata =
+            {
+                .checksum = 0,
+                .data_size_bytes = 0,
+            },
+        .version = MetadataVersion::kLatest,
+        .file_name_length = 0,
+    };
+  }
+};
+
+using BlobMetadataHeader = BlobMetadataHeaderV2;
+using ChecksumValue = BlobMetadataV1::ChecksumValue;
+
+}  // namespace pw::blob_store::internal
diff --git a/pw_blob_store/size_report/basic_blob.cc b/pw_blob_store/size_report/basic_blob.cc
index 1921546..6c69d7e 100644
--- a/pw_blob_store/size_report/basic_blob.cc
+++ b/pw_blob_store/size_report/basic_blob.cc
@@ -21,6 +21,11 @@
 #include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
 
+using pw::blob_store::BlobStore;
+using pw::blob_store::BlobStoreBuffer;
+
+namespace {
+
 char working_buffer[256];
 volatile bool is_set;
 
@@ -39,6 +44,8 @@
 
 int volatile* unoptimizable;
 
+}  // namespace
+
 int main() {
   pw::bloat::BloatThisBinary();
 
@@ -82,19 +89,22 @@
   // Start of basic blob **********************
   constexpr size_t kBufferSize = 1;
 
-  pw::blob_store::BlobStoreBuffer<kBufferSize> blob(
+  BlobStoreBuffer<kBufferSize> blob(
       name, pw::kvs::FlashTestPartition(), nullptr, test_kvs, kBufferSize);
   blob.Init().IgnoreError();  // TODO(pwbug/387): Handle Status properly
 
   // Use writer.
-  pw::blob_store::BlobStore::BlobWriter writer(blob);
+  constexpr size_t kMetadataBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
+  std::array<std::byte, kMetadataBufferSize> metadata_buffer;
+  BlobStore::BlobWriter writer(blob, metadata_buffer);
   writer.Open().IgnoreError();  // TODO(pwbug/387): Handle Status properly
   writer.Write(write_data)
       .IgnoreError();            // TODO(pwbug/387): Handle Status properly
   writer.Close().IgnoreError();  // TODO(pwbug/387): Handle Status properly
 
   // Use reader.
-  pw::blob_store::BlobStore::BlobReader reader(blob);
+  BlobStore::BlobReader reader(blob);
   reader.Open().IgnoreError();  // TODO(pwbug/387): Handle Status properly
   pw::Result<pw::ConstByteSpan> get_result = reader.GetMemoryMappedBlob();
   PW_LOG_INFO("%d", get_result.ok());
diff --git a/pw_blob_store/size_report/deferred_write_blob.cc b/pw_blob_store/size_report/deferred_write_blob.cc
index e8f3507..3adfa24 100644
--- a/pw_blob_store/size_report/deferred_write_blob.cc
+++ b/pw_blob_store/size_report/deferred_write_blob.cc
@@ -21,6 +21,10 @@
 #include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
 
+using pw::blob_store::BlobStore;
+
+namespace {
+
 char working_buffer[256];
 volatile bool is_set;
 
@@ -39,6 +43,8 @@
 
 int volatile* unoptimizable;
 
+}  // namespace
+
 int main() {
   pw::bloat::BloatThisBinary();
 
@@ -87,7 +93,10 @@
   blob.Init().IgnoreError();  // TODO(pwbug/387): Handle Status properly
 
   // Use writer.
-  pw::blob_store::BlobStore::DeferredWriter writer(blob);
+  constexpr size_t kMetadataBufferSize =
+      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
+  std::array<std::byte, kMetadataBufferSize> metadata_buffer;
+  pw::blob_store::BlobStore::DeferredWriter writer(blob, metadata_buffer);
   writer.Open().IgnoreError();  // TODO(pwbug/387): Handle Status properly
   writer.Write(write_data)
       .IgnoreError();            // TODO(pwbug/387): Handle Status properly