You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
7.9 KiB
242 lines
7.9 KiB
// 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.
|
|
|
|
#define PW_LOG_MODULE_NAME "KVS"
|
|
#define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
|
|
|
|
#include "pw_kvs/internal/entry_cache.h"
|
|
|
|
#include <cinttypes>
|
|
|
|
#include "pw_kvs/flash_memory.h"
|
|
#include "pw_kvs/internal/entry.h"
|
|
#include "pw_kvs/internal/hash.h"
|
|
#include "pw_kvs_private/config.h"
|
|
#include "pw_log/log.h"
|
|
|
|
namespace pw::kvs::internal {
|
|
namespace {
|
|
|
|
constexpr FlashPartition::Address kNoAddress = FlashPartition::Address(-1);
|
|
|
|
} // namespace
|
|
|
|
void EntryMetadata::RemoveAddress(Address address_to_remove) {
|
|
// Find the index of the address to remove.
|
|
for (Address& address : addresses_) {
|
|
if (address == address_to_remove) {
|
|
// Move the address at the back of the list to the slot of the address
|
|
// being removed. Do this unconditionally, even if the address to remove
|
|
// is the last slot since the logic still works.
|
|
address = addresses_.back();
|
|
|
|
// Remove the back entry of the address list.
|
|
addresses_.back() = kNoAddress;
|
|
addresses_ = std::span(addresses_.begin(), addresses_.size() - 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EntryMetadata::Reset(const KeyDescriptor& descriptor, Address address) {
|
|
*descriptor_ = descriptor;
|
|
|
|
addresses_[0] = address;
|
|
for (size_t i = 1; i < addresses_.size(); ++i) {
|
|
addresses_[i] = kNoAddress;
|
|
}
|
|
addresses_ = addresses_.first(1);
|
|
}
|
|
|
|
StatusWithSize EntryCache::Find(FlashPartition& partition,
|
|
const Sectors& sectors,
|
|
const EntryFormats& formats,
|
|
Key key,
|
|
EntryMetadata* metadata) const {
|
|
const uint32_t hash = internal::Hash(key);
|
|
Entry::KeyBuffer key_buffer;
|
|
bool error_detected = false;
|
|
|
|
for (size_t i = 0; i < descriptors_.size(); ++i) {
|
|
if (descriptors_[i].key_hash == hash) {
|
|
bool key_found = false;
|
|
Key read_key;
|
|
|
|
for (Address address : addresses(i)) {
|
|
Status read_result =
|
|
Entry::ReadKey(partition, address, key.size(), key_buffer.data());
|
|
|
|
read_key = Key(key_buffer.data(), key.size());
|
|
|
|
if (read_result.ok() && hash == internal::Hash(read_key)) {
|
|
key_found = true;
|
|
break;
|
|
} else {
|
|
// A hash mismatch can be caused by reading invalid data or a key hash
|
|
// collision of keys with differing size. To verify the data read from
|
|
// flash is good, validate the entry.
|
|
Entry entry;
|
|
read_result = Entry::Read(partition, address, formats, &entry);
|
|
if (read_result.ok() && entry.VerifyChecksumInFlash().ok()) {
|
|
key_found = true;
|
|
break;
|
|
}
|
|
|
|
PW_LOG_WARN(
|
|
" Found corrupt entry, invalidating this copy of the key");
|
|
error_detected = true;
|
|
sectors.FromAddress(address).mark_corrupt();
|
|
}
|
|
}
|
|
size_t error_val = error_detected ? 1 : 0;
|
|
|
|
if (!key_found) {
|
|
PW_LOG_ERROR("No valid entries for key. Data has been lost!");
|
|
return StatusWithSize::DataLoss(error_val);
|
|
} else if (key == read_key) {
|
|
PW_LOG_DEBUG("Found match for key hash 0x%08" PRIx32, hash);
|
|
*metadata = EntryMetadata(descriptors_[i], addresses(i));
|
|
return StatusWithSize(error_val);
|
|
} else {
|
|
PW_LOG_WARN("Found key hash collision for 0x%08" PRIx32, hash);
|
|
return StatusWithSize::AlreadyExists(error_val);
|
|
}
|
|
}
|
|
}
|
|
return StatusWithSize::NotFound();
|
|
}
|
|
|
|
EntryMetadata EntryCache::AddNew(const KeyDescriptor& descriptor,
|
|
Address entry_address) const {
|
|
// TODO(hepler): DCHECK(!full());
|
|
Address* first_address = ResetAddresses(descriptors_.size(), entry_address);
|
|
descriptors_.push_back(descriptor);
|
|
return EntryMetadata(descriptors_.back(), std::span(first_address, 1));
|
|
}
|
|
|
|
// TODO: This method is the trigger of the O(valid_entries * all_entries) time
|
|
// complexity for reading. At some cost to memory, this could be optimized by
|
|
// using a hash table instead of scanning, but in practice this should be fine
|
|
// for a small number of keys
|
|
Status EntryCache::AddNewOrUpdateExisting(const KeyDescriptor& descriptor,
|
|
Address address,
|
|
size_t sector_size_bytes) const {
|
|
// With the new key descriptor, either add it to the descriptor table or
|
|
// overwrite an existing entry with an older version of the key.
|
|
const int index = FindIndex(descriptor.key_hash);
|
|
|
|
// Write a new entry if there is room.
|
|
if (index == -1) {
|
|
if (full()) {
|
|
return Status::ResourceExhausted();
|
|
}
|
|
AddNew(descriptor, address);
|
|
return OkStatus();
|
|
}
|
|
|
|
// Existing entry is old; replace the existing entry with the new one.
|
|
if (descriptor.transaction_id > descriptors_[index].transaction_id) {
|
|
descriptors_[index] = descriptor;
|
|
ResetAddresses(index, address);
|
|
return OkStatus();
|
|
}
|
|
|
|
// If the entries have a duplicate transaction ID, add the new (redundant)
|
|
// entry to the existing descriptor.
|
|
if (descriptors_[index].transaction_id == descriptor.transaction_id) {
|
|
if (descriptors_[index].key_hash != descriptor.key_hash) {
|
|
PW_LOG_ERROR("Duplicate entry for key 0x%08" PRIx32
|
|
" with transaction ID %" PRIu32 " has non-matching hash",
|
|
descriptor.key_hash,
|
|
descriptor.transaction_id);
|
|
return Status::DataLoss();
|
|
}
|
|
|
|
// Verify that this entry is not in the same sector as an existing copy of
|
|
// this same key.
|
|
for (Address existing_address : addresses(index)) {
|
|
if (existing_address / sector_size_bytes == address / sector_size_bytes) {
|
|
PW_LOG_DEBUG("Multiple Redundant entries in same sector %u",
|
|
unsigned(address / sector_size_bytes));
|
|
return Status::DataLoss();
|
|
}
|
|
}
|
|
|
|
AddAddressIfRoom(index, address);
|
|
} else {
|
|
PW_LOG_DEBUG("Found stale entry when appending; ignoring");
|
|
}
|
|
return OkStatus();
|
|
}
|
|
|
|
size_t EntryCache::present_entries() const {
|
|
size_t present_entries = 0;
|
|
|
|
for (const KeyDescriptor& descriptor : descriptors_) {
|
|
if (descriptor.state != EntryState::kDeleted) {
|
|
present_entries += 1;
|
|
}
|
|
}
|
|
|
|
return present_entries;
|
|
}
|
|
|
|
int EntryCache::FindIndex(uint32_t key_hash) const {
|
|
for (size_t i = 0; i < descriptors_.size(); ++i) {
|
|
if (descriptors_[i].key_hash == key_hash) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void EntryCache::AddAddressIfRoom(size_t descriptor_index,
|
|
Address address) const {
|
|
Address* const existing = first_address(descriptor_index);
|
|
|
|
for (size_t i = 0; i < redundancy(); ++i) {
|
|
if (existing[i] == kNoAddress) {
|
|
existing[i] = address;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::span<EntryCache::Address> EntryCache::addresses(
|
|
size_t descriptor_index) const {
|
|
Address* const addresses = first_address(descriptor_index);
|
|
|
|
size_t size = 0;
|
|
while (size < redundancy() && addresses[size] != kNoAddress) {
|
|
size += 1;
|
|
}
|
|
|
|
return std::span(addresses, size);
|
|
}
|
|
|
|
EntryCache::Address* EntryCache::ResetAddresses(size_t descriptor_index,
|
|
Address address) const {
|
|
Address* first = first_address(descriptor_index);
|
|
*first = address;
|
|
|
|
// Clear the additional addresses, if any.
|
|
for (size_t i = 1; i < redundancy_; ++i) {
|
|
first[i] = kNoAddress;
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
} // namespace pw::kvs::internal
|