• 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/assert.h"
20 #include "pw_log/log.h"
21 #include "pw_status/try.h"
22 
23 namespace pw::blob_store {
24 
Init()25 Status BlobStore::Init() {
26   if (initialized_) {
27     return OkStatus();
28   }
29 
30   PW_LOG_INFO("Init BlobStore");
31 
32   const size_t write_buffer_size_alignment =
33       flash_write_size_bytes_ % partition_.alignment_bytes();
34   PW_CHECK_UINT_EQ((write_buffer_size_alignment), 0);
35   PW_CHECK_UINT_GE(write_buffer_.size_bytes(), flash_write_size_bytes_);
36   PW_CHECK_UINT_GE(flash_write_size_bytes_, partition_.alignment_bytes());
37 
38   ResetChecksum();
39   initialized_ = true;
40 
41   if (LoadMetadata().ok()) {
42     valid_data_ = true;
43     write_address_ = metadata_.data_size_bytes;
44     flash_address_ = metadata_.data_size_bytes;
45 
46     PW_LOG_DEBUG("BlobStore init - Have valid blob of %u bytes",
47                  static_cast<unsigned>(write_address_));
48     return OkStatus();
49   }
50 
51   // No saved blob, check for flash being erased.
52   bool erased = false;
53   if (partition_.IsErased(&erased).ok() && erased) {
54     flash_erased_ = true;
55 
56     // Blob data is considered valid as soon as the flash is erased. Even though
57     // there are 0 bytes written, they are valid.
58     valid_data_ = true;
59     PW_LOG_DEBUG("BlobStore init - is erased");
60   } else {
61     PW_LOG_DEBUG("BlobStore init - not erased");
62   }
63   return OkStatus();
64 }
65 
LoadMetadata()66 Status BlobStore::LoadMetadata() {
67   if (!kvs_.Get(MetadataKey(), &metadata_).ok()) {
68     // If no metadata was read, make sure the metadata is reset.
69     metadata_.reset();
70     return Status::NotFound();
71   }
72 
73   if (!ValidateChecksum().ok()) {
74     PW_LOG_ERROR("BlobStore init - Invalidating blob with invalid checksum");
75     Invalidate();
76     return Status::DataLoss();
77   }
78 
79   return OkStatus();
80 }
81 
MaxDataSizeBytes() const82 size_t BlobStore::MaxDataSizeBytes() const { return partition_.size_bytes(); }
83 
OpenWrite()84 Status BlobStore::OpenWrite() {
85   if (!initialized_) {
86     return Status::FailedPrecondition();
87   }
88 
89   // Writer can only be opened if there are no other writer or readers already
90   // open.
91   if (writer_open_ || readers_open_ != 0) {
92     return Status::Unavailable();
93   }
94 
95   PW_LOG_DEBUG("Blob writer open");
96 
97   writer_open_ = true;
98 
99   Invalidate();
100 
101   return OkStatus();
102 }
103 
OpenRead()104 Status BlobStore::OpenRead() {
105   if (!initialized_) {
106     return Status::FailedPrecondition();
107   }
108 
109   // Reader can only be opened if there is no writer open.
110   if (writer_open_) {
111     return Status::Unavailable();
112   }
113 
114   if (!ValidToRead()) {
115     PW_LOG_ERROR("Blob reader unable open without valid data");
116     return Status::FailedPrecondition();
117   }
118 
119   PW_LOG_DEBUG("Blob reader open");
120 
121   readers_open_++;
122   return OkStatus();
123 }
124 
CloseWrite()125 Status BlobStore::CloseWrite() {
126   auto do_close_write = [&]() -> Status {
127     // If not valid to write, there was data loss and the close will result in a
128     // not valid blob. Don't need to flush any write buffered bytes.
129     if (!ValidToWrite()) {
130       return Status::DataLoss();
131     }
132 
133     if (write_address_ == 0) {
134       return OkStatus();
135     }
136 
137     PW_LOG_DEBUG(
138         "Blob writer close of %u byte blob, with %u bytes still in write "
139         "buffer",
140         static_cast<unsigned>(write_address_),
141         static_cast<unsigned>(WriteBufferBytesUsed()));
142 
143     // Do a Flush of any flash_write_size_bytes_ sized chunks so any remaining
144     // bytes in the write buffer are less than flash_write_size_bytes_.
145     PW_TRY(Flush());
146 
147     // If any bytes remain in buffer it is because it is a chunk less than
148     // flash_write_size_bytes_. Pad the chunk to flash_write_size_bytes_ and
149     // write it to flash.
150     if (!WriteBufferEmpty()) {
151       PW_TRY(FlushFinalPartialChunk());
152     }
153     PW_DCHECK(WriteBufferEmpty());
154 
155     // If things are still good, save the blob metadata.
156     metadata_ = {.checksum = 0, .data_size_bytes = flash_address_};
157     if (checksum_algo_ != nullptr) {
158       ConstByteSpan checksum = checksum_algo_->Finish();
159       std::memcpy(&metadata_.checksum,
160                   checksum.data(),
161                   std::min(checksum.size(), sizeof(metadata_.checksum)));
162     }
163 
164     if (!ValidateChecksum().ok()) {
165       Invalidate();
166       return Status::DataLoss();
167     }
168 
169     if (!kvs_.Put(MetadataKey(), metadata_).ok()) {
170       return Status::DataLoss();
171     }
172 
173     return OkStatus();
174   };
175 
176   const Status status = do_close_write();
177   writer_open_ = false;
178 
179   if (!status.ok()) {
180     valid_data_ = false;
181     return Status::DataLoss();
182   }
183   return OkStatus();
184 }
185 
CloseRead()186 Status BlobStore::CloseRead() {
187   PW_CHECK_UINT_GT(readers_open_, 0);
188   readers_open_--;
189   PW_LOG_DEBUG("Blob reader close");
190   return OkStatus();
191 }
192 
Write(ConstByteSpan data)193 Status BlobStore::Write(ConstByteSpan data) {
194   if (!ValidToWrite()) {
195     return Status::DataLoss();
196   }
197   if (data.size_bytes() == 0) {
198     return OkStatus();
199   }
200   if (WriteBytesRemaining() == 0) {
201     return Status::OutOfRange();
202   }
203   if (WriteBytesRemaining() < data.size_bytes()) {
204     return Status::ResourceExhausted();
205   }
206 
207   if (!EraseIfNeeded().ok()) {
208     return Status::DataLoss();
209   }
210 
211   // Write in (up to) 3 steps:
212   // 1) Finish filling write buffer and if full write it to flash.
213   // 2) Write as many whole block-sized chunks as the data has remaining
214   //    after 1.
215   // 3) Put any remaining bytes less than flash write size in the write buffer.
216 
217   // Step 1) If there is any data in the write buffer, finish filling write
218   //         buffer and if full write it to flash.
219   if (!WriteBufferEmpty()) {
220     size_t bytes_in_buffer = WriteBufferBytesUsed();
221 
222     // Non-deferred writes only use the first flash_write_size_bytes_ of the
223     // write buffer to buffer writes less than flash_write_size_bytes_.
224     PW_CHECK_UINT_GT(flash_write_size_bytes_, bytes_in_buffer);
225 
226     // Not using WriteBufferBytesFree() because non-deferred writes (which
227     // is this method) only use the first flash_write_size_bytes_ of the write
228     // buffer.
229     size_t buffer_remaining = flash_write_size_bytes_ - bytes_in_buffer;
230 
231     // Add bytes up to filling the flash write size.
232     size_t add_bytes = std::min(buffer_remaining, data.size_bytes());
233     std::memcpy(write_buffer_.data() + bytes_in_buffer, data.data(), add_bytes);
234     write_address_ += add_bytes;
235     bytes_in_buffer += add_bytes;
236     data = data.subspan(add_bytes);
237 
238     if (bytes_in_buffer != flash_write_size_bytes_) {
239       // If there was not enough bytes to finish filling the write buffer, there
240       // should not be any bytes left.
241       PW_DCHECK(data.size_bytes() == 0);
242       return OkStatus();
243     }
244 
245     // The write buffer is full, flush to flash.
246     if (!CommitToFlash(write_buffer_).ok()) {
247       return Status::DataLoss();
248     }
249 
250     PW_DCHECK(WriteBufferEmpty());
251   }
252 
253   // At this point, if data.size_bytes() > 0, the write buffer should be empty.
254   // This invariant is checked as part of of steps 2 & 3.
255 
256   // Step 2) Write as many block-sized chunks as the data has remaining after
257   //         step 1.
258   while (data.size_bytes() >= flash_write_size_bytes_) {
259     PW_DCHECK(WriteBufferEmpty());
260 
261     write_address_ += flash_write_size_bytes_;
262     if (!CommitToFlash(data.first(flash_write_size_bytes_)).ok()) {
263       return Status::DataLoss();
264     }
265 
266     data = data.subspan(flash_write_size_bytes_);
267   }
268 
269   // step 3) Put any remaining bytes to the buffer. Put the bytes starting at
270   //         the begining of the buffer, since it must be empty if there are
271   //         still bytes due to step 1 either cleaned out the buffer or didn't
272   //         have any more data to write.
273   if (data.size_bytes() > 0) {
274     PW_DCHECK(WriteBufferEmpty());
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 (!EraseIfNeeded().ok()) {
313     return Status::DataLoss();
314   }
315 
316   ByteSpan data = std::span(write_buffer_.data(), WriteBufferBytesUsed());
317   while (data.size_bytes() >= flash_write_size_bytes_) {
318     if (!CommitToFlash(data.first(flash_write_size_bytes_)).ok()) {
319       return Status::DataLoss();
320     }
321 
322     data = data.subspan(flash_write_size_bytes_);
323   }
324 
325   // Only a multiple of flash_write_size_bytes_ are written in the flush. Any
326   // remainder is held until later for either a flush with
327   // flash_write_size_bytes buffered or the writer is closed.
328   if (!WriteBufferEmpty()) {
329     PW_DCHECK_UINT_EQ(data.size_bytes(), WriteBufferBytesUsed());
330     // For any leftover bytes less than the flash write size, move them to the
331     // start of the bufer.
332     std::memmove(write_buffer_.data(), data.data(), data.size_bytes());
333   } else {
334     PW_DCHECK_UINT_EQ(data.size_bytes(), 0);
335   }
336 
337   return OkStatus();
338 }
339 
FlushFinalPartialChunk()340 Status BlobStore::FlushFinalPartialChunk() {
341   size_t bytes_in_buffer = WriteBufferBytesUsed();
342 
343   PW_DCHECK_UINT_GT(bytes_in_buffer, 0);
344   PW_DCHECK_UINT_LE(bytes_in_buffer, flash_write_size_bytes_);
345   PW_DCHECK_UINT_LE(flash_write_size_bytes_, WriteBytesRemaining());
346 
347   PW_LOG_DEBUG(
348       "  Remainder %u bytes in write buffer to zero-pad to flash write "
349       "size and commit",
350       static_cast<unsigned>(bytes_in_buffer));
351 
352   // Zero out the remainder of the buffer.
353   auto zero_span = write_buffer_.subspan(bytes_in_buffer);
354   std::memset(zero_span.data(),
355               static_cast<int>(partition_.erased_memory_content()),
356               zero_span.size_bytes());
357 
358   ConstByteSpan remaining_bytes = write_buffer_.first(flash_write_size_bytes_);
359   return CommitToFlash(remaining_bytes, bytes_in_buffer);
360 }
361 
CommitToFlash(ConstByteSpan source,size_t data_bytes)362 Status BlobStore::CommitToFlash(ConstByteSpan source, size_t data_bytes) {
363   if (data_bytes == 0) {
364     data_bytes = source.size_bytes();
365   }
366   flash_erased_ = false;
367   StatusWithSize result = partition_.Write(flash_address_, source);
368   flash_address_ += data_bytes;
369   if (checksum_algo_ != nullptr) {
370     checksum_algo_->Update(source.first(data_bytes));
371   }
372 
373   if (!result.status().ok()) {
374     valid_data_ = false;
375   }
376 
377   return result.status();
378 }
379 
380 // Needs to be in .cc file since PW_CHECK doesn't like being in .h files.
WriteBufferBytesUsed() const381 size_t BlobStore::WriteBufferBytesUsed() const {
382   PW_CHECK_UINT_GE(write_address_, flash_address_);
383   return write_address_ - flash_address_;
384 }
385 
386 // Needs to be in .cc file since PW_DCHECK doesn't like being in .h files.
WriteBufferBytesFree() const387 size_t BlobStore::WriteBufferBytesFree() const {
388   PW_DCHECK_UINT_GE(write_buffer_.size_bytes(), WriteBufferBytesUsed());
389   size_t buffer_remaining = write_buffer_.size_bytes() - WriteBufferBytesUsed();
390   return std::min(buffer_remaining, WriteBytesRemaining());
391 }
392 
EraseIfNeeded()393 Status BlobStore::EraseIfNeeded() {
394   if (flash_address_ == 0) {
395     // Always just erase. Erase is smart enough to only erase if needed.
396     return Erase();
397   }
398   return OkStatus();
399 }
400 
Read(size_t offset,ByteSpan dest) const401 StatusWithSize BlobStore::Read(size_t offset, ByteSpan dest) const {
402   if (!ValidToRead()) {
403     return StatusWithSize::FailedPrecondition();
404   }
405   if (offset >= ReadableDataBytes()) {
406     return StatusWithSize::OutOfRange();
407   }
408 
409   size_t available_bytes = ReadableDataBytes() - offset;
410   size_t read_size = std::min(available_bytes, dest.size_bytes());
411 
412   return partition_.Read(offset, dest.first(read_size));
413 }
414 
GetMemoryMappedBlob() const415 Result<ConstByteSpan> BlobStore::GetMemoryMappedBlob() const {
416   if (!ValidToRead()) {
417     return Status::FailedPrecondition();
418   }
419 
420   std::byte* mcu_address = partition_.PartitionAddressToMcuAddress(0);
421   if (mcu_address == nullptr) {
422     return Status::Unimplemented();
423   }
424   return ConstByteSpan(mcu_address, ReadableDataBytes());
425 }
426 
ReadableDataBytes() const427 size_t BlobStore::ReadableDataBytes() const {
428   // TODO: clean up state related to readable bytes.
429   return flash_address_;
430 }
431 
Erase()432 Status BlobStore::Erase() {
433   // If already erased our work here is done.
434   if (flash_erased_) {
435     // The write buffer might already have bytes when this call happens, due to
436     // a deferred write.
437     PW_DCHECK_UINT_LE(write_address_, write_buffer_.size_bytes());
438     PW_DCHECK_UINT_EQ(flash_address_, 0);
439 
440     // Erased blobs should be valid as soon as the flash is erased. Even though
441     // there are 0 bytes written, they are valid.
442     PW_DCHECK(valid_data_);
443     return OkStatus();
444   }
445 
446   Invalidate();
447 
448   Status status = partition_.Erase();
449 
450   if (status.ok()) {
451     flash_erased_ = true;
452 
453     // Blob data is considered valid as soon as the flash is erased. Even though
454     // there are 0 bytes written, they are valid.
455     valid_data_ = true;
456   }
457   return status;
458 }
459 
Invalidate()460 Status BlobStore::Invalidate() {
461   metadata_.reset();
462 
463   // Blob data is considered if the flash is erased. Even though
464   // there are 0 bytes written, they are valid.
465   valid_data_ = flash_erased_;
466   ResetChecksum();
467   write_address_ = 0;
468   flash_address_ = 0;
469 
470   Status status = kvs_.Delete(MetadataKey());
471 
472   return (status.ok() || status.IsNotFound()) ? OkStatus() : Status::Internal();
473 }
474 
ValidateChecksum()475 Status BlobStore::ValidateChecksum() {
476   if (metadata_.data_size_bytes == 0) {
477     PW_LOG_INFO("Blob unable to validate checksum of an empty blob");
478     return Status::Unavailable();
479   }
480 
481   if (checksum_algo_ == nullptr) {
482     if (metadata_.checksum != 0) {
483       PW_LOG_ERROR(
484           "Blob invalid to have a checkum value with no checksum algo");
485       return Status::DataLoss();
486     }
487 
488     return OkStatus();
489   }
490 
491   PW_LOG_DEBUG("Validate checksum of 0x%08x in flash for blob of %u bytes",
492                static_cast<unsigned>(metadata_.checksum),
493                static_cast<unsigned>(metadata_.data_size_bytes));
494   PW_TRY(CalculateChecksumFromFlash(metadata_.data_size_bytes));
495 
496   Status status =
497       checksum_algo_->Verify(as_bytes(std::span(&metadata_.checksum, 1)));
498   PW_LOG_DEBUG("  checksum verify of %s", status.str());
499 
500   return status;
501 }
502 
CalculateChecksumFromFlash(size_t bytes_to_check)503 Status BlobStore::CalculateChecksumFromFlash(size_t bytes_to_check) {
504   if (checksum_algo_ == nullptr) {
505     return OkStatus();
506   }
507 
508   checksum_algo_->Reset();
509 
510   kvs::FlashPartition::Address address = 0;
511   const kvs::FlashPartition::Address end = bytes_to_check;
512 
513   constexpr size_t kReadBufferSizeBytes = 32;
514   std::array<std::byte, kReadBufferSizeBytes> buffer;
515   while (address < end) {
516     const size_t read_size = std::min(size_t(end - address), buffer.size());
517     PW_TRY(partition_.Read(address, std::span(buffer).first(read_size)));
518 
519     checksum_algo_->Update(buffer.data(), read_size);
520     address += read_size;
521   }
522 
523   // Safe to ignore the return from Finish, checksum_algo_ keeps the state
524   // information that it needs.
525   checksum_algo_->Finish();
526   return OkStatus();
527 }
528 
529 }  // namespace pw::blob_store
530