blob: 1801dc3cc5ac35fc946fb3efbdd5715d6b06cbe5 [file] [log] [blame]
// 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/key_value_store.h"
#include <cstring>
#include <type_traits>
#include "pw_kvs_private/format.h"
#include "pw_kvs_private/macros.h"
namespace pw::kvs {
using std::byte;
using std::string_view;
namespace {
// constexpr uint32_t SixFiveFiveNineNine(std::string_view string) {
constexpr uint32_t HashKey(std::string_view string) {
uint32_t hash = 0;
uint32_t coefficient = 65599u;
for (char ch : string) {
hash += coefficient * unsigned(ch);
coefficient *= 65599u;
}
return hash;
}
constexpr size_t EntrySize(string_view key, span<const byte> value) {
return sizeof(EntryHeader) + key.size() + value.size();
}
} // namespace
Status KeyValueStore::Init() {
// enabled_ = true;
return Status::UNIMPLEMENTED;
}
StatusWithSize KeyValueStore::Get(string_view key,
span<byte> value_buffer) const {
TRY(InvalidOperation(key));
const KeyMapEntry* key_map_entry;
TRY(FindKeyMapEntry(key, &key_map_entry));
EntryHeader header;
TRY(ReadEntryHeader(*key_map_entry, &header));
StatusWithSize result = ReadEntryValue(*key_map_entry, header, value_buffer);
if (result.ok() && options_.verify_on_read) {
return ValidateEntryChecksum(header, key, value_buffer);
}
return result;
}
Status KeyValueStore::Put(string_view key, span<const byte> value) {
TRY(InvalidOperation(key));
if (value.size() > (1 << 24)) {
// TODO: Reject sizes that are larger than the maximum?
}
if (KeyMapEntry * entry; FindKeyMapEntry(key, &entry).ok()) {
return WriteEntryForExistingKey(entry, key, value);
}
return WriteEntryForNewKey(key, value);
}
Status KeyValueStore::Delete(string_view key) {
TRY(InvalidOperation(key));
return Status::UNIMPLEMENTED;
}
const KeyValueStore::Entry& KeyValueStore::Iterator::operator*() {
const KeyMapEntry& map_entry = entry_.kvs_.key_map_[index_];
EntryHeader header;
if (entry_.kvs_.ReadEntryHeader(map_entry, &header).ok()) {
entry_.kvs_.ReadEntryKey(
map_entry, header.key_length(), entry_.key_buffer_.data());
}
return entry_;
}
Status KeyValueStore::InvalidOperation(string_view key) const {
if (InvalidKey(key)) {
return Status::INVALID_ARGUMENT;
}
if (!/*enabled_*/ 0) {
return Status::FAILED_PRECONDITION;
}
return Status::OK;
}
Status KeyValueStore::FindKeyMapEntry(string_view key,
const KeyMapEntry** result) const {
char key_buffer[kMaxKeyLength];
const uint32_t hash = HashKey(key);
for (auto& entry : entries()) {
if (entry.key_hash == hash) {
TRY(ReadEntryKey(entry, key.size(), key_buffer));
if (key == string_view(key_buffer, key.size())) {
*result = &entry;
return Status::OK;
}
}
}
return Status::NOT_FOUND;
}
Status KeyValueStore::ReadEntryHeader(const KeyMapEntry& entry,
EntryHeader* header) const {
return partition_.Read(entry.address, sizeof(*header), header).status();
}
Status KeyValueStore::ReadEntryKey(const KeyMapEntry& entry,
size_t key_length,
char* key) const {
// TODO: This check probably shouldn't be here; this is like
// checking that the Cortex M's RAM isn't corrupt. This should be
// done at boot time.
// ^^ This argument sometimes comes from EntryHeader::key_value_len,
// which is read directly from flash. If it's corrupted, we shouldn't try
// to read a bunch of extra data.
if (key_length == 0u || key_length > kMaxKeyLength) {
return Status::DATA_LOSS;
}
// The key is immediately after the entry header.
return partition_.Read(entry.address + sizeof(EntryHeader), key_length, key)
.status();
}
StatusWithSize KeyValueStore::ReadEntryValue(const KeyMapEntry& entry,
const EntryHeader& header,
span<byte> value) const {
const size_t read_size = std::min(header.value_length(), value.size());
StatusWithSize result =
partition_.Read(entry.address + sizeof(header) + header.key_length(),
value.subspan(read_size));
TRY(result);
if (read_size != header.value_length()) {
return StatusWithSize(Status::RESOURCE_EXHAUSTED, read_size);
}
return StatusWithSize(read_size);
}
Status KeyValueStore::ValidateEntryChecksum(const EntryHeader& header,
string_view key,
span<const byte> value) const {
CalculateEntryChecksum(header, key, value);
return entry_header_format_.checksum->Verify(header.checksum());
}
Status KeyValueStore::WriteEntryForExistingKey(KeyMapEntry* key_map_entry,
string_view key,
span<const byte> value) {
SectorMapEntry* sector;
TRY(FindOrRecoverSectorWithSpace(&sector, EntrySize(key, value)));
return AppendEntry(sector, key_map_entry, key, value);
}
Status KeyValueStore::WriteEntryForNewKey(string_view key,
span<const byte> value) {
if (EntryMapFull()) {
// TODO: Log, and also expose "in memory keymap full" in stats dump.
return Status::RESOURCE_EXHAUSTED;
}
// Modify the entry at the end of the array, without bumping the map size
// so the entry is prepared and written without committing first.
KeyMapEntry& entry = key_map_[key_map_size_];
entry.key_hash = HashKey(key);
entry.key_version = 0; // will be incremented by AppendEntry()
SectorMapEntry* sector;
TRY(FindOrRecoverSectorWithSpace(&sector, EntrySize(key, value)));
TRY(AppendEntry(sector, &entry, key, value));
// Only increment key_map_size_ when we are certain the write
// succeeded.
key_map_size_ += 1;
return Status::OK;
}
// Find either an existing sector with enough space, or an empty sector.
// Maintains the invariant that there is always at least 1 empty sector.
KeyValueStore::SectorMapEntry* KeyValueStore::FindSectorWithSpace(size_t size) {
int start = (last_written_sector_ + 1) % sector_map_.size();
SectorMapEntry* first_empty_sector = nullptr;
bool at_least_two_empty_sectors = false;
for (size_t i = start; i != last_written_sector_;
i = (i + 1) % sector_map_.size()) {
SectorMapEntry& sector = sector_map_[i];
if (!SectorEmpty(sector) && sector.HasSpace(size)) {
return &sector;
}
if (SectorEmpty(sector)) {
if (first_empty_sector == nullptr) {
first_empty_sector = &sector;
} else {
at_least_two_empty_sectors = true;
}
}
}
if (at_least_two_empty_sectors) {
return first_empty_sector;
}
return nullptr;
}
Status KeyValueStore::FindOrRecoverSectorWithSpace(SectorMapEntry** sector,
size_t size) {
*sector = FindSectorWithSpace(size);
if (*sector != nullptr) {
return Status::OK;
}
if (options_.partial_gc_on_write) {
return GarbageCollectOneSector(sector);
}
return Status::RESOURCE_EXHAUSTED;
}
Status KeyValueStore::GarbageCollectOneSector(SectorMapEntry** sector) {
// TODO: THIS NEEDS WORK
(void)sector;
SectorMapEntry* sector_to_gc = FindSectorToGarbageCollect();
const Address sector_base = SectorBaseAddress(sector_to_gc);
const Address sector_end = sector_base + partition_.sector_size_bytes();
for (auto entry : entries()) {
if ((entry.address >= sector_base) && (entry.address < sector_end)) {
// RelocateEntry(entry);
}
}
if (sector_to_gc->valid_bytes != 0) {
return Status::INTERNAL;
}
return Status::OK;
}
KeyValueStore::SectorMapEntry* KeyValueStore::FindSectorToGarbageCollect() {
// TODO: implement me
return nullptr;
}
Status KeyValueStore::AppendEntry(SectorMapEntry* sector,
KeyMapEntry* entry,
const string_view key,
span<const byte> value) {
// write header, key, and value
const EntryHeader header(entry_header_format_.magic,
CalculateEntryChecksum(header, key, value),
key.size(),
value.size(),
entry->key_version + 1);
// Handles writing multiple concatenated buffers, while breaking up the writes
// into alignment-sized blocks.
Address address = NextWritableAddress(sector);
TRY_ASSIGN(
size_t written,
partition_.Write(
address, {as_bytes(span(&header, 1)), as_bytes(span(key)), value}));
if (options_.verify_on_write) {
TRY(VerifyEntry(sector, entry));
}
// TODO: UPDATE last_written_sector_ appropriately
entry->address = address;
entry->key_version = header.key_version();
sector->valid_bytes += written;
sector->tail_free_bytes -= written;
return Status::OK;
}
Status KeyValueStore::VerifyEntry(SectorMapEntry* sector, KeyMapEntry* entry) {
// TODO: Implement me!
(void)sector;
(void)entry;
return Status::UNIMPLEMENTED;
}
span<const byte> KeyValueStore::CalculateEntryChecksum(
const EntryHeader& header,
const string_view key,
span<const byte> value) const {
auto& checksum = *entry_header_format_.checksum;
checksum.Reset();
checksum.Update(header.DataForChecksum());
checksum.Update(as_bytes(span(key)));
checksum.Update(value.data(), value.size_bytes());
return checksum.state();
}
Status KeyValueStore::RelocateEntry(KeyMapEntry* entry) {
// TODO: implement me
(void)entry;
return Status::UNIMPLEMENTED;
}
} // namespace pw::kvs