mirror of
https://fuchsia.googlesource.com/third_party/pigweed.googlesource.com/pigweed/pigweed
synced 2024-09-21 14:16:26 +00:00
4da1fcb4b2
Builds, but lots more work needed. Fun fact: this code was written collaboratively in a Google Doc. Change-Id: I8a89c5d0fdc71ec28cf432350e65d17e24a6f25c
331 lines
10 KiB
C++
331 lines
10 KiB
C++
// 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(§or, 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(§or, 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 §or;
|
|
}
|
|
|
|
if (SectorEmpty(sector)) {
|
|
if (first_empty_sector == nullptr) {
|
|
first_empty_sector = §or;
|
|
} 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
|