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, ¤t_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