• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <cstddef>
17 #include <span>
18 
19 #include "pw_assert/assert.h"
20 #include "pw_blob_store/internal/metadata_format.h"
21 #include "pw_bytes/span.h"
22 #include "pw_kvs/checksum.h"
23 #include "pw_kvs/flash_memory.h"
24 #include "pw_kvs/key_value_store.h"
25 #include "pw_status/status.h"
26 #include "pw_status/status_with_size.h"
27 #include "pw_status/try.h"
28 #include "pw_stream/seek.h"
29 #include "pw_stream/stream.h"
30 #include "pw_sync/borrow.h"
31 
32 namespace pw::blob_store {
33 
34 // BlobStore is a storage container for a single blob of data. BlobStore is
35 // a FlashPartition-backed persistent storage system with integrated data
36 // integrity checking that serves as a lightweight alternative to a file
37 // system.
38 //
39 // Write and read are only done using the BlobWriter and BlobReader classes.
40 //
41 // Once a blob write is closed, reopening to write will discard the previous
42 // blob.
43 //
44 // Write blob:
45 //  0) Create BlobWriter instance
46 //  1) BlobWriter::Open().
47 //  2) Add data using BlobWriter::Write().
48 //  3) BlobWriter::Close().
49 //
50 // Read blob:
51 //  0) Create BlobReader instance
52 //  1) BlobReader::Open().
53 //  2) Read data using BlobReader::Read() or
54 //     BlobReader::GetMemoryMappedBlob().
55 //  3) BlobReader::Close().
56 class BlobStore {
57  public:
58   // Implement the stream::Writer and erase interface for a BlobStore. If not
59   // already erased, the Write will do any needed erase.
60   //
61   // Only one writter (of either type) is allowed to be open at a time.
62   // Additionally, writers are unable to open if a reader is already open.
63   class BlobWriter : public stream::NonSeekableWriter {
64    public:
BlobWriter(BlobStore & store,ByteSpan metadata_buffer)65     constexpr BlobWriter(BlobStore& store, ByteSpan metadata_buffer)
66         : store_(store), metadata_buffer_(metadata_buffer), open_(false) {}
67     BlobWriter(const BlobWriter&) = delete;
68     BlobWriter& operator=(const BlobWriter&) = delete;
~BlobWriter()69     virtual ~BlobWriter() {
70       if (open_) {
71         Close().IgnoreError();  // TODO(pwbug/387): Handle Status properly
72       }
73     }
74 
RequiredMetadataBufferSize(size_t max_file_name_size)75     static constexpr size_t RequiredMetadataBufferSize(
76         size_t max_file_name_size) {
77       return max_file_name_size + sizeof(internal::BlobMetadataHeader);
78     }
79 
80     // Open a blob for writing/erasing. Open will invalidate any existing blob
81     // that may be stored, and will not retain the previous file name. Can not
82     // open when already open. Only one writer is allowed to be open at a time.
83     // Returns:
84     //
85     // Preconditions:
86     // This writer must not already be open.
87     // This writer's metadata encode buffer must be at least the size of
88     // internal::BlobMetadataHeader.
89     //
90     // OK - success.
91     // UNAVAILABLE - Unable to open, another writer or reader instance is
92     //     already open.
93     Status Open();
94 
95     // Finalize a blob write. Flush all remaining buffered data to storage and
96     // store blob metadata. Close fails in the closed state, do NOT retry Close
97     // on error. An error may or may not result in an invalid blob stored.
98     // Returns:
99     //
100     // OK - success.
101     // DATA_LOSS - Error writing data or fail to verify written data.
102     Status Close();
103 
IsOpen()104     bool IsOpen() { return open_; }
105 
106     // Erase the blob partition and reset state for a new blob. Explicit calls
107     // to Erase are optional, beginning a write will do any needed Erase.
108     // Returns:
109     //
110     // OK - success.
111     // UNAVAILABLE - Unable to erase while reader is open.
112     // [error status] - flash erase failed.
Erase()113     Status Erase() {
114       return open_ ? store_.Erase() : Status::FailedPrecondition();
115     }
116 
117     // Discard the current blob. Any written bytes to this point are considered
118     // invalid. Returns:
119     //
120     // OK - success.
121     // FAILED_PRECONDITION - not open.
Discard()122     Status Discard() {
123       return open_ ? store_.Invalidate() : Status::FailedPrecondition();
124     }
125 
126     // Sets file name to be associated with the data written by this
127     // ``BlobWriter``. This may be changed any time before Close() is called.
128     //
129     // Calling Discard() or Erase() will clear any set file name.
130     //
131     // The underlying buffer behind file_name may be invalidated after this
132     // function returns as the string is copied to the internally managed encode
133     // buffer.
134     //
135     // Preconditions:
136     // This writer must be open.
137     //
138     // OK - successfully set file name.
139     // RESOURCE_EXHAUSTED - File name too large to fit in metadata encode
140     //   buffer, file name not set.
141     Status SetFileName(std::string_view file_name);
142 
CurrentSizeBytes()143     size_t CurrentSizeBytes() const {
144       return open_ ? store_.write_address_ : 0;
145     }
146 
147     // Max file name length, not including null terminator (null terminators
148     // are not stored).
MaxFileNameLength()149     size_t MaxFileNameLength() {
150       return metadata_buffer_.size_bytes() <
151                      sizeof(internal::BlobMetadataHeader)
152                  ? 0
153                  : metadata_buffer_.size_bytes() -
154                        sizeof(internal::BlobMetadataHeader);
155     }
156 
157    protected:
DoWrite(ConstByteSpan data)158     Status DoWrite(ConstByteSpan data) override {
159       return open_ ? store_.Write(data) : Status::FailedPrecondition();
160     }
161 
162     // Commits changes to KVS as a BlobStore metadata entry.
163     Status WriteMetadata();
164 
165     BlobStore& store_;
166     ByteSpan metadata_buffer_;
167     bool open_;
168 
169    private:
170     // Probable (not guaranteed) minimum number of bytes at this time that can
171     // be written. This is not necessarily the full number of bytes remaining in
172     // the blob. Returns zero if, in the current state, Write would return
173     // status other than OK. See stream.h for additional details.
ConservativeLimit(LimitType limit)174     size_t ConservativeLimit(LimitType limit) const override {
175       if (open_ && limit == LimitType::kWrite) {
176         return store_.WriteBytesRemaining();
177       }
178       return 0;
179     }
180   };
181 
182   template <size_t kMaxFileNameSize = 0>
183   class BlobWriterWithBuffer final : public BlobWriter {
184    public:
BlobWriterWithBuffer(BlobStore & store)185     constexpr BlobWriterWithBuffer(BlobStore& store)
186         : BlobWriter(store, buffer_), buffer_() {}
187 
188    private:
189     std::array<std::byte, RequiredMetadataBufferSize(kMaxFileNameSize)> buffer_;
190   };
191 
192   // Implement the stream::Writer and erase interface with deferred action for a
193   // BlobStore. If not already erased, the Flush will do any needed erase.
194   //
195   // Only one writter (of either type) is allowed to be open at a time.
196   // Additionally, writers are unable to open if a reader is already open.
197   class DeferredWriter : public BlobWriter {
198    public:
DeferredWriter(BlobStore & store,ByteSpan metadata_buffer)199     constexpr DeferredWriter(BlobStore& store, ByteSpan metadata_buffer)
200         : BlobWriter(store, metadata_buffer) {}
201     DeferredWriter(const DeferredWriter&) = delete;
202     DeferredWriter& operator=(const DeferredWriter&) = delete;
~DeferredWriter()203     virtual ~DeferredWriter() {}
204 
205     // Flush data in the write buffer. Only a multiple of flash_write_size_bytes
206     // are written in the flush. Any remainder is held until later for either
207     // a flush with flash_write_size_bytes buffered or the writer is closed.
Flush()208     Status Flush() {
209       return open_ ? store_.Flush() : Status::FailedPrecondition();
210     }
211 
212     // Probable (not guaranteed) minimum number of bytes at this time that can
213     // be written. This is not necessarily the full number of bytes remaining in
214     // the blob. Returns zero if, in the current state, Write would return
215     // status other than OK. See stream.h for additional details.
ConservativeLimit(LimitType limit)216     size_t ConservativeLimit(LimitType limit) const final {
217       if (open_ && limit == LimitType::kWrite) {
218         // Deferred writes need to fit in the write buffer.
219         return store_.WriteBufferBytesFree();
220       }
221       return 0;
222     }
223 
224    private:
225     // Similar to normal Write, but instead immediately writing out to flash,
226     // it only buffers the data. A flush or Close is reqired to get bytes
227     // writen out to flash.
228     //
229     // AddToWriteBuffer will continue to accept new data after Flush has an
230     // erase error (buffer space permitting). Write errors during Flush will
231     // result in no new data being accepted.
DoWrite(ConstByteSpan data)232     Status DoWrite(ConstByteSpan data) final {
233       return open_ ? store_.AddToWriteBuffer(data)
234                    : Status::FailedPrecondition();
235     }
236   };
237 
238   template <size_t kMaxFileNameSize = 0>
239   class DeferredWriterWithBuffer final : public DeferredWriter {
240    public:
DeferredWriterWithBuffer(BlobStore & store)241     constexpr DeferredWriterWithBuffer(BlobStore& store)
242         : DeferredWriter(store, buffer_), buffer_() {}
243 
244    private:
245     std::array<std::byte, RequiredMetadataBufferSize(kMaxFileNameSize)> buffer_;
246   };
247 
248   // Implement stream::Reader interface for BlobStore. Multiple readers may be
249   // open at the same time, but readers may not be open with a writer open.
250   class BlobReader final : public stream::SeekableReader {
251    public:
BlobReader(BlobStore & store)252     constexpr BlobReader(BlobStore& store)
253         : store_(store), open_(false), offset_(0) {}
254 
255     BlobReader(const BlobReader&) = delete;
256     BlobReader& operator=(const BlobReader&) = delete;
257 
~BlobReader()258     ~BlobReader() {
259       if (open_) {
260         Close().IgnoreError();
261       }
262     }
263 
264     // Open to do a blob read at the given offset in to the blob. Can not open
265     // when already open. Multiple readers can be open at the same time.
266     // Returns:
267     //
268     //   OK - success.
269     //   FAILED_PRECONDITION - No readable blob available.
270     //   INVALID_ARGUMENT - Invalid offset.
271     //   UNAVAILABLE - Unable to open, already open.
272     //
273     Status Open(size_t offset = 0);
274 
275     // Finish reading a blob. Close fails in the closed state, do NOT retry
276     // Close on error. Returns:
277     //
278     //   OK - success
279     //   FAILED_PRECONDITION - already closed
280     //
Close()281     Status Close() {
282       if (!open_) {
283         return Status::FailedPrecondition();
284       }
285       open_ = false;
286       return store_.CloseRead();
287     }
288 
289     // Copies the file name of the stored data to `dest`, and returns the number
290     // of bytes written to the destination buffer. The string is not
291     // null-terminated.
292     //
293     // Returns:
294     //   OK - File name copied, size contains file name length.
295     //   RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains
296     //     first N bytes of the file name.
297     //   NOT_FOUND - No file name set for this blob.
298     //   FAILED_PRECONDITION - not open
299     //
GetFileName(std::span<char> dest)300     StatusWithSize GetFileName(std::span<char> dest) {
301       return open_ ? store_.GetFileName(dest)
302                    : StatusWithSize::FailedPrecondition();
303     }
304 
IsOpen()305     bool IsOpen() const { return open_; }
306 
307     // Get a span with the MCU pointer and size of the data. Returns:
308     //
309     //   OK with span - Valid span respresenting the blob data
310     //   FAILED_PRECONDITION - Reader not open.
311     //   UNIMPLEMENTED - Memory mapped access not supported for this blob.
312     //   FAILED_PRECONDITION - Writer is closed
313     //
GetMemoryMappedBlob()314     Result<ConstByteSpan> GetMemoryMappedBlob() {
315       return open_ ? store_.GetMemoryMappedBlob()
316                    : Status::FailedPrecondition();
317     }
318 
319    private:
320     // Probable (not guaranteed) minimum number of bytes at this time that can
321     // be read. Returns zero if, in the current state, Read would return status
322     // other than OK. See stream.h for additional details.
323     size_t ConservativeLimit(LimitType limit) const override;
324 
325     size_t DoTell() const override;
326 
327     Status DoSeek(ptrdiff_t offset, Whence origin) override;
328 
329     StatusWithSize DoRead(ByteSpan dest) override;
330 
331     BlobStore& store_;
332     bool open_;
333     size_t offset_;
334   };
335 
336   // BlobStore
337   // name - Name of blob store, used for metadata KVS key
338   // partition - Flash partiton to use for this blob. Blob uses the entire
339   //     partition for blob data.
340   // checksum_algo - Optional checksum for blob integrity checking. Use nullptr
341   //     for no check.
342   // kvs - KVS used for storing blob metadata.
343   // write_buffer - Used for buffering writes. Needs to be at least
344   //     flash_write_size_bytes.
345   // flash_write_size_bytes - Size in bytes to use for flash write operations.
346   //     This should be chosen to balance optimal write size and required buffer
347   //     size. Must be greater than or equal to flash write alignment, less than
348   //     or equal to flash sector size.
BlobStore(std::string_view name,kvs::FlashPartition & partition,kvs::ChecksumAlgorithm * checksum_algo,sync::Borrowable<kvs::KeyValueStore> & kvs,ByteSpan write_buffer,size_t flash_write_size_bytes)349   BlobStore(std::string_view name,
350             kvs::FlashPartition& partition,
351             kvs::ChecksumAlgorithm* checksum_algo,
352             sync::Borrowable<kvs::KeyValueStore>& kvs,
353             ByteSpan write_buffer,
354             size_t flash_write_size_bytes)
355       : name_(name),
356         partition_(partition),
357         checksum_algo_(checksum_algo),
358         kvs_(kvs),
359         write_buffer_(write_buffer),
360         flash_write_size_bytes_(flash_write_size_bytes),
361         initialized_(false),
362         valid_data_(false),
363         flash_erased_(false),
364         writer_open_(false),
365         readers_open_(0),
366         write_address_(0),
367         flash_address_(0),
368         file_name_length_(0) {}
369 
370   BlobStore(const BlobStore&) = delete;
371   BlobStore& operator=(const BlobStore&) = delete;
372 
373   // Initialize the blob instance. Checks if storage is erased or has any stored
374   // blob data. Returns:
375   //
376   // OK - success.
377   Status Init();
378 
379   // Maximum number of data bytes this BlobStore is able to store.
380   size_t MaxDataSizeBytes() const;
381 
382   // Get the current data state of the blob without needing to instantiate
383   // and/or open a reader or writer. This check is independent of any writers or
384   // readers of this blob that might exist (open or closed).
385   //
386   // NOTE: This state can be changed by any writer that is open(ed) for this
387   //       blob. Readers can not be opened until any open writers are closed.
388   //
389   // true -  Blob is valid/OK and has at least 1 data byte.
390   // false -  Blob is either invalid or does not have any data bytes
HasData()391   bool HasData() const { return (valid_data_ && ReadableDataBytes() > 0); }
392 
393  private:
394   Status LoadMetadata();
395 
396   // Open to do a blob write. Returns:
397   //
398   // OK - success.
399   // UNAVAILABLE - Unable to open writer, another writer or reader instance is
400   //     already open.
401   Status OpenWrite();
402 
403   // Open to do a blob read. Returns:
404   //
405   // OK - success.
406   // FAILED_PRECONDITION - Unable to open, no valid blob available.
407   Status OpenRead();
408 
409   // Finalize a blob write. Flush all remaining buffered data to storage and
410   // store blob metadata. Returns:
411   //
412   // OK - success, valid complete blob.
413   // DATA_LOSS - Error during write (this close or previous write/flush). Blob
414   //     is closed and marked as invalid.
415   Status CloseRead();
416 
417   // Write/append data to the in-progress blob write. Data is written
418   // sequentially, with each append added directly after the previous. Data is
419   // not guaranteed to be fully written out to storage on Write return. Returns:
420   //
421   // OK - successful write/enqueue of data.
422   // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No
423   //     data written.
424   // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written,
425   //     no more will be written.
426   // DATA_LOSS - Error during write (this write or previous write/flush). No
427   //     more will be written by following Write calls for current blob (until
428   //     erase/new blob started).
429   Status Write(ConstByteSpan data);
430 
431   // Similar to Write, but instead immediately writing out to flash, it only
432   // buffers the data. A flush or Close is reqired to get bytes writen out to
433   // flash.
434   //
435   // AddToWriteBuffer will continue to accept new data after Flush has an erase
436   // error (buffer space permitting). Write errors during Flush will result in
437   // no new data being accepted.
438   //
439   // OK - successful write/enqueue of data.
440   // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No
441   //     data written.
442   // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written,
443   //     no more will be written.
444   // DATA_LOSS - Error during a previous write/flush. No more will be written by
445   //     following Write calls for current blob (until erase/new blob started).
446   Status AddToWriteBuffer(ConstByteSpan data);
447 
448   // Flush data in the write buffer. Only a multiple of flash_write_size_bytes
449   // are written in the flush. Any remainder is held until later for either a
450   // flush with flash_write_size_bytes buffered or the writer is closed.
451   //
452   // OK - successful write/enqueue of data.
453   // DATA_LOSS - Error during write (this flush or previous write/flush). No
454   //     more will be written by following Write calls for current blob (until
455   //     erase/new blob started).
456   Status Flush();
457 
458   // Flush a chunk of data in the write buffer smaller than
459   // flash_write_size_bytes. This is only for the final flush as part of the
460   // CloseWrite. The partial chunk is padded to flash_write_size_bytes and a
461   // flash_write_size_bytes chunk is written to flash.
462   //
463   // OK - successful write/enqueue of data.
464   // DATA_LOSS - Error during write (this flush or previous write/flush). No
465   //     more will be written by following Write calls for current blob (until
466   //     erase/new blob started).
467   Status FlushFinalPartialChunk();
468 
469   // Commit data to flash and update flash_address_ with data bytes written. The
470   // only time data_bytes should be manually specified is for a CloseWrite with
471   // an unaligned-size chunk remaining in the buffer that has been zero padded
472   // to alignment.
473   Status CommitToFlash(ConstByteSpan source, size_t data_bytes = 0);
474 
475   // Blob is valid/OK to write to. Blob is considered valid to write if no data
476   // has been written due to the auto/implicit erase on write start.
477   //
478   // true - Blob is valid and OK to write to.
479   // false - Blob has previously had an error and not valid for writing new
480   //     data.
ValidToWrite()481   bool ValidToWrite() { return (valid_data_ == true) || (flash_address_ == 0); }
482 
WriteBufferEmpty()483   bool WriteBufferEmpty() const { return flash_address_ == write_address_; }
484 
485   size_t WriteBufferBytesUsed() const;
486 
487   size_t WriteBufferBytesFree() const;
488 
489   Status EraseIfNeeded();
490 
491   // Read valid data. Attempts to read the lesser of output.size_bytes() or
492   // available bytes worth of data. Returns:
493   //
494   // OK with span of bytes read - success, between 1 and dest.size_bytes() were
495   //     read.
496   // INVALID_ARGUMENT - offset is invalid.
497   // FAILED_PRECONDITION - Reader unable/not in state to read data.
498   // RESOURCE_EXHAUSTED - unable to read any bytes at this time. No bytes read.
499   //     Try again once bytes become available.
500   // OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes read, no
501   //     more will be read.
502   StatusWithSize Read(size_t offset, ByteSpan dest) const;
503 
504   // Get a span with the MCU pointer and size of the data. Returns:
505   //
506   // OK with span - Valid span respresenting the blob data
507   // FAILED_PRECONDITION - Blob not in a state to read data
508   // UNIMPLEMENTED - Memory mapped access not supported for this blob.
509   Result<ConstByteSpan> GetMemoryMappedBlob() const;
510 
511   // Size of blob/readable data, in bytes.
512   size_t ReadableDataBytes() const;
513 
WriteBytesRemaining()514   size_t WriteBytesRemaining() const {
515     return MaxDataSizeBytes() - write_address_;
516   }
517 
518   Status Erase();
519 
520   Status Invalidate();
521 
ResetChecksum()522   void ResetChecksum() {
523     if (checksum_algo_ != nullptr) {
524       checksum_algo_->Reset();
525     }
526   }
527 
528   Status ValidateChecksum(size_t blob_size_bytes,
529                           internal::ChecksumValue expected);
530 
531   Status CalculateChecksumFromFlash(size_t bytes_to_check);
532 
MetadataKey()533   const std::string_view MetadataKey() const { return name_; }
534 
535   // Copies the file name of the stored data to `dest`, and returns the number
536   // of bytes written to the destination buffer. The string is not
537   // null-terminated.
538   //
539   // Returns:
540   //   OK - File name copied, size contains file name length.
541   //   RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains
542   //     first N bytes of the file name.
543   //   NOT_FOUND - No file name set for this blob.
544   //   FAILED_PRECONDITION - BlobStore has not been initialized.
545   StatusWithSize GetFileName(std::span<char> dest) const;
546 
547   std::string_view name_;
548   kvs::FlashPartition& partition_;
549   // checksum_algo_ of nullptr indicates no checksum algorithm.
550   kvs::ChecksumAlgorithm* const checksum_algo_;
551   sync::Borrowable<kvs::KeyValueStore>& kvs_;
552   ByteSpan write_buffer_;
553 
554   // Size in bytes of flash write operations. This should be chosen to balance
555   // optimal write size and required buffer size. Must be GE flash write
556   // alignment, LE flash sector size.
557   const size_t flash_write_size_bytes_;
558 
559   //
560   // Internal state for Blob store
561   //
562   // TODO: Consolidate blob state to a single struct
563 
564   // Initialization has been done.
565   bool initialized_;
566 
567   // Bytes stored are valid and good. Blob is OK to read and write to. Set as
568   // soon as blob is erased. Even when bytes written is still 0, they are valid.
569   bool valid_data_;
570 
571   // Blob partition is currently erased and ready to write a new blob.
572   bool flash_erased_;
573 
574   // BlobWriter instance is currently open
575   bool writer_open_;
576 
577   // Count of open BlobReader instances
578   size_t readers_open_;
579 
580   // Current index for end of overall blob data. Represents current byte size of
581   // blob data since the FlashPartition starts at address 0.
582   kvs::FlashPartition::Address write_address_;
583 
584   // Current index of end of data written to flash. Number of buffered data
585   // bytes is write_address_ - flash_address_.
586   kvs::FlashPartition::Address flash_address_;
587 
588   // Length of the stored blob's filename.
589   size_t file_name_length_;
590 };
591 
592 // Creates a BlobStore with the buffer of kBufferSizeBytes.
593 //
594 // kBufferSizeBytes - Size in bytes of write buffer to create.
595 // name - Name of blob store, used for metadata KVS key
596 // partition - Flash partition to use for this blob. Blob uses the entire
597 //     partition for blob data.
598 // checksum_algo - Optional checksum for blob integrity checking. Use nullptr
599 //     for no check.
600 // kvs - KVS used for storing blob metadata.
601 // write_buffer - Used for buffering writes. Needs to be at least
602 //     flash_write_size_bytes.
603 // flash_write_size_bytes - Size in bytes to use for flash write operations.
604 //     This should be chosen to balance optimal write size and required buffer
605 //     size. Must be greater than or equal to flash write alignment, less than
606 //     or equal to flash sector size.
607 
608 template <size_t kBufferSizeBytes>
609 class BlobStoreBuffer : public BlobStore {
610  public:
BlobStoreBuffer(std::string_view name,kvs::FlashPartition & partition,kvs::ChecksumAlgorithm * checksum_algo,sync::Borrowable<kvs::KeyValueStore> & kvs,size_t flash_write_size_bytes)611   explicit BlobStoreBuffer(std::string_view name,
612                            kvs::FlashPartition& partition,
613                            kvs::ChecksumAlgorithm* checksum_algo,
614                            sync::Borrowable<kvs::KeyValueStore>& kvs,
615                            size_t flash_write_size_bytes)
616       : BlobStore(name,
617                   partition,
618                   checksum_algo,
619                   kvs,
620                   buffer_,
621                   flash_write_size_bytes) {}
622 
623  private:
624   std::array<std::byte, kBufferSizeBytes> buffer_;
625 };
626 
627 }  // namespace pw::blob_store
628