kvdb: implement MemoryKeyValueStore

This commit is contained in:
Michael Scire 2019-07-13 21:35:51 -07:00 committed by SciresM
parent a4a3ebed50
commit 99ebbd2a18
7 changed files with 1062 additions and 2 deletions

View File

@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
# INCLUDES is a list of directories containing header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr source/kvdb
DATA := data
INCLUDES := include

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "../defines.hpp"
#include "../results.hpp"
#include "kvdb_auto_buffer.hpp"
namespace sts::kvdb {
/* Functionality for parsing/generating a key value archive. */
class ArchiveReader {
private:
AutoBuffer &buffer;
size_t offset;
public:
ArchiveReader(AutoBuffer &b) : buffer(b), offset(0) { /* ... */ }
private:
Result Peek(void *dst, size_t size);
Result Read(void *dst, size_t size);
public:
Result ReadEntryCount(size_t *out);
Result GetEntrySize(size_t *out_key_size, size_t *out_value_size);
Result ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size);
};
class ArchiveWriter {
private:
AutoBuffer &buffer;
size_t offset;
public:
ArchiveWriter(AutoBuffer &b) : buffer(b), offset(0) { /* ... */ }
private:
Result Write(const void *src, size_t size);
public:
void WriteHeader(size_t entry_count);
void WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size);
};
class ArchiveSizeHelper {
private:
size_t size;
public:
ArchiveSizeHelper();
void AddEntry(size_t key_size, size_t value_size);
size_t GetSize() const {
return this->size;
}
};
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "../defines.hpp"
#include "../results.hpp"
namespace sts::kvdb {
class AutoBuffer {
NON_COPYABLE(AutoBuffer);
private:
std::unique_ptr<u8[]> buffer;
size_t size;
public:
AutoBuffer() : size(0) { /* ... */ }
AutoBuffer(AutoBuffer &&rhs) {
this->buffer = std::move(rhs.buffer);
this->size = rhs.size;
rhs.size = 0;
}
AutoBuffer& operator=(AutoBuffer &&rhs) {
rhs.Swap(*this);
return *this;
}
void Swap(AutoBuffer &rhs) {
std::swap(this->buffer, rhs.buffer);
std::swap(this->size, rhs.size);
}
void Reset() {
this->buffer.reset();
this->size = 0;
}
u8 *Get() const {
return this->buffer.get();
}
size_t GetSize() const {
return this->size;
}
Result Initialize(size_t size) {
/* Check that we're not already initialized. */
if (this->buffer != nullptr) {
std::abort();
}
/* Allocate a buffer. */
auto mem = new (std::nothrow) u8[size];
if (mem == nullptr) {
return ResultKvdbAllocationFailed;
}
this->buffer.reset(mem);
this->size = size;
return ResultSuccess;
}
Result Initialize(const void *buf, size_t size) {
/* Create a new buffer of the right size. */
R_TRY(this->Initialize(size));
/* Copy the input data in. */
std::memcpy(this->buffer.get(), buf, size);
return ResultSuccess;
}
};
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstring>
#include <cstdarg>
#include <switch.h>
#include "../defines.hpp"
#include "../results.hpp"
namespace sts::kvdb {
/* Represents a string with a backing buffer of N bytes. */
template<size_t N>
class BoundedString {
static_assert(N > 0, "BoundedString requires non-zero backing buffer!");
private:
char buffer[N];
private:
/* Utility. */
static inline void CheckLength(size_t len) {
if (len >= N) {
std::abort();
}
}
public:
/* Constructors. */
BoundedString() {
buffer[0] = 0;
}
explicit BoundedString(const char *s) {
this->Set(s);
}
/* Static constructors. */
static BoundedString<N> Make(const char *s) {
return BoundedString<N>(s);
}
static BoundedString<N> MakeFormat(const char *format, ...) __attribute__((format (printf, 1, 2))) {
BoundedString<N> string;
std::va_list args;
va_start(args, format);
CheckLength(std::vsnprintf(string.buffer, N, format, args));
va_end(args);
return string;
}
/* Getters. */
size_t GetLength() const {
return strnlen(this->buffer, N);
}
const char *Get() const {
return this->buffer;
}
operator const char *() const {
return this->buffer;
}
/* Setters. */
void Set(const char *s) {
/* Ensure string can fit in our buffer. */
CheckLength(strnlen(s, N));
std::strncpy(this->buffer, s, N);
}
void SetFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) {
/* Format into the buffer, abort if too large. */
std::va_list args;
va_start(args, format);
CheckLength(std::vsnprintf(this->buffer, N, format, args));
va_end(args);
}
/* Append to existing. */
void Append(const char *s) {
const size_t length = GetLength();
CheckLength(length + strnlen(s, N));
std::strncat(this->buffer, s, N - length - 1);
}
void Append(char c) {
const size_t length = GetLength();
CheckLength(length + 1);
this->buffer[length] = c;
this->buffer[length + 1] = 0;
}
void AppendFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) {
const size_t length = GetLength();
std::va_list args;
va_start(args, format);
CheckLength(std::vsnprintf(this->buffer + length, N - length, format, args) + length);
va_end(args);
}
/* Substring utilities. */
void GetSubstring(char *dst, size_t dst_size, size_t offset, size_t length) const {
/* Make sure output buffer can hold the substring. */
if (offset + length > GetLength() || dst_size <= length) {
std::abort();
}
/* Copy substring to dst. */
std::strncpy(dst, this->buffer + offset, length);
dst[length] = 0;
}
BoundedString<N> GetSubstring(size_t offset, size_t length) const {
BoundedString<N> string;
GetSubstring(string.buffer, N, offset, length);
return string;
}
/* Comparison. */
constexpr bool operator==(const BoundedString<N> &rhs) const {
return std::strncmp(this->buffer, rhs.buffer, N) == 0;
}
constexpr bool operator!=(const BoundedString<N> &rhs) const {
return !(*this == rhs);
}
bool EndsWith(const char *s, size_t offset) const {
return std::strncmp(this->buffer + offset, s, N - offset) == 0;
}
bool EndsWith(const char *s) {
const size_t suffix_length = strnlen(s, N);
const size_t length = GetLength();
return suffix_length <= length && EndsWith(s, length - suffix_length);
}
};
}

