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