• 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 
15 #include "pw_blob_store/blob_store.h"
16 
17 #include <algorithm>
18 
19 #include "pw_assert/check.h"
20 #include "pw_blob_store/internal/metadata_format.h"
21 #include "pw_bytes/byte_builder.h"
22 #include "pw_bytes/span.h"
23 #include "pw_kvs/checksum.h"
24 #include "pw_kvs/flash_memory.h"
25 #include "pw_kvs/key_value_store.h"
26 #include "pw_log/log.h"
27 #include "pw_status/status.h"
28 #include "pw_status/status_with_size.h"
29 #include "pw_status/try.h"
30 #include "pw_stream/stream.h"
31 
32 namespace pw::blob_store {
33 
34 using internal::BlobMetadataHeader;
35 using internal::ChecksumValue;
36 
Init()37 Status BlobStore::Init() {
38   if (initialized_) {
39     return OkStatus();
40   }
41 
42   PW_LOG_INFO("Init BlobStore");
43 
44   const size_t flash_write_size_alignment =
45       flash_write_size_bytes_ % partition_.alignment_bytes();
46   PW_CHECK_UINT_EQ(flash_write_size_alignment, 0);
47   PW_CHECK_UINT_GE(flash_write_size_bytes_, partition_.alignment_bytes());
48   const size_t partition_size_alignment =
49       partition_.size_bytes() % flash_write_size_bytes_;
50   PW_CHECK_UINT_EQ(partition_size_alignment, 0);
51   if (!write_buffer_.empty()) {
52     PW_CHECK_UINT_GE(write_buffer_.size_bytes(), flash_write_size_bytes_);
53   }
54 
55   ResetChecksum();
56   initialized_ = true;
57 
58   if (LoadMetadata().ok()) {
59     PW_LOG_DEBUG("BlobStore init - Have valid blob of %u bytes",
60                  static_cast<unsigned>(write_address_));
61     return OkStatus();
62   }
63 
64   // No saved blob, assume it has not been erased yet even if it has to avoid
65   // having to scan the potentially massive partition.
66   PW_LOG_DEBUG("BlobStore init - No valid blob, assuming not erased");
67   return OkStatus();
68 }
69 
LoadMetadata()70 Status BlobStore::LoadMetadata() {
71   write_address_ = 0;
72   flash_address_ = 0;
73   file_name_length_ = 0;
74   valid_data_ = false;
75 
76   BlobMetadataHeader metadata;
77   metadata.reset();
78 
79   // For kVersion1 metadata versions, only the first member of
80   // BlobMetadataHeaderV2 will be populated. If a file name is present,
81   // kvs_.Get() will return RESOURCE_EXHAUSTED as the file name won't fit in the
82   // BlobMetadtataHeader object, which is intended behavior.
83   if (StatusWithSize sws = kvs_.acquire()->Get(
84           MetadataKey(), std::as_writable_bytes(std::span(&metadata, 1)));
85       !sws.ok() && !sws.IsResourceExhausted()) {
86     return Status::NotFound();
87   }
88 
89   if (!ValidateChecksum(metadata.v1_metadata.data_size_bytes,
90                         metadata.v1_metadata.checksum)
91            .ok()) {
92     PW_LOG_ERROR("BlobStore init - Invalidating blob with invalid checksum");
93     Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
94     return Status::DataLoss();
95   }
96 
97   write_address_ = metadata.v1_metadata.data_size_bytes;
98   flash_address_ = metadata.v1_metadata.data_size_bytes;
99   file_name_length_ = metadata.file_name_length;
100   valid_data_ = true;
101 
102   return OkStatus();
103 }
104 
MaxDataSizeBytes() const105 size_t BlobStore::MaxDataSizeBytes() const { return partition_.size_bytes(); }
106 
OpenWrite()107 Status BlobStore::OpenWrite() {
108   if (!initialized_) {
109     return Status::FailedPrecondition();
110   }
111 
112   // Writer can only be opened if there are no other writer or readers already
113   // open.
114   if (writer_open_ || readers_open_ != 0) {
115     return Status::Unavailable();
116   }
117 
118   PW_LOG_DEBUG("Blob writer open");
119 
120   writer_open_ = true;
121 
122   // Clear any existing contents.
123   Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
124 
125   return OkStatus();
126 }
127 
GetFileName(std::span<char> dest) const128 StatusWithSize BlobStore::GetFileName(std::span<char> dest) const {
129   if (!initialized_) {
130     return StatusWithSize(Status::FailedPrecondition(), 0);
131   }
132 
133   if (file_name_length_ == 0) {
134     return StatusWithSize(Status::NotFound(), 0);
135   }
136 
137   const size_t bytes_to_read =
138       std::min(dest.size_bytes(), static_cast<size_t>(file_name_length_));
139 
140   Status status = bytes_to_read == file_name_length_
141                       ? OkStatus()
142                       : Status::ResourceExhausted();
143 
144   // Read file name from KVS.
145   constexpr size_t kFileNameOffset = sizeof(BlobMetadataHeader);
146   const StatusWithSize kvs_read_sws =
147       kvs_.acquire()->Get(MetadataKey(),
148                           std::as_writable_bytes(dest.first(bytes_to_read)),
149                           kFileNameOffset);
150   status.Update(kvs_read_sws.status());
151   return StatusWithSize(status, kvs_read_sws.size());
152 }
153 
OpenRead()154 Status BlobStore::OpenRead() {
155   if (!initialized_) {
156     return Status::FailedPrecondition();
157   }
158 
159   // Reader can only be opened if there is no writer open.
160   if (writer_open_) {
161     return Status::Unavailable();
162   }
163 
164   if (!HasData()) {
165     PW_LOG_ERROR("Blob reader unable open without valid data");
166     return Status::FailedPrecondition();
167   }
168 
169   PW_LOG_DEBUG("Blob reader open");
170 
171   readers_open_++;
172   return OkStatus();
173 }
174 
CloseRead()175 Status BlobStore::CloseRead() {
176   PW_CHECK_UINT_GT(readers_open_, 0);
177   readers_open_--;
178   PW_LOG_DEBUG("Blob reader close");
179   return OkStatus();
180 }
181 
Write(ConstByteSpan data)182 Status BlobStore::Write(ConstByteSpan data) {
183   if (!ValidToWrite()) {
184     return Status::DataLoss();
185   }
186   if (data.size_bytes() == 0) {
187     return OkStatus();
188   }
189   if (WriteBytesRemaining() == 0) {
190     return Status::OutOfRange();
191   }
192   if (WriteBytesRemaining() < data.size_bytes()) {
193     return Status::ResourceExhausted();
194   }
195   if ((write_buffer_.empty()) &&
196       ((data.size_bytes() % flash_write_size_bytes_) != 0)) {
197     return Status::InvalidArgument();
198   }
199 
200   if (!EraseIfNeeded().ok()) {
201     return Status::DataLoss();
202   }
203 
204   // Write in (up to) 3 steps:
205   // 1) Finish filling write buffer and if full write it to flash.
206   // 2) Write as many whole block-sized chunks as the data has remaining
207   //    after 1.
208   // 3) Put any remaining bytes less than flash write size in the write buffer.
209 
210   // Step 1) If there is any data in the write buffer, finish filling write
211   //         buffer and if full write it to flash.
212   if (!WriteBufferEmpty()) {
213     PW_DCHECK(!write_buffer_.empty());
214     size_t bytes_in_buffer = WriteBufferBytesUsed();
215 
216     // Non-deferred writes only use the first flash_write_size_bytes_ of the
217     // write buffer to buffer writes less than flash_write_size_bytes_.
218     PW_CHECK_UINT_GT(flash_write_size_bytes_, bytes_in_buffer);
219 
220     // Not using WriteBufferBytesFree() because non-deferred writes (which
221     // is this method) only use the first flash_write_size_bytes_ of the write
222     // buffer.
223     size_t buffer_remaining = flash_write_size_bytes_ - bytes_in_buffer;
224 
225     // Add bytes up to filling the flash write size.
226     size_t add_bytes = std::min(buffer_remaining, data.size_bytes());
227     std::memcpy(write_buffer_.data() + bytes_in_buffer, data.data(), add_bytes);
228     write_address_ += add_bytes;
229     bytes_in_buffer += add_bytes;
230     data = data.subspan(add_bytes);
231 
232     if (bytes_in_buffer != flash_write_size_bytes_) {
233       // If there was not enough bytes to finish filling the write buffer, there
234       // should not be any bytes left.
235       PW_DCHECK(data.size_bytes() == 0);
236       return OkStatus();
237     }
238 
239     // The write buffer is full, flush to flash.
240     if (!CommitToFlash(write_buffer_.first(flash_write_size_bytes_)).ok()) {
241       return Status::DataLoss();
242     }
243   }
244 
245   // At this point, if data.size_bytes() > 0, the write buffer should be empty.
246   // This invariant is checked as part of of steps 2 & 3.
247 
248   // Step 2) Write as many block-sized chunks as the data has remaining after
249   //         step 1.
250   PW_DCHECK(WriteBufferEmpty());
251 
252   const size_t final_partial_write_size_bytes =
253       data.size_bytes() % flash_write_size_bytes_;
254 
255   if (data.size_bytes() >= flash_write_size_bytes_) {
256     const size_t write_size_bytes =
257         data.size_bytes() - final_partial_write_size_bytes;
258     write_address_ += write_size_bytes;
259     if (!CommitToFlash(data.first(write_size_bytes)).ok()) {
260       return Status::DataLoss();
261     }
262     data = data.subspan(write_size_bytes);
263   }
264 
265   // step 3) Put any remaining bytes to the buffer. Put the bytes starting at
266   //         the begining of the buffer, since it must be empty if there are
267   //         still bytes due to step 1 either cleaned out the buffer or didn't
268   //         have any more data to write.
269   if (final_partial_write_size_bytes > 0) {
270     PW_DCHECK_INT_LT(data.size_bytes(), flash_write_size_bytes_);
271     PW_DCHECK(!write_buffer_.empty());
272 
273     // Don't need to DCHECK that buffer is empty, nothing writes to it since the
274     // previous time it was DCHECK'ed
275     std::memcpy(write_buffer_.data(), data.data(), data.size_bytes());
276     write_address_ += data.size_bytes();
277   }
278 
279   return OkStatus();
280 }
281 
AddToWriteBuffer(ConstByteSpan data)282 Status BlobStore::AddToWriteBuffer(ConstByteSpan data) {
283   if (!ValidToWrite()) {
284     return Status::DataLoss();
285   }
286   if (WriteBytesRemaining() == 0) {
287     return Status::OutOfRange();
288   }
289   if (WriteBufferBytesFree() < data.size_bytes()) {
290     return Status::ResourceExhausted();
291   }
292 
293   size_t bytes_in_buffer = WriteBufferBytesUsed();
294 
295   std::memcpy(
296       write_buffer_.data() + bytes_in_buffer, data.data(), data.size_bytes());
297   write_address_ += data.size_bytes();
298 
299   return OkStatus();
300 }
301 
Flush()302 Status BlobStore::Flush() {
303   if (!ValidToWrite()) {
304     return Status::DataLoss();
305   }
306   if (WriteBufferBytesUsed() == 0) {
307     return OkStatus();
308   }
309   // Don't need to check available space, AddToWriteBuffer() will not enqueue
310   // more than can be written to flash.
311 
312   // If there is no buffer there should never be any bytes enqueued.
313   PW_DCHECK(!write_buffer_.empty());
314 
315   if (!EraseIfNeeded().ok()) {
316     return Status::DataLoss();
317   }
318 
319   ByteSpan data = std::span(write_buffer_.data(), WriteBufferBytesUsed());
320   size_t write_size_bytes =
321       (data.size_bytes() / flash_write_size_bytes_) * flash_write_size_bytes_;
322   if (!CommitToFlash(data.first(write_size_bytes)).ok()) {
323     return Status::DataLoss();
324   }
325   data = data.subspan(write_size_bytes);
326   PW_DCHECK_INT_LT(data.size_bytes(), flash_write_size_bytes_);
327 
328   // Only a multiple of flash_write_size_bytes_ are written in the flush. Any
329   // remainder is held until later for either a flush with
330   // flash_write_size_bytes buffered or the writer is closed.
331   if (!WriteBufferEmpty()) {
332     PW_DCHECK_UINT_EQ(data.size_bytes(), WriteBufferBytesUsed());
333     // For any leftover bytes less than the flash write size, move them to the
334     // start of the bufer.
335     std::memmove(write_buffer_.data(), data.data(), data.size_bytes());
336   } else {
337     PW_DCHECK_UINT_EQ(data.size_bytes(), 0);
338   }
339 
340   return OkStatus();
341 }
342 
FlushFinalPartialChunk()343 Status BlobStore::FlushFinalPartialChunk() {
344   size_t bytes_in_buffer = WriteBufferBytesUsed();
345 
346   PW_DCHECK_UINT_GT(bytes_in_buffer, 0);
347   PW_DCHECK_UINT_LE(bytes_in_buffer, flash_write_size_bytes_);
348   PW_DCHECK_UINT_LE(flash_write_size_bytes_,
349                     MaxDataSizeBytes() - flash_address_);
350 
351   // If there is no buffer there should never be any bytes enqueued.
352   PW_DCHECK(!write_buffer_.empty());
353 
354   PW_LOG_DEBUG(
355       "  Remainder %u bytes in write buffer to zero-pad to flash write "
356       "size and commit",
357       static_cast<unsigned>(bytes_in_buffer));
358 
359   // Zero out the remainder of the buffer.
360   auto zero_span = write_buffer_.subspan(bytes_in_buffer);
361   std::memset(zero_span.data(),
362               static_cast<int>(partition_.erased_memory_content()),
363               zero_span.size_bytes());
364 
365   ConstByteSpan remaining_bytes = write_buffer_.first(flash_write_size_bytes_);
366   return CommitToFlash(remaining_bytes, bytes_in_buffer);
367 }
368 
CommitToFlash(ConstByteSpan source,size_t data_bytes)369 Status BlobStore::CommitToFlash(ConstByteSpan source, size_t data_bytes) {
370   if (data_bytes == 0) {
371     data_bytes = source.size_bytes();
372   }
373 
374   flash_erased_ = false;
375   StatusWithSize result = partition_.Write(flash_address_, source);
376   flash_address_ += data_bytes;
377   if (checksum_algo_ != nullptr) {
378     checksum_algo_->Update(source.first(data_bytes));
379   }
380 
381   if (!result.status().ok()) {
382     valid_data_ = false;
383   }
384 
385   return result.status();
386 }
387 
388 // Needs to be in .cc file since PW_CHECK doesn't like being in .h files.
WriteBufferBytesUsed() const389 size_t BlobStore::WriteBufferBytesUsed() const {
390   PW_CHECK_UINT_GE(write_address_, flash_address_);
391   return write_address_ - flash_address_;
392 }
393 
394 // Needs to be in .cc file since PW_DCHECK doesn't like being in .h files.
WriteBufferBytesFree() const395 size_t BlobStore::WriteBufferBytesFree() const {
396   PW_DCHECK_UINT_GE(write_buffer_.size_bytes(), WriteBufferBytesUsed());
397   size_t buffer_remaining = write_buffer_.size_bytes() - WriteBufferBytesUsed();
398   return std::min(buffer_remaining, WriteBytesRemaining());
399 }
400 
EraseIfNeeded()401 Status BlobStore::EraseIfNeeded() {
402   if (flash_address_ == 0) {
403     // Always just erase. Erase is smart enough to only erase if needed.
404     return Erase();
405   }
406   return OkStatus();
407 }
408 
Read(size_t offset,ByteSpan dest) const409 StatusWithSize BlobStore::Read(size_t offset, ByteSpan dest) const {
410   if (!HasData()) {
411     return StatusWithSize::FailedPrecondition();
412   }
413   if (offset >= ReadableDataBytes()) {
414     return StatusWithSize::OutOfRange();
415   }
416 
417   size_t available_bytes = ReadableDataBytes() - offset;
418   size_t read_size = std::min(available_bytes, dest.size_bytes());
419 
420   return partition_.Read(offset, dest.first(read_size));
421 }
422 
GetMemoryMappedBlob() const423 Result<ConstByteSpan> BlobStore::GetMemoryMappedBlob() const {
424   if (!HasData()) {
425     return Status::FailedPrecondition();
426   }
427 
428   std::byte* mcu_address = partition_.PartitionAddressToMcuAddress(0);
429   if (mcu_address == nullptr) {
430     return Status::Unimplemented();
431   }
432   return ConstByteSpan(mcu_address, ReadableDataBytes());
433 }
434 
ReadableDataBytes() const435 size_t BlobStore::ReadableDataBytes() const {
436   // TODO: clean up state related to readable bytes.
437   return flash_address_;
438 }
439 
Erase()440 Status BlobStore::Erase() {
441   // If already erased our work here is done.
442   if (flash_erased_) {
443     // The write buffer might already have bytes when this call happens, due to
444     // a deferred write.
445     PW_DCHECK_UINT_LE(write_address_, write_buffer_.size_bytes());
446     PW_DCHECK_UINT_EQ(flash_address_, 0);
447 
448     // Erased blobs should be valid as soon as the flash is erased. Even though
449     // there are 0 bytes written, they are valid.
450     PW_DCHECK(valid_data_);
451     return OkStatus();
452   }
453 
454   // If any writes have been performed, reset the state.
455   if (flash_address_ != 0) {
456     Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
457   }
458 
459   PW_TRY(partition_.Erase());
460 
461   flash_erased_ = true;
462 
463   // Blob data is considered valid as soon as the flash is erased. Even though
464   // there are 0 bytes written, they are valid.
465   valid_data_ = true;
466   return OkStatus();
467 }
468 
Invalidate()469 Status BlobStore::Invalidate() {
470   // Blob data is considered valid if the flash is erased. Even though
471   // there are 0 bytes written, they are valid.
472   valid_data_ = flash_erased_;
473   ResetChecksum();
474   write_address_ = 0;
475   flash_address_ = 0;
476   file_name_length_ = 0;
477 
478   Status status = kvs_.acquire()->Delete(MetadataKey());
479 
480   return (status.ok() || status.IsNotFound()) ? OkStatus() : Status::Internal();
481 }
482 
ValidateChecksum(size_t blob_size_bytes,ChecksumValue expected)483 Status BlobStore::ValidateChecksum(size_t blob_size_bytes,
484                                    ChecksumValue expected) {
485   if (blob_size_bytes == 0) {
486     PW_LOG_INFO("Blob unable to validate checksum of an empty blob");
487     return Status::Unavailable();
488   }
489 
490   if (checksum_algo_ == nullptr) {
491     if (expected != 0) {
492       PW_LOG_ERROR(
493           "Blob invalid to have a checkum value with no checksum algo");
494       return Status::DataLoss();
495     }
496 
497     return OkStatus();
498   }
499 
500   PW_LOG_DEBUG("Validate checksum of 0x%08x in flash for blob of %u bytes",
501                static_cast<unsigned>(expected),
502                static_cast<unsigned>(blob_size_bytes));
503   PW_TRY(CalculateChecksumFromFlash(blob_size_bytes));
504 
505   Status status = checksum_algo_->Verify(as_bytes(std::span(&expected, 1)));
506   PW_LOG_DEBUG("  checksum verify of %s", status.str());
507 
508   return status;
509 }
510 
CalculateChecksumFromFlash(size_t bytes_to_check)511 Status BlobStore::CalculateChecksumFromFlash(size_t bytes_to_check) {
512   if (checksum_algo_ == nullptr) {
513     return OkStatus();
514   }
515 
516   checksum_algo_->Reset();
517 
518   kvs::FlashPartition::Address address = 0;
519   const kvs::FlashPartition::Address end = bytes_to_check;
520 
521   constexpr size_t kReadBufferSizeBytes = 32;
522   std::array<std::byte, kReadBufferSizeBytes> buffer;
523   while (address < end) {
524     const size_t read_size = std::min(size_t(end - address), buffer.size());
525     PW_TRY(partition_.Read(address, std::span(buffer).first(read_size)));
526 
527     checksum_algo_->Update(buffer.data(), read_size);
528     address += read_size;
529   }
530 
531   // Safe to ignore the return from Finish, checksum_algo_ keeps the state
532   // information that it needs.
533   checksum_algo_->Finish();
534   return OkStatus();
535 }
536 
SetFileName(std::string_view file_name)537 Status BlobStore::BlobWriter::SetFileName(std::string_view file_name) {
538   if (!open_) {
539     return Status::FailedPrecondition();
540   }
541   PW_DCHECK_NOTNULL(file_name.data());
542   PW_DCHECK(store_.writer_open_);
543 
544   if (file_name.length() > MaxFileNameLength()) {
545     return Status::ResourceExhausted();
546   }
547 
548   // Stage the file name to the encode buffer, just past the BlobMetadataHeader
549   // struct.
550   constexpr size_t kFileNameOffset = sizeof(BlobMetadataHeader);
551   const ByteSpan file_name_dest = metadata_buffer_.subspan(kFileNameOffset);
552   std::memcpy(file_name_dest.data(), file_name.data(), file_name.length());
553 
554   store_.file_name_length_ = file_name.length();
555   return OkStatus();
556 }
557 
Open()558 Status BlobStore::BlobWriter::Open() {
559   PW_DCHECK(!open_);
560   PW_DCHECK_UINT_GE(metadata_buffer_.size_bytes(),
561                     sizeof(internal::BlobMetadataHeader));
562 
563   const Status status = store_.OpenWrite();
564   if (status.ok()) {
565     open_ = true;
566   }
567   return status;
568 }
569 
570 // Validates and commits BlobStore metadata to KVS.
571 //
572 // 1. Finalize checksum calculation.
573 // 2. Check the calculated checksum against data actually committed to flash.
574 // 3. Build the metadata header into the metadata buffer, placing it before the
575 //    staged file name (if any).
576 // 4. Commit the metadata to KVS.
WriteMetadata()577 Status BlobStore::BlobWriter::WriteMetadata() {
578   // Finalize the in-progress checksum, if any.
579   ChecksumValue calculated_checksum = 0;
580   if (store_.checksum_algo_ != nullptr) {
581     ConstByteSpan checksum = store_.checksum_algo_->Finish();
582     std::memcpy(&calculated_checksum,
583                 checksum.data(),
584                 std::min(checksum.size(), sizeof(ChecksumValue)));
585   }
586 
587   // Check the in-memory checksum against the data that was actually committed
588   // to flash.
589   if (!store_.ValidateChecksum(store_.flash_address_, calculated_checksum)
590            .ok()) {
591     PW_CHECK_OK(store_.Invalidate());
592     return Status::DataLoss();
593   }
594 
595   // Encode the metadata header. This follows the latest struct behind
596   // BlobMetadataHeader. Currently, the order is as follows:
597   // - Encode checksum.
598   // - Encode stored data size.
599   // - Encode version magic.
600   // - Encode file name size.
601   // - File name, if present, is already staged at the end.
602   //
603   // Open() guarantees the metadata buffer is large enough to fit the metadata
604   // header.
605   ByteBuilder metadata_builder(metadata_buffer_);
606   metadata_builder.PutUint32(calculated_checksum);
607   metadata_builder.PutUint32(store_.flash_address_);
608   metadata_builder.PutUint32(internal::MetadataVersion::kLatest);
609   metadata_builder.PutUint8(store_.file_name_length_);
610   PW_DCHECK_INT_EQ(metadata_builder.size(), sizeof(BlobMetadataHeader));
611   PW_DCHECK_OK(metadata_builder.status());
612 
613   // If a filename was provided, it is already written to the correct location
614   // in the buffer. When the file name was set, the metadata buffer was verified
615   // to fit the requested name in addition to the metadata header. If it doesn't
616   // fit now, something's very wrong.
617   const size_t bytes_to_write =
618       metadata_builder.size() + store_.file_name_length_;
619   PW_DCHECK(metadata_buffer_.size_bytes() >= bytes_to_write);
620 
621   // Do final commit to KVS.
622   return store_.kvs_.acquire()->Put(store_.MetadataKey(),
623                                     metadata_buffer_.first(bytes_to_write));
624 }
625 
Close()626 Status BlobStore::BlobWriter::Close() {
627   if (!open_) {
628     return Status::FailedPrecondition();
629   }
630   open_ = false;
631 
632   // This is a lambda so the BlobWriter will be unconditionally closed even if
633   // the final flash commits fail. This lambda may early return to Close() if
634   // errors are encountered, but Close() will not return without updating both
635   // the BlobWriter and BlobStore such that neither are open for writes
636   // anymore.
637   auto do_close_write = [&]() -> Status {
638     // If not valid to write, there was data loss and the close will result in a
639     // not valid blob. Don't need to flush any write buffered bytes.
640     if (!store_.ValidToWrite()) {
641       return Status::DataLoss();
642     }
643 
644     if (store_.write_address_ == 0) {
645       return OkStatus();
646     }
647 
648     PW_LOG_DEBUG(
649         "Blob writer close of %u byte blob, with %u bytes still in write "
650         "buffer",
651         static_cast<unsigned>(store_.write_address_),
652         static_cast<unsigned>(store_.WriteBufferBytesUsed()));
653 
654     // Do a Flush of any flash_write_size_bytes_ sized chunks so any remaining
655     // bytes in the write buffer are less than flash_write_size_bytes_.
656     PW_TRY(store_.Flush());
657 
658     // If any bytes remain in buffer it is because it is a chunk less than
659     // flash_write_size_bytes_. Pad the chunk to flash_write_size_bytes_ and
660     // write it to flash.
661     if (!store_.WriteBufferEmpty()) {
662       PW_TRY(store_.FlushFinalPartialChunk());
663     }
664     PW_DCHECK(store_.WriteBufferEmpty());
665 
666     if (!WriteMetadata().ok()) {
667       return Status::DataLoss();
668     }
669 
670     return OkStatus();
671   };
672 
673   const Status status = do_close_write();
674   store_.writer_open_ = false;
675 
676   if (!status.ok()) {
677     store_.valid_data_ = false;
678     return Status::DataLoss();
679   }
680   return OkStatus();
681 }
682 
ConservativeLimit(LimitType limit) const683 size_t BlobStore::BlobReader::ConservativeLimit(LimitType limit) const {
684   if (open_ && limit == LimitType::kRead) {
685     return store_.ReadableDataBytes() - offset_;
686   }
687   return 0;
688 }
689 
Open(size_t offset)690 Status BlobStore::BlobReader::Open(size_t offset) {
691   PW_DCHECK(!open_);
692   if (!store_.HasData()) {
693     return Status::FailedPrecondition();
694   }
695   if (offset >= store_.ReadableDataBytes()) {
696     return Status::InvalidArgument();
697   }
698 
699   offset_ = offset;
700   Status status = store_.OpenRead();
701   if (status.ok()) {
702     open_ = true;
703   }
704   return status;
705 }
706 
DoTell() const707 size_t BlobStore::BlobReader::DoTell() const {
708   return open_ ? offset_ : kUnknownPosition;
709 }
710 
DoSeek(ptrdiff_t offset,Whence origin)711 Status BlobStore::BlobReader::DoSeek(ptrdiff_t offset, Whence origin) {
712   if (!open_) {
713     return Status::FailedPrecondition();
714   }
715 
716   // Note that Open ensures HasData() which in turn guarantees
717   // store_.ReadableDataBytes() > 0.
718 
719   size_t pos = offset_;
720   PW_TRY(CalculateSeek(offset, origin, store_.ReadableDataBytes() - 1, pos));
721   offset_ = pos;
722 
723   return OkStatus();
724 }
725 
DoRead(ByteSpan dest)726 StatusWithSize BlobStore::BlobReader::DoRead(ByteSpan dest) {
727   if (!open_) {
728     return StatusWithSize::FailedPrecondition();
729   }
730 
731   StatusWithSize status = store_.Read(offset_, dest);
732   if (status.ok()) {
733     offset_ += status.size();
734   }
735   return status;
736 }
737 
738 }  // namespace pw::blob_store
739