mirror of https://github.com/oxen-io/oxen-core.git
337 lines
17 KiB
C++
337 lines
17 KiB
C++
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of the Andrey N. Sabelnikov nor the
|
|
// names of its contributors may be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
|
|
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <type_traits>
|
|
#include <set>
|
|
#include <unordered_set>
|
|
#include <list>
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <array>
|
|
#include <optional>
|
|
#include "../span.h"
|
|
#include "../storages/portable_storage_base.h"
|
|
|
|
#undef LOKI_DEFAULT_LOG_CATEGORY
|
|
#define LOKI_DEFAULT_LOG_CATEGORY "serialization"
|
|
|
|
namespace epee
|
|
{
|
|
namespace
|
|
{
|
|
template <typename T>
|
|
constexpr bool is_std_vector = false;
|
|
template <typename... T>
|
|
constexpr bool is_std_vector<std::vector<T...>> = true;
|
|
template <typename T>
|
|
constexpr bool is_std_optional = false;
|
|
template <typename T>
|
|
constexpr bool is_std_optional<std::optional<T>> = true;
|
|
}
|
|
namespace serialization
|
|
{
|
|
|
|
template <typename T, typename SFINAE = void> constexpr bool is_basic_serializable = false;
|
|
template <typename T> constexpr bool is_basic_serializable<T, std::enable_if_t<std::is_integral<T>::value>> = true;
|
|
template <typename T> constexpr bool is_basic_serializable<const T> = is_basic_serializable<T>;
|
|
template <typename T> constexpr bool is_basic_serializable<std::optional<T>> = is_basic_serializable<T>;
|
|
template <> inline constexpr bool is_basic_serializable<std::string> = true;
|
|
template <> inline constexpr bool is_basic_serializable<double> = true;
|
|
template <> inline constexpr bool is_basic_serializable<storage_entry> = true;
|
|
|
|
template <typename T> constexpr bool is_serialize_stl_container = false;
|
|
template <typename T> constexpr bool is_serialize_stl_container<std::vector<T>> = true;
|
|
template <typename T> constexpr bool is_serialize_stl_container<std::deque<T>> = true;
|
|
template <typename T> constexpr bool is_serialize_stl_container<std::list<T>> = true;
|
|
template <typename T> constexpr bool is_serialize_stl_container<std::set<T>> = true;
|
|
template <typename T> constexpr bool is_serialize_stl_container<std::unordered_set<T>> = true;
|
|
template <typename T, size_t S> constexpr bool is_serialize_stl_container<std::array<T, S>> = true;
|
|
|
|
// static_asserts that the type is suitable for binary serialization: by default, this means it
|
|
// has no padding and is trivially copyable. Types that are safe but don't satisfy these
|
|
// requirements can specialize is_byte_spannable.
|
|
template <typename T>
|
|
constexpr void assert_blob_serializable() {
|
|
static_assert(is_byte_spannable<T>, "Type is not acceptable for blob serialization");
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class t_type, class t_storage>
|
|
static bool serialize_t_val(const t_type& d, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
return stg.set_value(pname, d, parent_section);
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class t_type, class t_storage>
|
|
static bool unserialize_t_val(t_type& d, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
return stg.get_value(pname, d, parent_section);
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class t_type, class t_storage>
|
|
static bool serialize_t_val_as_blob(const t_type& d, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
assert_blob_serializable<t_type>();
|
|
std::string blob((const char *)&d, sizeof(d));
|
|
return stg.set_value(pname, blob, parent_section);
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class t_type, class t_storage>
|
|
static bool unserialize_t_val_as_blob(t_type& d, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
assert_blob_serializable<t_type>();
|
|
std::string blob;
|
|
if(!stg.get_value(pname, blob, parent_section))
|
|
return false;
|
|
CHECK_AND_ASSERT_MES(blob.size() == sizeof(d), false, "unserialize_t_val_as_blob: size of " << typeid(t_type).name() << " = " << sizeof(t_type) << ", but stored blod size = " << blob.size() << ", value name = " << pname);
|
|
d = *(const t_type*)blob.data();
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class serializible_type, class t_storage>
|
|
static bool serialize_t_obj(const serializible_type& obj, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
section* child_section = stg.open_section(pname, parent_section, true);
|
|
CHECK_AND_ASSERT_MES(child_section, false, "serialize_t_obj: failed to open/create section " << pname);
|
|
return obj.store(stg, child_section);
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class serializible_type, class t_storage>
|
|
static bool unserialize_t_obj(serializible_type& obj, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
section* child_section = stg.open_section(pname, parent_section, false);
|
|
if(!child_section) return false;
|
|
return obj._load(stg, child_section);
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
template<class stl_container, class t_storage>
|
|
static bool serialize_stl_container_t_val(const stl_container& container, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
using T = typename stl_container::value_type;
|
|
if(!container.size()) return true;
|
|
auto *arr = stg.template make_array_t<T>(pname, parent_section);
|
|
CHECK_AND_ASSERT_MES(arr, false, "failed to create array in storage");
|
|
for (auto& elem : container)
|
|
arr->push_back(elem);
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<class stl_container, class t_storage>
|
|
static bool unserialize_stl_container_t_val(stl_container& container, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
using T = typename stl_container::value_type;
|
|
container.clear();
|
|
try {
|
|
for (auto [it, end] = stg.template converting_array_range<T>(pname, parent_section); it != end; ++it)
|
|
container.insert(container.end(), *it);
|
|
return true;
|
|
} catch (const std::out_of_range&) { // ignore silently
|
|
} catch (const std::exception& e) {
|
|
LOG_ERROR("Failed to deserialize stl container: " << e.what());
|
|
}
|
|
return false;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<typename T, size_t Size, class t_storage>
|
|
static bool unserialize_stl_container_t_val(std::array<T, Size>& array, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
static_assert(Size > 0, "cannot deserialize empty std::array");
|
|
size_t next_i = 0;
|
|
for (auto [it, end] = stg.template converting_array_range<T>(pname, parent_section); it != end; ++it) {
|
|
CHECK_AND_ASSERT_MES(next_i < array.size(), false, "too many values to deserialize into fixed size std::array");
|
|
array[next_i++] = *it;
|
|
}
|
|
CHECK_AND_ASSERT_MES(next_i == array.size(), false, "not enough values to deserialize into fixed size std::array");
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<class stl_container, class t_storage>
|
|
static bool serialize_stl_container_pod_val_as_blob(const stl_container& container, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
using T = typename stl_container::value_type;
|
|
assert_blob_serializable<T>();
|
|
|
|
if(!container.size()) return true;
|
|
std::string mb;
|
|
if constexpr (is_std_vector<stl_container>)
|
|
mb.append(reinterpret_cast<const char*>(container.data()), sizeof(T) * container.size());
|
|
else
|
|
{
|
|
mb.reserve(sizeof(T) * container.size());
|
|
for (const auto &v : container)
|
|
mb.append(reinterpret_cast<const char*>(&v), sizeof(T));
|
|
}
|
|
return stg.set_value(pname, mb, parent_section);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<class stl_container, class t_storage>
|
|
static bool unserialize_stl_container_pod_val_as_blob(stl_container& container, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
using T = typename stl_container::value_type;
|
|
assert_blob_serializable<T>();
|
|
|
|
container.clear();
|
|
std::string buff;
|
|
if (!stg.get_value(pname, buff, parent_section))
|
|
return false;
|
|
|
|
CHECK_AND_ASSERT_MES(buff.size() % sizeof(T) == 0,
|
|
false,
|
|
"size in blob " << buff.size() << " not have not zero modulo for sizeof(value_type) = " << sizeof(T) << ", type " << typeid(T).name());
|
|
if constexpr (is_std_vector<stl_container>)
|
|
{
|
|
container.resize(buff.size() / sizeof(T));
|
|
// The explicit cast to (void*) is to silence a compiler warning about non-trivial types;
|
|
// we've already verified the byte copy is okay with the assert_blob_serializable<T> above.
|
|
std::memcpy((void*) container.data(), buff.data(), buff.size());
|
|
}
|
|
else
|
|
{
|
|
// memcpy one element at a time because we have no alignment guarantee on buff's data
|
|
for (size_t i = 0; i < buff.size(); i += sizeof(T))
|
|
std::memcpy(&container.emplace_back(), buff.data() + i, sizeof(T));
|
|
}
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<class stl_container, class t_storage>
|
|
static bool serialize_stl_container_t_obj(const stl_container& container, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
if (container.empty()) return true;
|
|
auto* sec_array = stg.template make_array_t<section>(pname, parent_section);
|
|
CHECK_AND_ASSERT_MES(sec_array, false, "failed to insert first section with section name " << pname);
|
|
|
|
for (auto& elem : container)
|
|
if (!elem.store(stg, &sec_array->emplace_back()))
|
|
return false;
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<class stl_container, class t_storage>
|
|
static bool unserialize_stl_container_t_obj(stl_container& container, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
container.clear();
|
|
auto* arr = stg.template get_array<section>(pname, parent_section);
|
|
if (!arr) return false;
|
|
for (auto& child_section : *arr)
|
|
if (!container.emplace_back()._load(stg, &child_section))
|
|
return false;
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template<typename T, size_t Size, class t_storage>
|
|
static bool unserialize_stl_container_t_obj(std::array<T, Size>& out, t_storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
static_assert(Size > 0, "cannot deserialize empty std::array");
|
|
auto* arr = stg.template get_array<section>(pname, parent_section);
|
|
if (!arr) return false;
|
|
CHECK_AND_ASSERT_MES(arr->size() != Size, false, "incorrect number of values to deserialize into fixed size std::array");
|
|
auto it = out.begin();
|
|
for (auto& child_section : *arr)
|
|
if (!(it++)->_load(stg, &child_section))
|
|
return false;
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------------
|
|
template <bool Serializing, typename T, typename Storage>
|
|
bool perform_serialize(T& d, Storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
if constexpr (Serializing)
|
|
return kv_serialize(d, stg, parent_section, pname);
|
|
else
|
|
return kv_unserialize(d, stg, parent_section, pname);
|
|
}
|
|
|
|
template <bool Serializing, typename T, typename Storage>
|
|
bool perform_serialize_blob(T& d, Storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
if constexpr (Serializing)
|
|
return serialize_t_val_as_blob(d, stg, parent_section, pname);
|
|
else
|
|
return unserialize_t_val_as_blob(d, stg, parent_section, pname);
|
|
}
|
|
|
|
template <bool Serializing, typename T, typename Storage>
|
|
bool perform_serialize_blob_container(T& d, Storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
if constexpr (Serializing)
|
|
return serialize_stl_container_pod_val_as_blob(d, stg, parent_section, pname);
|
|
else
|
|
return unserialize_stl_container_pod_val_as_blob(d, stg, parent_section, pname);
|
|
}
|
|
|
|
template<class T, class Storage>
|
|
bool kv_serialize(const T& d, Storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
if constexpr (is_std_optional<T>)
|
|
// Optional: only serialize if non-empty
|
|
return d ? kv_serialize(*d, stg, parent_section, pname) : false;
|
|
|
|
else if constexpr (!is_serialize_stl_container<T>)
|
|
{ // Non-container
|
|
if constexpr (is_basic_serializable<T>) // basic serializable or using portable storage:
|
|
return stg.set_value(pname, d, parent_section);
|
|
else // non-basic, non-portable serializable:
|
|
return serialize_t_obj(d, stg, parent_section, pname);
|
|
}
|
|
else if constexpr (is_basic_serializable<typename T::value_type>)
|
|
// stl container of basic or portable value type:
|
|
return serialize_stl_container_t_val(d, stg, parent_section, pname);
|
|
else
|
|
// stl containers (non-basic value type and non-portable storage), i.e. containers of custom
|
|
// serializable types.
|
|
return serialize_stl_container_t_obj(d, stg, parent_section, pname);
|
|
}
|
|
template<class T, class Storage>
|
|
bool kv_unserialize(T& d, Storage& stg, section* parent_section, const char* pname)
|
|
{
|
|
if constexpr (is_std_optional<T>) {
|
|
// Emplace a new value and try to deserialize into it
|
|
d = typename T::value_type{};
|
|
bool ret = kv_unserialize(*d, stg, parent_section, pname);
|
|
if (!ret) d.reset(); // Deserialization failed so clear the value
|
|
return ret;
|
|
}
|
|
else if constexpr (!is_serialize_stl_container<T>)
|
|
{ // Non-container
|
|
if constexpr (is_basic_serializable<T>) // basic serializable or using portable storage:
|
|
return stg.get_value(pname, d, parent_section);
|
|
else // non-basic, non-portable serializable:
|
|
return unserialize_t_obj(d, stg, parent_section, pname);
|
|
}
|
|
else if constexpr (is_basic_serializable<typename T::value_type>)
|
|
// stl container of basic or portable value type:
|
|
return unserialize_stl_container_t_val(d, stg, parent_section, pname);
|
|
else
|
|
// stl containers (non-basic value type and non-portable storage), i.e. containers of custom
|
|
// serializable types.
|
|
return unserialize_stl_container_t_obj(d, stg, parent_section, pname);
|
|
}
|
|
}
|
|
}
|