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