From 99ebbd2a186924d88dfd6e6913db6be41d9a0284 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Sat, 13 Jul 2019 21:35:51 -0700 Subject: [PATCH] kvdb: implement MemoryKeyValueStore --- Makefile | 2 +- include/stratosphere/kvdb/kvdb_archive.hpp | 68 +++ .../stratosphere/kvdb/kvdb_auto_buffer.hpp | 88 +++ .../stratosphere/kvdb/kvdb_bounded_string.hpp | 152 +++++ .../kvdb/kvdb_memory_key_value_store.hpp | 566 ++++++++++++++++++ include/stratosphere/results/kvdb_results.hpp | 2 +- source/kvdb/kvdb_archive.cpp | 186 ++++++ 7 files changed, 1062 insertions(+), 2 deletions(-) create mode 100644 include/stratosphere/kvdb/kvdb_archive.hpp create mode 100644 include/stratosphere/kvdb/kvdb_auto_buffer.hpp create mode 100644 include/stratosphere/kvdb/kvdb_bounded_string.hpp create mode 100644 include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp create mode 100644 source/kvdb/kvdb_archive.cpp diff --git a/Makefile b/Makefile index 26e4c3b3..ea746be8 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/include/stratosphere/kvdb/kvdb_archive.hpp b/include/stratosphere/kvdb/kvdb_archive.hpp new file mode 100644 index 00000000..64fe074d --- /dev/null +++ b/include/stratosphere/kvdb/kvdb_archive.hpp @@ -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 . + */ + +#pragma once +#include +#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; + } + }; + +} \ No newline at end of file diff --git a/include/stratosphere/kvdb/kvdb_auto_buffer.hpp b/include/stratosphere/kvdb/kvdb_auto_buffer.hpp new file mode 100644 index 00000000..6fe115ad --- /dev/null +++ b/include/stratosphere/kvdb/kvdb_auto_buffer.hpp @@ -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 . + */ + +#pragma once +#include +#include "../defines.hpp" +#include "../results.hpp" + +namespace sts::kvdb { + + class AutoBuffer { + NON_COPYABLE(AutoBuffer); + private: + std::unique_ptr 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; + } + }; +} \ No newline at end of file diff --git a/include/stratosphere/kvdb/kvdb_bounded_string.hpp b/include/stratosphere/kvdb/kvdb_bounded_string.hpp new file mode 100644 index 00000000..16c02ef0 --- /dev/null +++ b/include/stratosphere/kvdb/kvdb_bounded_string.hpp @@ -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 . + */ + +#pragma once +#include +#include +#include +#include "../defines.hpp" +#include "../results.hpp" + +namespace sts::kvdb { + + /* Represents a string with a backing buffer of N bytes. */ + template + 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 Make(const char *s) { + return BoundedString(s); + } + + static BoundedString MakeFormat(const char *format, ...) __attribute__((format (printf, 1, 2))) { + BoundedString 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 GetSubstring(size_t offset, size_t length) const { + BoundedString string; + GetSubstring(string.buffer, N, offset, length); + return string; + } + + /* Comparison. */ + constexpr bool operator==(const BoundedString &rhs) const { + return std::strncmp(this->buffer, rhs.buffer, N) == 0; + } + + constexpr bool operator!=(const BoundedString &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); + } + }; + +} \ No newline at end of file diff --git a/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp b/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp new file mode 100644 index 00000000..984fe5f8 --- /dev/null +++ b/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp @@ -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 . + */ + +#pragma once +#include +#include +#include +#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 MemoryKeyValueStore { + static_assert(std::is_pod::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 + Value *GetValuePointer() { + /* Size check. Note: Nintendo does not size check. */ + if constexpr (!std::is_same::value) { + if (sizeof(Value) > this->value_size) { + std::abort(); + } + /* Ensure we only get pod. */ + static_assert(std::is_pod::value, "KeyValueStore Values must be pod"); + } + return reinterpret_cast(this->value); + } + + template + const Value *GetValuePointer() const { + /* Size check. Note: Nintendo does not size check. */ + if constexpr (!std::is_same::value) { + if (sizeof(Value) > this->value_size) { + std::abort(); + } + /* Ensure we only get pod. */ + static_assert(std::is_pod::value, "KeyValueStore Values must be pod"); + } + return reinterpret_cast(this->value); + } + + template + Value &GetValue() { + return *(this->GetValuePointer()); + } + + template + const Value &GetValue() const { + return *(this->GetValuePointer()); + } + + 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(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; + 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 + Result Set(const Key &key, const Value &value) { + /* Only allow setting pod. */ + static_assert(std::is_pod::value, "KeyValueStore Values must be pod"); + return this->Set(key, &value, sizeof(Value)); + } + + template + Result Set(const Key &key, const Value *value) { + /* Only allow setting pod. */ + static_assert(std::is_pod::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 + 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(); + return ResultSuccess; + } + + template + 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(); + return ResultSuccess; + } + + template + 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(); + 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; + } + }; + +} \ No newline at end of file diff --git a/include/stratosphere/results/kvdb_results.hpp b/include/stratosphere/results/kvdb_results.hpp index 8248f8f2..97351914 100644 --- a/include/stratosphere/results/kvdb_results.hpp +++ b/include/stratosphere/results/kvdb_results.hpp @@ -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); diff --git a/source/kvdb/kvdb_archive.cpp b/source/kvdb/kvdb_archive.cpp new file mode 100644 index 00000000..503f155f --- /dev/null +++ b/source/kvdb/kvdb_archive.cpp @@ -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 . + */ + +#include +#include + +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(entry_count); + return header; + } + }; + static_assert(sizeof(ArchiveHeader) == 0xC && std::is_pod::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::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; + } + +} \ No newline at end of file