View File

@ -0,0 +1,566 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <algorithm>
#include <switch.h>
#include <sys/stat.h>
#include "kvdb_auto_buffer.hpp"
#include "kvdb_archive.hpp"
#include "kvdb_bounded_string.hpp"
#include "kvdb_memory_key_value_store.hpp"
namespace sts::kvdb {
template<class Key>
class MemoryKeyValueStore {
static_assert(std::is_pod<Key>::value, "KeyValueStore Keys must be pod!");
NON_COPYABLE(MemoryKeyValueStore);
NON_MOVEABLE(MemoryKeyValueStore);
public:
/* Subtypes. */
class Entry {
private:
Key key;
void *value;
size_t value_size;
public:
constexpr Entry(const Key &k, void *v, size_t s) : key(k), value(v), value_size(s) { /* ... */ }
const Key &GetKey() const {
return this->key;
}
template<typename Value = void>
Value *GetValuePointer() {
/* Size check. Note: Nintendo does not size check. */
if constexpr (!std::is_same<Value, void>::value) {
if (sizeof(Value) > this->value_size) {
std::abort();
}
/* Ensure we only get pod. */
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
}
return reinterpret_cast<Value *>(this->value);
}
template<typename Value = void>
const Value *GetValuePointer() const {
/* Size check. Note: Nintendo does not size check. */
if constexpr (!std::is_same<Value, void>::value) {
if (sizeof(Value) > this->value_size) {
std::abort();
}
/* Ensure we only get pod. */
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
}
return reinterpret_cast<Value *>(this->value);
}
template<typename Value>
Value &GetValue() {
return *(this->GetValuePointer<Value>());
}
template<typename Value>
const Value &GetValue() const {
return *(this->GetValuePointer<Value>());
}
size_t GetValueSize() const {
return this->value_size;
}
constexpr inline bool operator<(const Key &rhs) const {
return key < rhs;
}
constexpr inline bool operator==(const Key &rhs) const {
return key == rhs;
}
};
class Index {
private:
size_t count;
size_t capacity;
Entry *entries;
public:
Index() : count(0), capacity(0), entries(nullptr) { /* ... */ }
~Index() {
if (this->entries != nullptr) {
this->ResetEntries();
std::free(this->entries);
this->entries = nullptr;
}
}
size_t GetCount() const {
return this->count;
}
size_t GetCapacity() const {
return this->capacity;
}
void ResetEntries() {
for (size_t i = 0; i < this->count; i++) {
std::free(this->entries[i].GetValuePointer());
}
this->count = 0;
}
Result Initialize(size_t capacity) {
this->entries = reinterpret_cast<Entry *>(std::malloc(sizeof(Entry) * capacity));
if (this->entries == nullptr) {
return ResultKvdbAllocationFailed;
}
this->capacity = capacity;
return ResultSuccess;
}
Result Set(const Key &key, const void *value, size_t value_size) {
/* Allocate new value. */
void *new_value = std::malloc(value_size);
if (new_value == nullptr) {
return ResultKvdbAllocationFailed;
}
std::memcpy(new_value, value, value_size);
/* Find entry for key. */
Entry *it = this->lower_bound(key);
if (it != this->end() && it->GetKey() == key) {
/* Entry already exists. Free old value. */
std::free(it->GetValuePointer());
} else {
/* We need to add a new entry. Check we have room, move future keys forward. */
if (this->count >= this->capacity) {
std::free(new_value);
return ResultKvdbTooManyKeys;
}
std::memmove(it + 1, it, sizeof(*it) * (this->end() - it));
this->count++;
}
/* Save the new Entry in the map. */
*it = Entry(key, new_value, value_size);
return ResultSuccess;
}
Result AddUnsafe(const Key &key, void *value, size_t value_size) {
if (this->count >= this->capacity) {
return ResultKvdbTooManyKeys;
}
this->entries[this->count++] = Entry(key, value, value_size);
return ResultSuccess;
}
Result Remove(const Key &key) {
/* Find entry for key. */
Entry *it = this->find(key);
/* Check if the entry is valid. */
if (it != this->end()) {
/* Free the value, move entries back. */
std::free(it->GetValuePointer());
std::memmove(it, it + 1, sizeof(*it) * (this->end() - (it + 1)));
this->count--;
return ResultSuccess;
}
/* If it's not, we didn't remove it. */
return ResultKvdbKeyNotFound;
}
Entry *begin() {
return this->GetBegin();
}
const Entry *begin() const {
return this->GetBegin();
}
Entry *end() {
return this->GetEnd();
}
const Entry *end() const {
return this->GetEnd();
}
const Entry *cbegin() const {
return this->begin();
}
const Entry *cend() const {
return this->end();
}
Entry *lower_bound(const Key &key) {
return this->GetLowerBound(key);
}
const Entry *lower_bound(const Key &key) const {
return this->GetLowerBound(key);
}
Entry *find(const Key &key) {
return this->Find(key);
}
const Entry *find(const Key &key) const {
return this->Find(key);
}
private:
Entry *GetBegin() {
return this->entries;
}
const Entry *GetBegin() const {
return this->entries;
}
Entry *GetEnd() {
return this->GetBegin() + this->count;
}
const Entry *GetEnd() const {
return this->GetBegin() + this->count;
}
Entry *GetLowerBound(const Key &key) {
return std::lower_bound(this->GetBegin(), this->GetEnd(), key);
}
const Entry *GetLowerBound(const Key &key) const {
return std::lower_bound(this->GetBegin(), this->GetEnd(), key);
}
Entry *Find(const Key &key) {
auto it = this->GetLowerBound(key);
auto end = this->GetEnd();
if (it != end && it->GetKey() == key) {
return it;
}
return end;
}
const Entry *Find(const Key &key) const {
auto it = this->GetLowerBound(key);
auto end = this->GetEnd();
if (it != end && it->GetKey() == key) {
return it;
}
return end;
}
};
private:
static constexpr size_t MaxPathLen = 0x300; /* TODO: FS_MAX_PATH - 1? */
using Path = kvdb::BoundedString<MaxPathLen>;
private:
Index index;
Path path;
Path temp_path;
public:
MemoryKeyValueStore() { /* ... */ }
Result Initialize(const char *dir, size_t capacity) {
/* Ensure that the passed path is a directory. */
{
struct stat st;
if (stat(dir, &st) != 0 || !(S_ISDIR(st.st_mode))) {
return ResultFsPathNotFound;
}
}
/* Set paths. */
this->path.SetFormat("%s%s", dir, "/imkvdb.arc");
this->temp_path.SetFormat("%s%s", dir, "/imkvdb.tmp");
/* Initialize our index. */
R_TRY(this->index.Initialize(capacity));
return ResultSuccess;
}
Result Initialize(size_t capacity) {
/* This initializes without an archive file. */
/* A store initialized this way cannot have its contents loaded from or flushed to disk. */
this->path.Set("");
this->temp_path.Set("");
/* Initialize our index. */
R_TRY(this->index.Initialize(capacity));
return ResultSuccess;
}
size_t GetCount() const {
return this->index.GetCount();
}
size_t GetCapacity() const {
return this->index.GetCapacity();
}
Result Load() {
/* Reset any existing entries. */
this->index.ResetEntries();
/* Try to read the archive -- note, path not found is a success condition. */
/* This is because no archive file = no entries, so we're in the right state. */
AutoBuffer buffer;
R_TRY_CATCH(this->ReadArchiveFile(&buffer)) {
R_CATCH(ResultFsPathNotFound) {
return ResultSuccess;
}
} R_END_TRY_CATCH;
/* Parse entries from the buffer. */
{
ArchiveReader reader(buffer);
size_t entry_count = 0;
R_TRY(reader.ReadEntryCount(&entry_count));
for (size_t i = 0; i < entry_count; i++) {
/* Get size of key/value. */
size_t key_size = 0, value_size = 0;
R_TRY(reader.GetEntrySize(&key_size, &value_size));
/* Allocate memory for value. */
void *new_value = std::malloc(value_size);
if (new_value == nullptr) {
return ResultKvdbAllocationFailed;
}
auto value_guard = SCOPE_GUARD { std::free(new_value); };
/* Read key and value. */
Key key;
R_TRY(reader.ReadEntry(&key, sizeof(key), new_value, value_size));
R_TRY(this->index.AddUnsafe(key, new_value, value_size));
/* We succeeded, so cancel the value guard to prevent deallocation. */
value_guard.Cancel();
}
}
return ResultSuccess;
}
Result Save() {
/* Create a buffer to hold the archive. */
AutoBuffer buffer;
R_TRY(buffer.Initialize(this->GetArchiveSize()));
/* Write the archive to the buffer. */
{
ArchiveWriter writer(buffer);
writer.WriteHeader(this->GetCount());
for (const auto &it : this->index) {
const auto &key = it.GetKey();
writer.WriteEntry(&key, sizeof(Key), it.GetValuePointer(), it.GetValueSize());
}
}
/* Save the buffer to disk. */
return this->Commit(buffer);
}
Result Set(const Key &key, const void *value, size_t value_size) {
return this->index.Set(key, value, value_size);
}
template<typename Value>
Result Set(const Key &key, const Value &value) {
/* Only allow setting pod. */
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
return this->Set(key, &value, sizeof(Value));
}
template<typename Value>
Result Set(const Key &key, const Value *value) {
/* Only allow setting pod. */
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
return this->Set(key, value, sizeof(Value));
}
Result Get(size_t *out_size, void *out_value, size_t max_out_size, const Key &key) {
/* Find entry. */
auto it = this->find(key);
if (it == this->end()) {
return ResultKvdbKeyNotFound;
}
size_t size = std::min(max_out_size, it.GetValueSize());
std::memcpy(out_value, it.GetValuePointer(), size);
*out_size = size;
return ResultSuccess;
}
template<typename Value = void>
Result GetValuePointer(Value **out_value, const Key &key) {
/* Find entry. */
auto it = this->find(key);
if (it == this->end()) {
return ResultKvdbKeyNotFound;
}
*out_value = it->template GetValuePointer<Value>();
return ResultSuccess;
}
template<typename Value = void>
Result GetValuePointer(const Value **out_value, const Key &key) const {
/* Find entry. */
auto it = this->find(key);
if (it == this->end()) {
return ResultKvdbKeyNotFound;
}
*out_value = it->template GetValuePointer<Value>();
return ResultSuccess;
}
template<typename Value>
Result GetValue(Value *out_value, const Key &key) const {
/* Find entry. */
auto it = this->find(key);
if (it == this->end()) {
return ResultKvdbKeyNotFound;
}
*out_value = it->template GetValue<Value>();
return ResultSuccess;
}
Result GetValueSize(size_t *out_size, const Key &key) const {
/* Find entry. */
auto it = this->find(key);
if (it == this->end()) {
return ResultKvdbKeyNotFound;
}
*out_size = it->GetValueSize();
return ResultSuccess;
}
Result Remove(const Key &key) {
return this->index.Remove(key);
}
Entry *begin() {
return this->index.begin();
}
const Entry *begin() const {
return this->index.begin();
}
Entry *end() {
return this->index.end();
}
const Entry *end() const {
return this->index.end();
}
const Entry *cbegin() const {
return this->index.cbegin();
}
const Entry *cend() const {
return this->index.cend();
}
Entry *lower_bound(const Key &key) {
return this->index.lower_bound(key);
}
const Entry *lower_bound(const Key &key) const {
return this->index.lower_bound(key);
}
Entry *find(const Key &key) {
return this->index.find(key);
}
const Entry *find(const Key &key) const {
return this->index.find(key);
}
private:
Result Commit(const AutoBuffer &buffer) {
/* Try to delete temporary archive, but allow deletion failure (it may not exist). */
std::remove(this->temp_path.Get());
/* Write data to the temporary archive. */
{
FILE *f = fopen(this->temp_path, "wb");
if (f == nullptr) {
return fsdevGetLastResult();
}
ON_SCOPE_EXIT { fclose(f); };
if (fwrite(buffer.Get(), buffer.GetSize(), 1, f) != 1) {
return fsdevGetLastResult();
}
}
/* Try to delete the saved archive, but allow deletion failure. */
std::remove(this->path.Get());
/* Rename the path. */
if (std::rename(this->temp_path.Get(), this->path.Get()) != 0) {
return fsdevGetLastResult();
}
return ResultSuccess;
}
size_t GetArchiveSize() const {
ArchiveSizeHelper size_helper;
for (const auto &it : this->index) {
size_helper.AddEntry(sizeof(Key), it.GetValueSize());
}
return size_helper.GetSize();
}
Result ReadArchiveFile(AutoBuffer *dst) const {
/* Open the file. */
FILE *f = fopen(this->path, "rb");
if (f == nullptr) {
return fsdevGetLastResult();
}
ON_SCOPE_EXIT { fclose(f); };
/* Get the archive file size. */
fseek(f, 0, SEEK_END);
const size_t archive_size = ftell(f);
fseek(f, 0, SEEK_SET);
/* Make a new buffer, read the file. */
R_TRY(dst->Initialize(archive_size));
if (fread(dst->Get(), archive_size, 1, f) != 1) {
return fsdevGetLastResult();
}
return ResultSuccess;
}
};
}

