| // Copyright 2020 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| #pragma once |
| |
| #include <cstddef> |
| |
| #include "pw_assert/assert.h" |
| #include "pw_blob_store/internal/metadata_format.h" |
| #include "pw_bytes/span.h" |
| #include "pw_kvs/checksum.h" |
| #include "pw_kvs/flash_memory.h" |
| #include "pw_kvs/key_value_store.h" |
| #include "pw_span/span.h" |
| #include "pw_status/status.h" |
| #include "pw_status/status_with_size.h" |
| #include "pw_status/try.h" |
| #include "pw_stream/seek.h" |
| #include "pw_stream/stream.h" |
| #include "pw_sync/borrow.h" |
| |
| namespace pw::blob_store { |
| |
| // BlobStore is a storage container for a single blob of data. BlobStore is |
| // a FlashPartition-backed persistent storage system with integrated data |
| // integrity checking that serves as a lightweight alternative to a file |
| // system. |
| // |
| // Write and read are only done using the BlobWriter and BlobReader classes. |
| // |
| // Once a blob write is closed, reopening to write will discard the previous |
| // blob. |
| // |
| // Write blob: |
| // 0) Create BlobWriter instance |
| // 1) BlobWriter::Open(). |
| // 2) Add data using BlobWriter::Write(). |
| // 3) BlobWriter::Close(). |
| // |
| // Read blob: |
| // 0) Create BlobReader instance |
| // 1) BlobReader::Open(). |
| // 2) Read data using BlobReader::Read() or |
| // BlobReader::GetMemoryMappedBlob(). |
| // 3) BlobReader::Close(). |
| class BlobStore { |
| public: |
| // Implement the stream::Writer and erase interface for a BlobStore. If not |
| // already erased, the Write will do any needed erase. |
| // |
| // Only one writter (of either type) is allowed to be open at a time. |
| // Additionally, writers are unable to open if a reader is already open. |
| class BlobWriter : public stream::NonSeekableWriter { |
| public: |
| constexpr BlobWriter(BlobStore& store, ByteSpan metadata_buffer) |
| : store_(store), metadata_buffer_(metadata_buffer), open_(false) {} |
| BlobWriter(const BlobWriter&) = delete; |
| BlobWriter& operator=(const BlobWriter&) = delete; |
| ~BlobWriter() override { |
| if (open_) { |
| Close().IgnoreError(); // TODO(b/242598609): Handle Status properly |
| } |
| } |
| |
| static constexpr size_t RequiredMetadataBufferSize( |
| size_t max_file_name_size) { |
| return max_file_name_size + sizeof(internal::BlobMetadataHeader); |
| } |
| |
| // Open a blob for writing/erasing. Open will invalidate any existing blob |
| // that may be stored, and will not retain the previous file name. Can not |
| // open when already open. Only one writer is allowed to be open at a time. |
| // Returns: |
| // |
| // Preconditions: |
| // This writer must not already be open. |
| // This writer's metadata encode buffer must be at least the size of |
| // internal::BlobMetadataHeader. |
| // |
| // OK - success. |
| // UNAVAILABLE - Unable to open, another writer or reader instance is |
| // already open. |
| Status Open(); |
| |
| // Finalize a blob write. Flush all remaining buffered data to storage and |
| // store blob metadata. Close fails in the closed state, do NOT retry Close |
| // on error. An error may or may not result in an invalid blob stored. |
| // Returns: |
| // |
| // OK - success. |
| // DATA_LOSS - Error writing data or fail to verify written data. |
| Status Close(); |
| |
| bool IsOpen() { return open_; } |
| |
| // Erase the blob partition and reset state for a new blob. Explicit calls |
| // to Erase are optional, beginning a write will do any needed Erase. |
| // Returns: |
| // |
| // OK - success. |
| // UNAVAILABLE - Unable to erase while reader is open. |
| // [error status] - flash erase failed. |
| Status Erase() { |
| return open_ ? store_.Erase() : Status::FailedPrecondition(); |
| } |
| |
| // Discard the current blob. Any written bytes to this point are considered |
| // invalid. Returns: |
| // |
| // OK - success. |
| // FAILED_PRECONDITION - not open. |
| Status Discard() { |
| return open_ ? store_.Invalidate() : Status::FailedPrecondition(); |
| } |
| |
| // Sets file name to be associated with the data written by this |
| // ``BlobWriter``. This may be changed any time before Close() is called. |
| // |
| // Calling Discard() or Erase() will clear any set file name. |
| // |
| // The underlying buffer behind file_name may be invalidated after this |
| // function returns as the string is copied to the internally managed encode |
| // buffer. |
| // |
| // Preconditions: |
| // This writer must be open. |
| // |
| // OK - successfully set file name. |
| // RESOURCE_EXHAUSTED - File name too large to fit in metadata encode |
| // buffer, file name not set. |
| Status SetFileName(std::string_view file_name); |
| |
| size_t CurrentSizeBytes() const { |
| return open_ ? store_.write_address_ : 0; |
| } |
| |
| // Max file name length, not including null terminator (null terminators |
| // are not stored). |
| size_t MaxFileNameLength() { |
| return metadata_buffer_.size_bytes() < |
| sizeof(internal::BlobMetadataHeader) |
| ? 0 |
| : metadata_buffer_.size_bytes() - |
| sizeof(internal::BlobMetadataHeader); |
| } |
| |
| protected: |
| Status DoWrite(ConstByteSpan data) override { |
| return open_ ? store_.Write(data) : Status::FailedPrecondition(); |
| } |
| |
| // Commits changes to KVS as a BlobStore metadata entry. |
| Status WriteMetadata(); |
| |
| BlobStore& store_; |
| ByteSpan metadata_buffer_; |
| bool open_; |
| |
| private: |
| // Probable (not guaranteed) minimum number of bytes at this time that can |
| // be written. This is not necessarily the full number of bytes remaining in |
| // the blob. Returns zero if, in the current state, Write would return |
| // status other than OK. See stream.h for additional details. |
| size_t ConservativeLimit(LimitType limit) const override { |
| if (open_ && limit == LimitType::kWrite) { |
| return store_.WriteBytesRemaining(); |
| } |
| return 0; |
| } |
| }; |
| |
| template <size_t kMaxFileNameSize = 0> |
| class BlobWriterWithBuffer final : public BlobWriter { |
| public: |
| constexpr BlobWriterWithBuffer(BlobStore& store) |
| : BlobWriter(store, buffer_), buffer_() {} |
| |
| private: |
| std::array<std::byte, RequiredMetadataBufferSize(kMaxFileNameSize)> buffer_; |
| }; |
| |
| // Implement the stream::Writer and erase interface with deferred action for a |
| // BlobStore. If not already erased, the Flush will do any needed erase. |
| // |
| // Only one writter (of either type) is allowed to be open at a time. |
| // Additionally, writers are unable to open if a reader is already open. |
| class DeferredWriter : public BlobWriter { |
| public: |
| constexpr DeferredWriter(BlobStore& store, ByteSpan metadata_buffer) |
| : BlobWriter(store, metadata_buffer) {} |
| DeferredWriter(const DeferredWriter&) = delete; |
| DeferredWriter& operator=(const DeferredWriter&) = delete; |
| ~DeferredWriter() override {} |
| |
| // Flush data in the write buffer. Only a multiple of flash_write_size_bytes |
| // are written in the flush. Any remainder is held until later for either |
| // a flush with flash_write_size_bytes buffered or the writer is closed. |
| Status Flush() { |
| return open_ ? store_.Flush() : Status::FailedPrecondition(); |
| } |
| |
| // Probable (not guaranteed) minimum number of bytes at this time that can |
| // be written. This is not necessarily the full number of bytes remaining in |
| // the blob. Returns zero if, in the current state, Write would return |
| // status other than OK. See stream.h for additional details. |
| size_t ConservativeLimit(LimitType limit) const final { |
| if (open_ && limit == LimitType::kWrite) { |
| // Deferred writes need to fit in the write buffer. |
| return store_.WriteBufferBytesFree(); |
| } |
| return 0; |
| } |
| |
| private: |
| // Similar to normal Write, but instead immediately writing out to flash, |
| // it only buffers the data. A flush or Close is reqired to get bytes |
| // writen out to flash. |
| // |
| // AddToWriteBuffer will continue to accept new data after Flush has an |
| // erase error (buffer space permitting). Write errors during Flush will |
| // result in no new data being accepted. |
| Status DoWrite(ConstByteSpan data) final { |
| return open_ ? store_.AddToWriteBuffer(data) |
| : Status::FailedPrecondition(); |
| } |
| }; |
| |
| template <size_t kMaxFileNameSize = 0> |
| class DeferredWriterWithBuffer final : public DeferredWriter { |
| public: |
| constexpr DeferredWriterWithBuffer(BlobStore& store) |
| : DeferredWriter(store, buffer_), buffer_() {} |
| |
| private: |
| std::array<std::byte, RequiredMetadataBufferSize(kMaxFileNameSize)> buffer_; |
| }; |
| |
| // Implement stream::Reader interface for BlobStore. Multiple readers may be |
| // open at the same time, but readers may not be open with a writer open. |
| class BlobReader final : public stream::SeekableReader { |
| public: |
| constexpr BlobReader(BlobStore& store) |
| : store_(store), open_(false), offset_(0) {} |
| |
| BlobReader(const BlobReader&) = delete; |
| BlobReader& operator=(const BlobReader&) = delete; |
| |
| ~BlobReader() override { |
| if (open_) { |
| Close().IgnoreError(); |
| } |
| } |
| |
| // Open to do a blob read at the given offset in to the blob. Can not open |
| // when already open. Multiple readers can be open at the same time. |
| // Returns: |
| // |
| // OK - success. |
| // FAILED_PRECONDITION - No readable blob available. |
| // INVALID_ARGUMENT - Invalid offset. |
| // UNAVAILABLE - Unable to open, already open. |
| // |
| Status Open(size_t offset = 0); |
| |
| // Finish reading a blob. Close fails in the closed state, do NOT retry |
| // Close on error. Returns: |
| // |
| // OK - success |
| // FAILED_PRECONDITION - already closed |
| // |
| Status Close() { |
| if (!open_) { |
| return Status::FailedPrecondition(); |
| } |
| open_ = false; |
| return store_.CloseRead(); |
| } |
| |
| // Copies the file name of the stored data to `dest`, and returns the number |
| // of bytes written to the destination buffer. The string is not |
| // null-terminated. |
| // |
| // Returns: |
| // OK - File name copied, size contains file name length. |
| // RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains |
| // first N bytes of the file name. |
| // NOT_FOUND - No file name set for this blob. |
| // FAILED_PRECONDITION - not open |
| // |
| StatusWithSize GetFileName(span<char> dest) { |
| return open_ ? store_.GetFileName(dest) |
| : StatusWithSize::FailedPrecondition(); |
| } |
| |
| bool IsOpen() const { return open_; } |
| |
| // Get a span with the MCU pointer and size of the data. Returns: |
| // |
| // OK with span - Valid span respresenting the blob data |
| // FAILED_PRECONDITION - Reader not open. |
| // UNIMPLEMENTED - Memory mapped access not supported for this blob. |
| // FAILED_PRECONDITION - Writer is closed |
| // |
| Result<ConstByteSpan> GetMemoryMappedBlob() { |
| return open_ ? store_.GetMemoryMappedBlob() |
| : Status::FailedPrecondition(); |
| } |
| |
| private: |
| // Probable (not guaranteed) minimum number of bytes at this time that can |
| // be read. Returns zero if, in the current state, Read would return status |
| // other than OK. See stream.h for additional details. |
| size_t ConservativeLimit(LimitType limit) const override; |
| |
| size_t DoTell() override; |
| |
| Status DoSeek(ptrdiff_t offset, Whence origin) override; |
| |
| StatusWithSize DoRead(ByteSpan dest) override; |
| |
| BlobStore& store_; |
| bool open_; |
| size_t offset_; |
| }; |
| |
| // BlobStore |
| // name - Name of blob store, used for metadata KVS key |
| // partition - Flash partiton to use for this blob. Blob uses the entire |
| // partition for blob data. |
| // checksum_algo - Optional checksum for blob integrity checking. Use nullptr |
| // for no check. |
| // kvs - KVS used for storing blob metadata. |
| // write_buffer - Used for buffering writes. Needs to be at least |
| // flash_write_size_bytes. |
| // flash_write_size_bytes - Size in bytes to use for flash write operations. |
| // This should be chosen to balance optimal write size and required buffer |
| // size. Must be greater than or equal to flash write alignment, less than |
| // or equal to flash sector size. |
| BlobStore(std::string_view name, |
| kvs::FlashPartition& partition, |
| kvs::ChecksumAlgorithm* checksum_algo, |
| sync::Borrowable<kvs::KeyValueStore>& kvs, |
| ByteSpan write_buffer, |
| size_t flash_write_size_bytes) |
| : name_(name), |
| partition_(partition), |
| checksum_algo_(checksum_algo), |
| kvs_(kvs), |
| write_buffer_(write_buffer), |
| flash_write_size_bytes_(flash_write_size_bytes), |
| initialized_(false), |
| valid_data_(false), |
| flash_erased_(false), |
| writer_open_(false), |
| readers_open_(0), |
| write_address_(0), |
| flash_address_(0), |
| file_name_length_(0) {} |
| |
| BlobStore(const BlobStore&) = delete; |
| BlobStore& operator=(const BlobStore&) = delete; |
| |
| // Initialize the blob instance. Checks if storage is erased or has any stored |
| // blob data. Returns: |
| // |
| // OK - success. |
| Status Init(); |
| |
| // Maximum number of data bytes this BlobStore is able to store. |
| size_t MaxDataSizeBytes() const; |
| |
| // Get the current data state of the blob without needing to instantiate |
| // and/or open a reader or writer. This check is independent of any writers or |
| // readers of this blob that might exist (open or closed). |
| // |
| // NOTE: This state can be changed by any writer that is open(ed) for this |
| // blob. Readers can not be opened until any open writers are closed. |
| // |
| // true - Blob is valid/OK and has at least 1 data byte. |
| // false - Blob is either invalid or does not have any data bytes |
| bool HasData() const { return (valid_data_ && ReadableDataBytes() > 0); } |
| |
| private: |
| Status LoadMetadata(); |
| |
| // Open to do a blob write. Returns: |
| // |
| // OK - success. |
| // UNAVAILABLE - Unable to open writer, another writer or reader instance is |
| // already open. |
| Status OpenWrite(); |
| |
| // Open to do a blob read. Returns: |
| // |
| // OK - success. |
| // FAILED_PRECONDITION - Unable to open, no valid blob available. |
| Status OpenRead(); |
| |
| // Finalize a blob write. Flush all remaining buffered data to storage and |
| // store blob metadata. Returns: |
| // |
| // OK - success, valid complete blob. |
| // DATA_LOSS - Error during write (this close or previous write/flush). Blob |
| // is closed and marked as invalid. |
| Status CloseRead(); |
| |
| // Write/append data to the in-progress blob write. Data is written |
| // sequentially, with each append added directly after the previous. Data is |
| // not guaranteed to be fully written out to storage on Write return. Returns: |
| // |
| // OK - successful write/enqueue of data. |
| // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No |
| // data written. |
| // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written, |
| // no more will be written. |
| // DATA_LOSS - Error during write (this write or previous write/flush). No |
| // more will be written by following Write calls for current blob (until |
| // erase/new blob started). |
| Status Write(ConstByteSpan data); |
| |
| // Similar to Write, but instead immediately writing out to flash, it only |
| // buffers the data. A flush or Close is reqired to get bytes writen out to |
| // flash. |
| // |
| // AddToWriteBuffer will continue to accept new data after Flush has an erase |
| // error (buffer space permitting). Write errors during Flush will result in |
| // no new data being accepted. |
| // |
| // OK - successful write/enqueue of data. |
| // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No |
| // data written. |
| // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written, |
| // no more will be written. |
| // DATA_LOSS - Error during a previous write/flush. No more will be written by |
| // following Write calls for current blob (until erase/new blob started). |
| Status AddToWriteBuffer(ConstByteSpan data); |
| |
| // Flush data in the write buffer. Only a multiple of flash_write_size_bytes |
| // are written in the flush. Any remainder is held until later for either a |
| // flush with flash_write_size_bytes buffered or the writer is closed. |
| // |
| // OK - successful write/enqueue of data. |
| // DATA_LOSS - Error during write (this flush or previous write/flush). No |
| // more will be written by following Write calls for current blob (until |
| // erase/new blob started). |
| Status Flush(); |
| |
| // Flush a chunk of data in the write buffer smaller than |
| // flash_write_size_bytes. This is only for the final flush as part of the |
| // CloseWrite. The partial chunk is padded to flash_write_size_bytes and a |
| // flash_write_size_bytes chunk is written to flash. |
| // |
| // OK - successful write/enqueue of data. |
| // DATA_LOSS - Error during write (this flush or previous write/flush). No |
| // more will be written by following Write calls for current blob (until |
| // erase/new blob started). |
| Status FlushFinalPartialChunk(); |
| |
| // Commit data to flash and update flash_address_ with data bytes written. The |
| // only time data_bytes should be manually specified is for a CloseWrite with |
| // an unaligned-size chunk remaining in the buffer that has been zero padded |
| // to alignment. |
| Status CommitToFlash(ConstByteSpan source, size_t data_bytes = 0); |
| |
| // Blob is valid/OK to write to. Blob is considered valid to write if no data |
| // has been written due to the auto/implicit erase on write start. |
| // |
| // true - Blob is valid and OK to write to. |
| // false - Blob has previously had an error and not valid for writing new |
| // data. |
| bool ValidToWrite() { return (valid_data_ == true) || (flash_address_ == 0); } |
| |
| bool WriteBufferEmpty() const { return flash_address_ == write_address_; } |
| |
| size_t WriteBufferBytesUsed() const; |
| |
| size_t WriteBufferBytesFree() const; |
| |
| Status EraseIfNeeded(); |
| |
| // Read valid data. Attempts to read the lesser of output.size_bytes() or |
| // available bytes worth of data. Returns: |
| // |
| // OK with span of bytes read - success, between 1 and dest.size_bytes() were |
| // read. |
| // INVALID_ARGUMENT - offset is invalid. |
| // FAILED_PRECONDITION - Reader unable/not in state to read data. |
| // RESOURCE_EXHAUSTED - unable to read any bytes at this time. No bytes read. |
| // Try again once bytes become available. |
| // OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes read, no |
| // more will be read. |
| StatusWithSize Read(size_t offset, ByteSpan dest) const; |
| |
| // Get a span with the MCU pointer and size of the data. Returns: |
| // |
| // OK with span - Valid span respresenting the blob data |
| // FAILED_PRECONDITION - Blob not in a state to read data |
| // UNIMPLEMENTED - Memory mapped access not supported for this blob. |
| Result<ConstByteSpan> GetMemoryMappedBlob() const; |
| |
| // Size of blob/readable data, in bytes. |
| size_t ReadableDataBytes() const; |
| |
| size_t WriteBytesRemaining() const { |
| return MaxDataSizeBytes() - write_address_; |
| } |
| |
| Status Erase(); |
| |
| Status Invalidate(); |
| |
| void ResetChecksum() { |
| if (checksum_algo_ != nullptr) { |
| checksum_algo_->Reset(); |
| } |
| } |
| |
| Status ValidateChecksum(size_t blob_size_bytes, |
| internal::ChecksumValue expected); |
| |
| Status CalculateChecksumFromFlash(size_t bytes_to_check); |
| |
| const std::string_view MetadataKey() const { return name_; } |
| |
| // Copies the file name of the stored data to `dest`, and returns the number |
| // of bytes written to the destination buffer. The string is not |
| // null-terminated. |
| // |
| // Returns: |
| // OK - File name copied, size contains file name length. |
| // RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains |
| // first N bytes of the file name. |
| // NOT_FOUND - No file name set for this blob. |
| // FAILED_PRECONDITION - BlobStore has not been initialized. |
| StatusWithSize GetFileName(span<char> dest) const; |
| |
| std::string_view name_; |
| kvs::FlashPartition& partition_; |
| // checksum_algo_ of nullptr indicates no checksum algorithm. |
| kvs::ChecksumAlgorithm* const checksum_algo_; |
| sync::Borrowable<kvs::KeyValueStore>& kvs_; |
| ByteSpan write_buffer_; |
| |
| // Size in bytes of flash write operations. This should be chosen to balance |
| // optimal write size and required buffer size. Must be GE flash write |
| // alignment, LE flash sector size. |
| const size_t flash_write_size_bytes_; |
| |
| // |
| // Internal state for Blob store |
| // |
| // TODO: Consolidate blob state to a single struct |
| |
| // Initialization has been done. |
| bool initialized_; |
| |
| // Bytes stored are valid and good. Blob is OK to read and write to. Set as |
| // soon as blob is erased. Even when bytes written is still 0, they are valid. |
| bool valid_data_; |
| |
| // Blob partition is currently erased and ready to write a new blob. |
| bool flash_erased_; |
| |
| // BlobWriter instance is currently open |
| bool writer_open_; |
| |
| // Count of open BlobReader instances |
| size_t readers_open_; |
| |
| // Current index for end of overall blob data. Represents current byte size of |
| // blob data since the FlashPartition starts at address 0. |
| kvs::FlashPartition::Address write_address_; |
| |
| // Current index of end of data written to flash. Number of buffered data |
| // bytes is write_address_ - flash_address_. |
| kvs::FlashPartition::Address flash_address_; |
| |
| // Length of the stored blob's filename. |
| size_t file_name_length_; |
| }; |
| |
| // Creates a BlobStore with the buffer of kBufferSizeBytes. |
| // |
| // kBufferSizeBytes - Size in bytes of write buffer to create. |
| // name - Name of blob store, used for metadata KVS key |
| // partition - Flash partition to use for this blob. Blob uses the entire |
| // partition for blob data. |
| // checksum_algo - Optional checksum for blob integrity checking. Use nullptr |
| // for no check. |
| // kvs - KVS used for storing blob metadata. |
| // write_buffer - Used for buffering writes. Needs to be at least |
| // flash_write_size_bytes. |
| // flash_write_size_bytes - Size in bytes to use for flash write operations. |
| // This should be chosen to balance optimal write size and required buffer |
| // size. Must be greater than or equal to flash write alignment, less than |
| // or equal to flash sector size. |
| |
| template <size_t kBufferSizeBytes> |
| class BlobStoreBuffer : public BlobStore { |
| public: |
| explicit BlobStoreBuffer(std::string_view name, |
| kvs::FlashPartition& partition, |
| kvs::ChecksumAlgorithm* checksum_algo, |
| sync::Borrowable<kvs::KeyValueStore>& kvs, |
| size_t flash_write_size_bytes) |
| : BlobStore(name, |
| partition, |
| checksum_algo, |
| kvs, |
| buffer_, |
| flash_write_size_bytes) {} |
| |
| private: |
| std::array<std::byte, kBufferSizeBytes> buffer_; |
| }; |
| |
| } // namespace pw::blob_store |