2020-02-04 15:36:45 +00:00
|
|
|
// 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.
|
|
|
|
|
2020-02-21 03:27:26 +00:00
|
|
|
#include "pw_kvs/internal/entry.h"
|
2020-02-04 15:36:45 +00:00
|
|
|
|
2020-02-12 19:01:49 +00:00
|
|
|
#include <cinttypes>
|
2020-02-14 17:10:53 +00:00
|
|
|
#include <cstring>
|
2020-02-12 19:01:49 +00:00
|
|
|
|
2020-02-04 15:36:45 +00:00
|
|
|
#include "pw_kvs_private/macros.h"
|
2020-02-05 16:41:06 +00:00
|
|
|
#include "pw_log/log.h"
|
2020-02-04 15:36:45 +00:00
|
|
|
|
2020-02-20 01:17:51 +00:00
|
|
|
namespace pw::kvs::internal {
|
2020-02-04 15:36:45 +00:00
|
|
|
|
|
|
|
using std::byte;
|
|
|
|
using std::string_view;
|
|
|
|
|
2020-03-05 22:57:11 +00:00
|
|
|
Status Entry::Read(FlashPartition& partition,
|
|
|
|
Address address,
|
|
|
|
const internal::EntryFormats& formats,
|
|
|
|
Entry* entry) {
|
2020-02-14 17:10:53 +00:00
|
|
|
EntryHeader header;
|
|
|
|
TRY(partition.Read(address, sizeof(header), &header));
|
|
|
|
|
|
|
|
if (partition.AppearsErased(as_bytes(span(&header.magic, 1)))) {
|
|
|
|
return Status::NOT_FOUND;
|
|
|
|
}
|
2020-02-14 22:31:26 +00:00
|
|
|
if (header.key_length_bytes > kMaxKeyLength) {
|
|
|
|
return Status::DATA_LOSS;
|
|
|
|
}
|
|
|
|
|
2020-03-05 22:57:11 +00:00
|
|
|
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);
|
2020-02-14 17:10:53 +00:00
|
|
|
return Status::OK;
|
|
|
|
}
|
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
Status Entry::ReadKey(FlashPartition& partition,
|
|
|
|
Address address,
|
|
|
|
size_t key_length,
|
|
|
|
char* key) {
|
2020-02-14 17:10:53 +00:00
|
|
|
if (key_length == 0u || key_length > kMaxKeyLength) {
|
2020-02-14 22:31:26 +00:00
|
|
|
return Status::DATA_LOSS;
|
2020-02-14 17:10:53 +00:00
|
|
|
}
|
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
return partition.Read(address + sizeof(EntryHeader), key_length, key)
|
|
|
|
.status();
|
2020-02-14 17:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Entry::Entry(FlashPartition& partition,
|
|
|
|
Address address,
|
2020-02-21 03:33:27 +00:00
|
|
|
const EntryFormat& format,
|
2020-02-12 19:26:05 +00:00
|
|
|
string_view key,
|
|
|
|
span<const byte> value,
|
2020-02-14 17:10:53 +00:00
|
|
|
uint16_t value_size_bytes,
|
2020-02-20 01:17:51 +00:00
|
|
|
uint32_t transaction_id)
|
2020-02-14 17:10:53 +00:00
|
|
|
: Entry(&partition,
|
|
|
|
address,
|
2020-03-05 22:57:11 +00:00
|
|
|
format,
|
2020-02-21 03:33:27 +00:00
|
|
|
{.magic = format.magic,
|
2020-02-14 17:10:53 +00:00
|
|
|
.checksum = 0,
|
2020-02-21 23:30:53 +00:00
|
|
|
.alignment_units =
|
|
|
|
alignment_bytes_to_units(partition.alignment_bytes()),
|
2020-02-14 17:10:53 +00:00
|
|
|
.key_length_bytes = static_cast<uint8_t>(key.size()),
|
|
|
|
.value_size_bytes = value_size_bytes,
|
2020-02-20 01:17:51 +00:00
|
|
|
.transaction_id = transaction_id}) {
|
2020-03-11 01:26:02 +00:00
|
|
|
if (checksum_algo_ != nullptr) {
|
2020-03-05 22:57:11 +00:00
|
|
|
span<const byte> checksum = CalculateChecksum(key, value);
|
2020-02-12 19:26:05 +00:00
|
|
|
std::memcpy(&header_.checksum,
|
2020-02-12 22:49:14 +00:00
|
|
|
checksum.data(),
|
2020-02-12 19:26:05 +00:00
|
|
|
std::min(checksum.size(), sizeof(header_.checksum)));
|
2020-02-04 15:36:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
StatusWithSize Entry::Write(string_view key, span<const byte> value) const {
|
2020-02-14 17:10:53 +00:00
|
|
|
FlashPartition::Output flash(partition(), address_);
|
|
|
|
return AlignedWrite<64>(
|
|
|
|
flash,
|
|
|
|
alignment_bytes(),
|
|
|
|
{as_bytes(span(&header_, 1)), as_bytes(span(key)), value});
|
|
|
|
}
|
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
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 {
|
2020-03-11 23:15:16 +00:00
|
|
|
PW_LOG_DEBUG("Copying entry from 0x%x to 0x%x as ID %" PRIu32,
|
2020-03-11 01:26:02 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-02-19 00:54:31 +00:00
|
|
|
StatusWithSize Entry::ReadValue(span<byte> buffer, size_t offset_bytes) const {
|
|
|
|
if (offset_bytes > value_size()) {
|
2020-02-25 21:50:05 +00:00
|
|
|
return StatusWithSize::OUT_OF_RANGE;
|
2020-02-19 00:54:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
2020-02-14 17:10:53 +00:00
|
|
|
TRY_WITH_SIZE(result);
|
|
|
|
|
2020-02-19 00:54:31 +00:00
|
|
|
if (read_size != remaining_bytes) {
|
2020-02-14 17:10:53 +00:00
|
|
|
return StatusWithSize(Status::RESOURCE_EXHAUSTED, read_size);
|
|
|
|
}
|
|
|
|
return StatusWithSize(read_size);
|
|
|
|
}
|
|
|
|
|
2020-03-05 22:57:11 +00:00
|
|
|
Status Entry::VerifyChecksum(string_view key, span<const byte> value) const {
|
2020-03-11 01:26:02 +00:00
|
|
|
if (checksum_algo_ == nullptr) {
|
|
|
|
return header_.checksum == 0 ? Status::OK : Status::DATA_LOSS;
|
2020-02-04 15:36:45 +00:00
|
|
|
}
|
2020-03-05 22:57:11 +00:00
|
|
|
CalculateChecksum(key, value);
|
2020-03-11 01:26:02 +00:00
|
|
|
return checksum_algo_->Verify(checksum_bytes());
|
2020-02-04 15:36:45 +00:00
|
|
|
}
|
|
|
|
|
2020-03-05 22:57:11 +00:00
|
|
|
Status Entry::VerifyChecksumInFlash() const {
|
2020-02-12 19:01:49 +00:00
|
|
|
// 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);
|
|
|
|
|
2020-02-14 17:10:53 +00:00
|
|
|
Address read_address = address_;
|
|
|
|
|
2020-02-12 19:01:49 +00:00
|
|
|
// Read the first chunk, which includes the header, and compare the checksum.
|
2020-02-14 17:10:53 +00:00
|
|
|
TRY(partition().Read(read_address, read_size, buffer));
|
2020-02-12 19:01:49 +00:00
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
if (header_to_verify.checksum != header_.checksum) {
|
2020-02-12 19:01:49 +00:00
|
|
|
PW_LOG_ERROR("Expected checksum %08" PRIx32 ", found %08" PRIx32,
|
2020-03-11 01:26:02 +00:00
|
|
|
header_.checksum,
|
2020-02-12 19:01:49 +00:00
|
|
|
header_to_verify.checksum);
|
2020-02-05 16:41:06 +00:00
|
|
|
return Status::DATA_LOSS;
|
|
|
|
}
|
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
if (checksum_algo_ == nullptr) {
|
|
|
|
return header_.checksum == 0 ? Status::OK : Status::DATA_LOSS;
|
2020-02-05 23:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-02-12 19:01:49 +00:00
|
|
|
// The checksum is calculated as if the header's checksum field were 0.
|
|
|
|
header_to_verify.checksum = 0;
|
2020-02-04 15:36:45 +00:00
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
checksum_algo_->Reset();
|
2020-02-05 01:47:40 +00:00
|
|
|
|
2020-02-12 19:01:49 +00:00
|
|
|
while (true) {
|
|
|
|
// Add the chunk in the buffer to the checksum.
|
2020-03-11 01:26:02 +00:00
|
|
|
checksum_algo_->Update(buffer, read_size);
|
2020-02-05 01:47:40 +00:00
|
|
|
|
|
|
|
bytes_to_read -= read_size;
|
2020-02-12 19:01:49 +00:00
|
|
|
if (bytes_to_read == 0u) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the next chunk into the buffer.
|
2020-02-14 17:10:53 +00:00
|
|
|
read_address += read_size;
|
2020-02-12 19:01:49 +00:00
|
|
|
read_size = std::min(sizeof(buffer), bytes_to_read);
|
2020-02-14 17:10:53 +00:00
|
|
|
TRY(partition().Read(read_address, read_size, buffer));
|
2020-02-04 15:36:45 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
checksum_algo_->Finish();
|
|
|
|
return checksum_algo_->Verify(checksum_bytes());
|
2020-02-04 15:36:45 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
void Entry::DebugLog() const {
|
|
|
|
PW_LOG_DEBUG("Entry [%s]: ", deleted() ? "tombstone" : "present");
|
2020-02-14 17:10:53 +00:00
|
|
|
PW_LOG_DEBUG(" Address = 0x%zx", size_t(address_));
|
2020-03-11 01:26:02 +00:00
|
|
|
PW_LOG_DEBUG(" Transaction = %zu", size_t(transaction_id()));
|
2020-02-14 17:10:53 +00:00
|
|
|
PW_LOG_DEBUG(" Magic = 0x%zx", size_t(magic()));
|
2020-03-11 01:26:02 +00:00
|
|
|
PW_LOG_DEBUG(" Checksum = 0x%zx", size_t(header_.checksum));
|
2020-02-14 17:10:53 +00:00
|
|
|
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()));
|
|
|
|
}
|
|
|
|
|
2020-03-05 22:57:11 +00:00
|
|
|
span<const byte> Entry::CalculateChecksum(const string_view key,
|
2020-02-12 19:26:05 +00:00
|
|
|
span<const byte> value) const {
|
2020-03-11 01:26:02 +00:00
|
|
|
checksum_algo_->Reset();
|
2020-02-12 19:01:49 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
EntryHeader header_for_checksum = header_;
|
|
|
|
header_for_checksum.checksum = 0;
|
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
checksum_algo_->Update(&header_for_checksum, sizeof(header_for_checksum));
|
|
|
|
checksum_algo_->Update(as_bytes(span(key)));
|
|
|
|
checksum_algo_->Update(value);
|
2020-02-12 19:01:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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));
|
2020-03-11 01:26:02 +00:00
|
|
|
checksum_algo_->Update(padding, chunk_size);
|
2020-02-12 19:01:49 +00:00
|
|
|
padding_to_add -= chunk_size;
|
|
|
|
}
|
2020-02-12 22:49:14 +00:00
|
|
|
|
2020-03-11 01:26:02 +00:00
|
|
|
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<std::byte, 2 * kMinAlignmentBytes> 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;
|
2020-02-04 15:36:45 +00:00
|
|
|
}
|
|
|
|
|
2020-02-20 01:17:51 +00:00
|
|
|
} // namespace pw::kvs::internal
|