View File

@ -19,7 +19,7 @@
static constexpr u32 Module_Kvdb = 20;
static constexpr Result ResultKvdbTooLargeKey = MAKERESULT(Module_Kvdb, 1);
static constexpr Result ResultKvdbTooManyKeys = MAKERESULT(Module_Kvdb, 1);
static constexpr Result ResultKvdbKeyNotFound = MAKERESULT(Module_Kvdb, 2);
static constexpr Result ResultKvdbAllocationFailed = MAKERESULT(Module_Kvdb, 4);
static constexpr Result ResultKvdbInvalidKeyValue = MAKERESULT(Module_Kvdb, 5);

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include <stratosphere/kvdb/kvdb_archive.hpp>
namespace sts::kvdb {
namespace {
/* Convenience definitions. */
constexpr u8 ArchiveHeaderMagic[4] = {'I', 'M', 'K', 'V'};
constexpr u8 ArchiveEntryMagic[4] = {'I', 'M', 'E', 'N'};
/* Archive types. */
struct ArchiveHeader {
u8 magic[sizeof(ArchiveHeaderMagic)];
u32 pad;
u32 entry_count;
Result Validate() const {
if (std::memcmp(this->magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)) != 0) {
return ResultKvdbInvalidKeyValue;
}
return ResultSuccess;
}
static ArchiveHeader Make(size_t entry_count) {
ArchiveHeader header = {};
std::memcpy(header.magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic));
header.entry_count = static_cast<u32>(entry_count);
return header;
}
};
static_assert(sizeof(ArchiveHeader) == 0xC && std::is_pod<ArchiveHeader>::value, "ArchiveHeader definition!");
struct ArchiveEntryHeader {
u8 magic[sizeof(ArchiveEntryMagic)];
u32 key_size;
u32 value_size;
Result Validate() const {
if (std::memcmp(this->magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)) != 0) {
return ResultKvdbInvalidKeyValue;
}
return ResultSuccess;
}
static ArchiveEntryHeader Make(size_t ksz, size_t vsz) {
ArchiveEntryHeader header = {};
std::memcpy(header.magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic));
header.key_size = ksz;
header.value_size = vsz;
return header;
}
};
static_assert(sizeof(ArchiveEntryHeader) == 0xC && std::is_pod<ArchiveEntryHeader>::value, "ArchiveEntryHeader definition!");
}
/* Reader functionality. */
Result ArchiveReader::Peek(void *dst, size_t size) {
/* Bounds check. */
if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
return ResultKvdbInvalidKeyValue;
}
std::memcpy(dst, this->buffer.Get() + this->offset, size);
return ResultSuccess;
}
Result ArchiveReader::Read(void *dst, size_t size) {
R_TRY(this->Peek(dst, size));
this->offset += size;
return ResultSuccess;
}
Result ArchiveReader::ReadEntryCount(size_t *out) {
/* This should only be called at the start of reading stream. */
if (this->offset != 0) {
std::abort();
}
/* Read and validate header. */
ArchiveHeader header;
R_TRY(this->Read(&header, sizeof(header)));
R_TRY(header.Validate());
*out = header.entry_count;
return ResultSuccess;
}
Result ArchiveReader::GetEntrySize(size_t *out_key_size, size_t *out_value_size) {
/* This should only be called after ReadEntryCount. */
if (this->offset == 0) {
std::abort();
}
/* Peek the next entry header. */
ArchiveEntryHeader header;
R_TRY(this->Peek(&header, sizeof(header)));
R_TRY(header.Validate());
*out_key_size = header.key_size;
*out_value_size = header.value_size;
return ResultSuccess;
}
Result ArchiveReader::ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size) {
/* This should only be called after ReadEntryCount. */
if (this->offset == 0) {
std::abort();
}
/* Read the next entry header. */
ArchiveEntryHeader header;
R_TRY(this->Read(&header, sizeof(header)));
R_TRY(header.Validate());
/* Key size and Value size must be correct. */
if (key_size != header.key_size || value_size != header.value_size) {
std::abort();
}
R_ASSERT(this->Read(out_key, key_size));
R_ASSERT(this->Read(out_value, value_size));
return ResultSuccess;
}
/* Writer functionality. */
Result ArchiveWriter::Write(const void *src, size_t size) {
/* Bounds check. */
if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
return ResultKvdbInvalidKeyValue;
}
std::memcpy(this->buffer.Get() + this->offset, src, size);
this->offset += size;
return ResultSuccess;
}
void ArchiveWriter::WriteHeader(size_t entry_count) {
/* This should only be called at start of write. */
if (this->offset != 0) {
std::abort();
}
ArchiveHeader header = ArchiveHeader::Make(entry_count);
R_ASSERT(this->Write(&header, sizeof(header)));
}
void ArchiveWriter::WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size) {
/* This should only be called after writing header. */
if (this->offset == 0) {
std::abort();
}
ArchiveEntryHeader header = ArchiveEntryHeader::Make(key_size, value_size);
R_ASSERT(this->Write(&header, sizeof(header)));
R_ASSERT(this->Write(key, key_size));
R_ASSERT(this->Write(value, value_size));
}
/* Size helper functionality. */
ArchiveSizeHelper::ArchiveSizeHelper() : size(sizeof(ArchiveHeader)) {
/* ... */
}
void ArchiveSizeHelper::AddEntry(size_t key_size, size_t value_size) {
this->size += sizeof(ArchiveEntryHeader) + key_size + value_size;
}
}