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