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