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