// 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. #include "pw_kvs/internal/entry.h" #include #include #include "pw_kvs_private/macros.h" #include "pw_log/log.h" namespace pw::kvs::internal { using std::byte; using std::string_view; Status Entry::Read(FlashPartition& partition, Address address, const internal::EntryFormats& formats, Entry* entry) { EntryHeader header; TRY(partition.Read(address, sizeof(header), &header)); if (partition.AppearsErased(as_bytes(span(&header.magic, 1)))) { return Status::NOT_FOUND; } if (header.key_length_bytes > kMaxKeyLength) { return Status::DATA_LOSS; } const EntryFormat* format = formats.Find(header.magic); if (format == nullptr) { PW_LOG_ERROR("Found corrupt magic: %" PRIx32 " at address %zx", header.magic, size_t(address)); return Status::DATA_LOSS; } *entry = Entry(&partition, address, *format, header); return Status::OK; } Status Entry::ReadKey(FlashPartition& partition, Address address, size_t key_length, char* key) { if (key_length == 0u || key_length > kMaxKeyLength) { return Status::DATA_LOSS; } return partition.Read(address + sizeof(EntryHeader), key_length, key) .status(); } Entry::Entry(FlashPartition& partition, Address address, const EntryFormat& format, string_view key, span value, uint16_t value_size_bytes, uint32_t transaction_id) : Entry(&partition, address, format, {.magic = format.magic, .checksum = 0, .alignment_units = alignment_bytes_to_units(partition.alignment_bytes()), .key_length_bytes = static_cast(key.size()), .value_size_bytes = value_size_bytes, .transaction_id = transaction_id}) { if (checksum_algo_ != nullptr) { span checksum = CalculateChecksum(key, value); std::memcpy(&header_.checksum, checksum.data(), std::min(checksum.size(), sizeof(header_.checksum))); } } StatusWithSize Entry::Write(string_view key, span value) const { FlashPartition::Output flash(partition(), address_); return AlignedWrite<64>( flash, alignment_bytes(), {as_bytes(span(&header_, 1)), as_bytes(span(key)), value}); } Status Entry::Update(const EntryFormat& new_format, uint32_t new_transaction_id) { checksum_algo_ = new_format.checksum; header_.magic = new_format.magic; header_.alignment_units = alignment_bytes_to_units(partition_->alignment_bytes()); header_.transaction_id = new_transaction_id; // If we could write the header last, we could avoid reading the entry twice // when moving an entry. However, to support alignments greater than the // header size, we first read the entire value to calculate the new checksum, // then write the full entry in WriteFrom. return CalculateChecksumFromFlash(); } StatusWithSize Entry::Copy(Address new_address) const { PW_LOG_DEBUG("Copying entry from 0x%x to 0x%x as ID %" PRIu32, unsigned(address()), unsigned(new_address), transaction_id()); FlashPartition::Output output(partition(), new_address); AlignedWriterBuffer<4 * kMinAlignmentBytes> writer(alignment_bytes(), output); // Use this object's header rather than the header in flash of flash, since // this Entry may have been updated. TRY_WITH_SIZE(writer.Write(&header_, sizeof(header_))); // Write only the key and value from the original entry. FlashPartition::Input input(partition(), address() + sizeof(EntryHeader)); TRY_WITH_SIZE(writer.Write(input, key_length() + value_size())); return writer.Flush(); } StatusWithSize Entry::ReadValue(span buffer, size_t offset_bytes) const { if (offset_bytes > value_size()) { return StatusWithSize::OUT_OF_RANGE; } const size_t remaining_bytes = value_size() - offset_bytes; const size_t read_size = std::min(buffer.size(), remaining_bytes); StatusWithSize result = partition().Read( address_ + sizeof(EntryHeader) + key_length() + offset_bytes, buffer.subspan(0, read_size)); TRY_WITH_SIZE(result); if (read_size != remaining_bytes) { return StatusWithSize(Status::RESOURCE_EXHAUSTED, read_size); } return StatusWithSize(read_size); } Status Entry::VerifyChecksum(string_view key, span value) const { if (checksum_algo_ == nullptr) { return header_.checksum == 0 ? Status::OK : Status::DATA_LOSS; } CalculateChecksum(key, value); return checksum_algo_->Verify(checksum_bytes()); } Status Entry::VerifyChecksumInFlash() const { // Read the entire entry piece-by-piece into a small buffer. If the entry is // 32 B or less, only one read is required. union { EntryHeader header_to_verify; byte buffer[sizeof(EntryHeader) * 2]; }; size_t bytes_to_read = size(); size_t read_size = std::min(sizeof(buffer), bytes_to_read); Address read_address = address_; // Read the first chunk, which includes the header, and compare the checksum. TRY(partition().Read(read_address, read_size, buffer)); if (header_to_verify.checksum != header_.checksum) { PW_LOG_ERROR("Expected checksum %08" PRIx32 ", found %08" PRIx32, header_.checksum, header_to_verify.checksum); return Status::DATA_LOSS; } if (checksum_algo_ == nullptr) { return header_.checksum == 0 ? Status::OK : Status::DATA_LOSS; } // The checksum is calculated as if the header's checksum field were 0. header_to_verify.checksum = 0; checksum_algo_->Reset(); while (true) { // Add the chunk in the buffer to the checksum. checksum_algo_->Update(buffer, read_size); bytes_to_read -= read_size; if (bytes_to_read == 0u) { break; } // Read the next chunk into the buffer. read_address += read_size; read_size = std::min(sizeof(buffer), bytes_to_read); TRY(partition().Read(read_address, read_size, buffer)); } checksum_algo_->Finish(); return checksum_algo_->Verify(checksum_bytes()); } void Entry::DebugLog() const { PW_LOG_DEBUG("Entry [%s]: ", deleted() ? "tombstone" : "present"); PW_LOG_DEBUG(" Address = 0x%zx", size_t(address_)); PW_LOG_DEBUG(" Transaction = %zu", size_t(transaction_id())); PW_LOG_DEBUG(" Magic = 0x%zx", size_t(magic())); PW_LOG_DEBUG(" Checksum = 0x%zx", size_t(header_.checksum)); PW_LOG_DEBUG(" Key length = 0x%zx", size_t(key_length())); PW_LOG_DEBUG(" Value length = 0x%zx", size_t(value_size())); PW_LOG_DEBUG(" Entry size = 0x%zx", size_t(size())); PW_LOG_DEBUG(" Alignment = 0x%zx", size_t(alignment_bytes())); } span Entry::CalculateChecksum(const string_view key, span value) const { checksum_algo_->Reset(); { EntryHeader header_for_checksum = header_; header_for_checksum.checksum = 0; checksum_algo_->Update(&header_for_checksum, sizeof(header_for_checksum)); checksum_algo_->Update(as_bytes(span(key))); checksum_algo_->Update(value); } // Update the checksum with 0s to pad the entry to its alignment boundary. constexpr byte padding[kMinAlignmentBytes - 1] = {}; size_t padding_to_add = Padding(content_size(), alignment_bytes()); while (padding_to_add > 0u) { const size_t chunk_size = std::min(padding_to_add, sizeof(padding)); checksum_algo_->Update(padding, chunk_size); padding_to_add -= chunk_size; } return checksum_algo_->Finish(); } Status Entry::CalculateChecksumFromFlash() { header_.checksum = 0; if (checksum_algo_ == nullptr) { return Status::OK; } checksum_algo_->Reset(); checksum_algo_->Update(&header_, sizeof(header_)); Address address = address_ + sizeof(EntryHeader); const Address end = address_ + content_size(); std::array buffer; while (address < end) { const size_t read_size = std::min(size_t(end - address), buffer.size()); TRY(partition_->Read(address, span(buffer).first(read_size))); checksum_algo_->Update(buffer.data(), read_size); address += read_size; } span checksum = checksum_algo_->Finish(); std::memcpy(&header_.checksum, checksum.data(), std::min(checksum.size(), sizeof(header_.checksum))); return Status::OK; } } // namespace pw::kvs::internal