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 #pragma once 15 16 #include <span> 17 18 #include "pw_assert/light.h" 19 #include "pw_kvs/checksum.h" 20 #include "pw_kvs/flash_memory.h" 21 #include "pw_kvs/key_value_store.h" 22 #include "pw_status/status.h" 23 #include "pw_stream/stream.h" 24 25 namespace pw::blob_store { 26 27 // BlobStore is a storage container for a single blob of data. BlobStore is a 28 // FlashPartition-backed persistent storage system with integrated data 29 // integrity checking that serves as a lightweight alternative to a file 30 // system. 31 // 32 // Write and read are only done using the BlobWriter and BlobReader classes. 33 // 34 // Once a blob write is closed, reopening to write will discard the previous 35 // blob. 36 // 37 // Write blob: 38 // 0) Create BlobWriter instance 39 // 1) BlobWriter::Open(). 40 // 2) Add data using BlobWriter::Write(). 41 // 3) BlobWriter::Close(). 42 // 43 // Read blob: 44 // 0) Create BlobReader instance 45 // 1) BlobReader::Open(). 46 // 2) Read data using BlobReader::Read() or 47 // BlobReader::GetMemoryMappedBlob(). 48 // 3) BlobReader::Close(). 49 class BlobStore { 50 public: 51 // Implement the stream::Writer and erase interface for a BlobStore. If not 52 // already erased, the Write will do any needed erase. 53 // 54 // Only one writter (of either type) is allowed to be open at a time. 55 // Additionally, writters are unable to open if a reader is already open. 56 class BlobWriter : public stream::Writer { 57 public: BlobWriter(BlobStore & store)58 constexpr BlobWriter(BlobStore& store) : store_(store), open_(false) {} 59 BlobWriter(const BlobWriter&) = delete; 60 BlobWriter& operator=(const BlobWriter&) = delete; ~BlobWriter()61 virtual ~BlobWriter() { 62 if (open_) { 63 Close(); 64 } 65 } 66 67 // Open a blob for writing/erasing. Open will invalidate any existing blob 68 // that may be stored. Can not open when already open. Only one writer is 69 // allowed to be open at a time. Returns: 70 // 71 // OK - success. 72 // UNAVAILABLE - Unable to open, another writer or reader instance is 73 // already open. Open()74 Status Open() { 75 PW_DASSERT(!open_); 76 Status status = store_.OpenWrite(); 77 if (status.ok()) { 78 open_ = true; 79 } 80 return status; 81 } 82 83 // Finalize a blob write. Flush all remaining buffered data to storage and 84 // store blob metadata. Close fails in the closed state, do NOT retry Close 85 // on error. An error may or may not result in an invalid blob stored. 86 // Returns: 87 // 88 // OK - success. 89 // DATA_LOSS - Error writing data or fail to verify written data. Close()90 Status Close() { 91 PW_DASSERT(open_); 92 open_ = false; 93 return store_.CloseWrite(); 94 } 95 IsOpen()96 bool IsOpen() { return open_; } 97 98 // Erase the blob partition and reset state for a new blob. Explicit calls 99 // to Erase are optional, beginning a write will do any needed Erase. 100 // Returns: 101 // 102 // OK - success. 103 // UNAVAILABLE - Unable to erase while reader is open. 104 // [error status] - flash erase failed. Erase()105 Status Erase() { 106 PW_DASSERT(open_); 107 return store_.Erase(); 108 } 109 110 // Discard the current blob. Any written bytes to this point are considered 111 // invalid. Returns: 112 // 113 // OK - success. 114 // FAILED_PRECONDITION - not open. Discard()115 Status Discard() { 116 PW_DASSERT(open_); 117 return store_.Invalidate(); 118 } 119 120 // Probable (not guaranteed) minimum number of bytes at this time that can 121 // be written. This is not necessarily the full number of bytes remaining in 122 // the blob. Returns zero if, in the current state, Write would return 123 // status other than OK. See stream.h for additional details. ConservativeWriteLimit()124 size_t ConservativeWriteLimit() const override { 125 PW_DASSERT(open_); 126 return store_.WriteBytesRemaining(); 127 } 128 CurrentSizeBytes()129 size_t CurrentSizeBytes() { 130 PW_DASSERT(open_); 131 return store_.write_address_; 132 } 133 134 protected: DoWrite(ConstByteSpan data)135 Status DoWrite(ConstByteSpan data) override { 136 PW_DASSERT(open_); 137 return store_.Write(data); 138 } 139 140 BlobStore& store_; 141 bool open_; 142 }; 143 144 // Implement the stream::Writer and erase interface with deferred action for a 145 // BlobStore. If not already erased, the Flush will do any needed erase. 146 // 147 // Only one writter (of either type) is allowed to be open at a time. 148 // Additionally, writters are unable to open if a reader is already open. 149 class DeferredWriter final : public BlobWriter { 150 public: DeferredWriter(BlobStore & store)151 constexpr DeferredWriter(BlobStore& store) : BlobWriter(store) {} 152 DeferredWriter(const DeferredWriter&) = delete; 153 DeferredWriter& operator=(const DeferredWriter&) = delete; ~DeferredWriter()154 virtual ~DeferredWriter() {} 155 156 // Flush data in the write buffer. Only a multiple of flash_write_size_bytes 157 // are written in the flush. Any remainder is held until later for either 158 // a flush with flash_write_size_bytes buffered or the writer is closed. Flush()159 Status Flush() { 160 PW_DASSERT(open_); 161 return store_.Flush(); 162 } 163 164 // Probable (not guaranteed) minimum number of bytes at this time that can 165 // be written. This is not necessarily the full number of bytes remaining in 166 // the blob. Returns zero if, in the current state, Write would return 167 // status other than OK. See stream.h for additional details. ConservativeWriteLimit()168 size_t ConservativeWriteLimit() const override { 169 PW_DASSERT(open_); 170 // Deferred writes need to fit in the write buffer. 171 return store_.WriteBufferBytesFree(); 172 } 173 174 private: DoWrite(ConstByteSpan data)175 Status DoWrite(ConstByteSpan data) override { 176 PW_DASSERT(open_); 177 return store_.AddToWriteBuffer(data); 178 } 179 }; 180 181 // Implement stream::Reader interface for BlobStore. Multiple readers may be 182 // open at the same time, but readers may not be open with a writer open. 183 class BlobReader final : public stream::Reader { 184 public: BlobReader(BlobStore & store)185 constexpr BlobReader(BlobStore& store) 186 : store_(store), open_(false), offset_(0) {} 187 BlobReader(const BlobReader&) = delete; 188 BlobReader& operator=(const BlobReader&) = delete; ~BlobReader()189 ~BlobReader() { 190 if (open_) { 191 Close(); 192 } 193 } 194 195 // Open to do a blob read at the given offset in to the blob. Can not open 196 // when already open. Multiple readers can be open at the same time. 197 // Returns: 198 // 199 // OK - success. 200 // FAILED_PRECONDITION - No readable blob available. 201 // INVALID_ARGUMENT - Invalid offset. 202 // UNAVAILABLE - Unable to open, already open. 203 Status Open(size_t offset = 0) { 204 PW_DASSERT(!open_); 205 if (!store_.ValidToRead()) { 206 return Status::FailedPrecondition(); 207 } 208 if (offset >= store_.ReadableDataBytes()) { 209 return Status::InvalidArgument(); 210 } 211 212 offset_ = offset; 213 Status status = store_.OpenRead(); 214 if (status.ok()) { 215 open_ = true; 216 } 217 return status; 218 } 219 220 // Finish reading a blob. Close fails in the closed state, do NOT retry 221 // Close on error. Returns: 222 // 223 // OK - success. Close()224 Status Close() { 225 PW_DASSERT(open_); 226 open_ = false; 227 return store_.CloseRead(); 228 } 229 IsOpen()230 bool IsOpen() { return open_; } 231 232 // Probable (not guaranteed) minimum number of bytes at this time that can 233 // be read. Returns zero if, in the current state, Read would return status 234 // other than OK. See stream.h for additional details. ConservativeReadLimit()235 size_t ConservativeReadLimit() const override { 236 PW_DASSERT(open_); 237 return store_.ReadableDataBytes() - offset_; 238 } 239 240 // Get a span with the MCU pointer and size of the data. Returns: 241 // 242 // OK with span - Valid span respresenting the blob data 243 // FAILED_PRECONDITION - Reader not open. 244 // UNIMPLEMENTED - Memory mapped access not supported for this blob. GetMemoryMappedBlob()245 Result<ConstByteSpan> GetMemoryMappedBlob() { 246 PW_DASSERT(open_); 247 return store_.GetMemoryMappedBlob(); 248 } 249 250 private: DoRead(ByteSpan dest)251 StatusWithSize DoRead(ByteSpan dest) override { 252 PW_DASSERT(open_); 253 StatusWithSize status = store_.Read(offset_, dest); 254 if (status.ok()) { 255 offset_ += status.size(); 256 } 257 return status; 258 } 259 260 BlobStore& store_; 261 bool open_; 262 size_t offset_; 263 }; 264 265 // BlobStore 266 // name - Name of blob store, used for metadata KVS key 267 // partition - Flash partiton to use for this blob. Blob uses the entire 268 // partition for blob data. 269 // checksum_algo - Optional checksum for blob integrity checking. Use nullptr 270 // for no check. 271 // kvs - KVS used for storing blob metadata. 272 // write_buffer - Used for buffering writes. Needs to be at least 273 // flash_write_size_bytes. 274 // flash_write_size_bytes - Size in bytes to use for flash write operations. 275 // This should be chosen to balance optimal write size and required buffer 276 // size. Must be greater than or equal to flash write alignment, less than 277 // or equal to flash sector size. BlobStore(std::string_view name,kvs::FlashPartition & partition,kvs::ChecksumAlgorithm * checksum_algo,kvs::KeyValueStore & kvs,ByteSpan write_buffer,size_t flash_write_size_bytes)278 BlobStore(std::string_view name, 279 kvs::FlashPartition& partition, 280 kvs::ChecksumAlgorithm* checksum_algo, 281 kvs::KeyValueStore& kvs, 282 ByteSpan write_buffer, 283 size_t flash_write_size_bytes) 284 : name_(name), 285 partition_(partition), 286 checksum_algo_(checksum_algo), 287 kvs_(kvs), 288 write_buffer_(write_buffer), 289 flash_write_size_bytes_(flash_write_size_bytes), 290 initialized_(false), 291 valid_data_(false), 292 flash_erased_(false), 293 writer_open_(false), 294 readers_open_(0), 295 metadata_({}), 296 write_address_(0), 297 flash_address_(0) {} 298 299 BlobStore(const BlobStore&) = delete; 300 BlobStore& operator=(const BlobStore&) = delete; 301 302 // Initialize the blob instance. Checks if storage is erased or has any stored 303 // blob data. Returns: 304 // 305 // OK - success. 306 Status Init(); 307 308 // Maximum number of data bytes this BlobStore is able to store. 309 size_t MaxDataSizeBytes() const; 310 311 private: 312 typedef uint32_t ChecksumValue; 313 314 Status LoadMetadata(); 315 316 // Open to do a blob write. Returns: 317 // 318 // OK - success. 319 // UNAVAILABLE - Unable to open writer, another writer or reader instance is 320 // already open. 321 Status OpenWrite(); 322 323 // Open to do a blob read. Returns: 324 // 325 // OK - success. 326 // FAILED_PRECONDITION - Unable to open, no valid blob available. 327 Status OpenRead(); 328 329 // Finalize a blob write. Flush all remaining buffered data to storage and 330 // store blob metadata. Returns: 331 // 332 // OK - success, valid complete blob. 333 // DATA_LOSS - Error during write (this close or previous write/flush). Blob 334 // is closed and marked as invalid. 335 Status CloseWrite(); 336 Status CloseRead(); 337 338 // Write/append data to the in-progress blob write. Data is written 339 // sequentially, with each append added directly after the previous. Data is 340 // not guaranteed to be fully written out to storage on Write return. Returns: 341 // 342 // OK - successful write/enqueue of data. 343 // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No 344 // data written. 345 // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written, 346 // no more will be written. 347 // DATA_LOSS - Error during write (this write or previous write/flush). No 348 // more will be written by following Write calls for current blob (until 349 // erase/new blob started). 350 Status Write(ConstByteSpan data); 351 352 // Similar to Write, but instead immediately writing out to flash, it only 353 // buffers the data. A flush or Close is reqired to get bytes writen out to 354 // flash 355 // 356 // OK - successful write/enqueue of data. 357 // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No 358 // data written. 359 // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written, 360 // no more will be written. 361 // DATA_LOSS - Error during a previous write/flush. No more will be written by 362 // following Write calls for current blob (until erase/new blob started). 363 Status AddToWriteBuffer(ConstByteSpan data); 364 365 // Flush data in the write buffer. Only a multiple of flash_write_size_bytes 366 // are written in the flush. Any remainder is held until later for either a 367 // flush with flash_write_size_bytes buffered or the writer is closed. 368 // 369 // OK - successful write/enqueue of data. 370 // DATA_LOSS - Error during write (this flush or previous write/flush). No 371 // more will be written by following Write calls for current blob (until 372 // erase/new blob started). 373 Status Flush(); 374 375 // Flush a chunk of data in the write buffer smaller than 376 // flash_write_size_bytes. This is only for the final flush as part of the 377 // CloseWrite. The partial chunk is padded to flash_write_size_bytes and a 378 // flash_write_size_bytes chunk is written to flash. 379 // 380 // OK - successful write/enqueue of data. 381 // DATA_LOSS - Error during write (this flush or previous write/flush). No 382 // more will be written by following Write calls for current blob (until 383 // erase/new blob started). 384 Status FlushFinalPartialChunk(); 385 386 // Commit data to flash and update flash_address_ with data bytes written. The 387 // only time data_bytes should be manually specified is for a CloseWrite with 388 // an unaligned-size chunk remaining in the buffer that has been zero padded 389 // to alignment. 390 Status CommitToFlash(ConstByteSpan source, size_t data_bytes = 0); 391 392 // Blob is valid/OK to write to. Blob is considered valid to write if no data 393 // has been written due to the auto/implicit erase on write start. 394 // 395 // true - Blob is valid and OK to write to. 396 // false - Blob has previously had an error and not valid for writing new 397 // data. ValidToWrite()398 bool ValidToWrite() { return (valid_data_ == true) || (write_address_ == 0); } 399 WriteBufferEmpty()400 bool WriteBufferEmpty() const { return flash_address_ == write_address_; } 401 402 size_t WriteBufferBytesUsed() const; 403 404 size_t WriteBufferBytesFree() const; 405 406 Status EraseIfNeeded(); 407 408 // Blob is valid/OK and has data to read. ValidToRead()409 bool ValidToRead() const { return (valid_data_ && ReadableDataBytes() > 0); } 410 411 // Read valid data. Attempts to read the lesser of output.size_bytes() or 412 // available bytes worth of data. Returns: 413 // 414 // OK with span of bytes read - success, between 1 and dest.size_bytes() were 415 // read. 416 // INVALID_ARGUMENT - offset is invalid. 417 // FAILED_PRECONDITION - Reader unable/not in state to read data. 418 // RESOURCE_EXHAUSTED - unable to read any bytes at this time. No bytes read. 419 // Try again once bytes become available. 420 // OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes read, no 421 // more will be read. 422 StatusWithSize Read(size_t offset, ByteSpan dest) const; 423 424 // Get a span with the MCU pointer and size of the data. Returns: 425 // 426 // OK with span - Valid span respresenting the blob data 427 // FAILED_PRECONDITION - Blob not in a state to read data 428 // UNIMPLEMENTED - Memory mapped access not supported for this blob. 429 Result<ConstByteSpan> GetMemoryMappedBlob() const; 430 431 // Size of blob/readable data, in bytes. 432 size_t ReadableDataBytes() const; 433 WriteBytesRemaining()434 size_t WriteBytesRemaining() const { 435 return MaxDataSizeBytes() - write_address_; 436 } 437 438 Status Erase(); 439 440 Status Invalidate(); 441 ResetChecksum()442 void ResetChecksum() { 443 if (checksum_algo_ != nullptr) { 444 checksum_algo_->Reset(); 445 } 446 } 447 448 Status ValidateChecksum(); 449 450 Status CalculateChecksumFromFlash(size_t bytes_to_check); 451 MetadataKey()452 const std::string_view MetadataKey() { return name_; } 453 454 // Changes to the metadata format should also get a different key signature to 455 // avoid new code improperly reading old format metadata. 456 struct BlobMetadata { 457 // The checksum of the blob data stored in flash. 458 ChecksumValue checksum; 459 460 // Number of blob data bytes stored in flash. 461 size_t data_size_bytes; 462 resetBlobMetadata463 constexpr void reset() { 464 *this = { 465 .checksum = 0, 466 .data_size_bytes = 0, 467 }; 468 } 469 }; 470 471 std::string_view name_; 472 kvs::FlashPartition& partition_; 473 // checksum_algo_ of nullptr indicates no checksum algorithm. 474 kvs::ChecksumAlgorithm* const checksum_algo_; 475 kvs::KeyValueStore& kvs_; 476 ByteSpan write_buffer_; 477 478 // Size in bytes of flash write operations. This should be chosen to balance 479 // optimal write size and required buffer size. Must be GE flash write 480 // alignment, LE flash sector size. 481 const size_t flash_write_size_bytes_; 482 483 // 484 // Internal state for Blob store 485 // 486 // TODO: Consolidate blob state to a single struct 487 488 // Initialization has been done. 489 bool initialized_; 490 491 // Bytes stored are valid and good. Blob is OK to read and write to. Set as 492 // soon as blob is erased. Even when bytes written is still 0, they are valid. 493 bool valid_data_; 494 495 // Blob partition is currently erased and ready to write a new blob. 496 bool flash_erased_; 497 498 // BlobWriter instance is currently open 499 bool writer_open_; 500 501 // Count of open BlobReader instances 502 size_t readers_open_; 503 504 // Metadata for the blob. 505 BlobMetadata metadata_; 506 507 // Current index for end of overal blob data. Represents current byte size of 508 // blob data since the FlashPartition starts at address 0. 509 kvs::FlashPartition::Address write_address_; 510 511 // Current index of end of data written to flash. Number of buffered data 512 // bytes is write_address_ - flash_address_. 513 kvs::FlashPartition::Address flash_address_; 514 }; 515 516 // Creates a BlobStore with the buffer of kBufferSizeBytes. 517 // 518 // kBufferSizeBytes - Size in bytes of write buffer to create. 519 // name - Name of blob store, used for metadata KVS key 520 // partition - Flash partiton to use for this blob. Blob uses the entire 521 // partition for blob data. 522 // checksum_algo - Optional checksum for blob integrity checking. Use nullptr 523 // for no check. 524 // kvs - KVS used for storing blob metadata. 525 // write_buffer - Used for buffering writes. Needs to be at least 526 // flash_write_size_bytes. 527 // flash_write_size_bytes - Size in bytes to use for flash write operations. 528 // This should be chosen to balance optimal write size and required buffer 529 // size. Must be greater than or equal to flash write alignment, less than 530 // or equal to flash sector size. 531 532 template <size_t kBufferSizeBytes> 533 class BlobStoreBuffer : public BlobStore { 534 public: BlobStoreBuffer(std::string_view name,kvs::FlashPartition & partition,kvs::ChecksumAlgorithm * checksum_algo,kvs::KeyValueStore & kvs,size_t flash_write_size_bytes)535 explicit BlobStoreBuffer(std::string_view name, 536 kvs::FlashPartition& partition, 537 kvs::ChecksumAlgorithm* checksum_algo, 538 kvs::KeyValueStore& kvs, 539 size_t flash_write_size_bytes) 540 : BlobStore(name, 541 partition, 542 checksum_algo, 543 kvs, 544 buffer_, 545 flash_write_size_bytes) {} 546 547 private: 548 std::array<std::byte, kBufferSizeBytes> buffer_; 549 }; 550 551 } // namespace pw::blob_store 552