serialise: add a simple binary serialisation framework

This commit is contained in:
Danny Robson 2022-05-19 10:01:15 +10:00
parent dc46dc7c91
commit c3866ef632
5 changed files with 384 additions and 0 deletions

View File

@ -550,6 +550,9 @@ list (
registrar.hpp registrar.hpp
roots/bisection.hpp roots/bisection.hpp
scoped.hpp scoped.hpp
serialise/converter.hpp
serialise/ops.hpp
serialise/std.hpp
set/dset.cpp set/dset.cpp
set/dset.hpp set/dset.hpp
signal.cpp signal.cpp
@ -782,6 +785,7 @@ if (TESTS)
registrar registrar
roots/bisection roots/bisection
scoped scoped
serialise
set/dset set/dset
signal signal
singleton singleton

61
serialise/converter.hpp Normal file
View File

@ -0,0 +1,61 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include <cruft/util/view.hpp>
#include <cstddef>
namespace cruft::serialise {
template <typename ValueT>
struct converter {
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
ValueT const &src
);
static
ValueT
from_bytes (cruft::view<std::byte*> &src);
static std::size_t size (ValueT const&);
};
template <typename ValueT> struct converter<ValueT const> : public converter<ValueT> {};
template <typename ValueT> struct converter<ValueT &> : public converter<ValueT> {};
template <typename ValueT>
requires (std::is_trivial_v<ValueT>)
struct converter<ValueT> {
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
ValueT const &src
) {
if (dst.size () < sizeof (ValueT))
throw std::bad_alloc ();
return write (dst, src);
}
static
ValueT
from_bytes (cruft::view<std::byte*> &src)
{
return read<ValueT> (src);
}
static std::size_t size (ValueT const&) { return sizeof (ValueT); }
};
}

50
serialise/ops.hpp Normal file
View File

@ -0,0 +1,50 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "./converter.hpp"
#include <cruft/util/view.hpp>
#include <cstddef>
namespace cruft::serialise {
template <typename ValueT>
cruft::view<std::byte*>
to_bytes [[nodiscard]] (cruft::view<std::byte*> dst, ValueT const &src)
{
return converter<ValueT>::to_bytes (dst, src);
}
template <typename HeadT, typename ...TailT>
cruft::view<std::byte*>
to_bytes [[nodiscard]] (cruft::view<std::byte*> dst, HeadT const &head, TailT&& ...tail)
{
dst = to_bytes (dst, head);
if constexpr (sizeof... (TailT))
dst = to_bytes (dst, std::forward<TailT> (tail)...);
return dst;
}
template <typename ValueT>
ValueT
from_bytes (cruft::view<std::byte*> &src)
{
return converter<ValueT>::from_bytes (src);
}
template <typename ...ArgsT>
std::size_t size (ArgsT &&...args)
{
return (converter<ArgsT>::size (args) + ... + 0uz);
}
}

193
serialise/std.hpp Normal file
View File

@ -0,0 +1,193 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "./converter.hpp"
#include "./ops.hpp"
///////////////////////////////////////////////////////////////////////////////
template <typename ElementT>
struct cruft::serialise::converter<std::vector<ElementT>> {
using value_type = std::vector<ElementT>;
static std::size_t
size (value_type const &val)
{
std::size_t accum = sizeof (typename value_type::size_type);
for (auto const &i: val)
accum += converter<ElementT>::size (i);
return accum;
}
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
value_type const &src
) {
dst = ::cruft::serialise::to_bytes (dst, src.size ());
for (auto const &i: src)
dst = ::cruft::serialise::to_bytes (dst, i);
return dst;
}
static
value_type
from_bytes (cruft::view<std::byte*> &src)
{
auto const len = ::cruft::serialise::from_bytes<typename std::vector<ElementT>::size_type> (src);
std::vector<ElementT> res;
res.reserve (len);
for (std::size_t i = 0; i != len; ++i)
res.push_back (::cruft::serialise::from_bytes<ElementT> (src));
return res;
}
};
///////////////////////////////////////////////////////////////////////////////
template <>
struct cruft::serialise::converter<std::string> {
using value_type = std::string;
static std::size_t
size (value_type const &val)
{
return sizeof (value_type::size_type) + sizeof (value_type::value_type) * val.size ();
}
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
value_type const &src
) {
dst = ::cruft::serialise::to_bytes (dst, src.size ());
for (auto const &i: src)
dst = ::cruft::serialise::to_bytes (dst, i);
return dst;
}
static
value_type
from_bytes (cruft::view<std::byte*> &src)
{
std::string res;
res.resize (::cruft::serialise::converter<std::string::size_type>::from_bytes (src));
for (auto &i: res)
i = ::cruft::serialise::converter<std::string::value_type>::from_bytes (src);
return res;
}
};
///////////////////////////////////////////////////////////////////////////////
template <typename A, typename B>
struct cruft::serialise::converter<std::pair<A, B>> {
using value_type = std::pair<A, B>;
static std::size_t
size (value_type const &val)
{
return converter<A>::size (val.first) + converter<B>::size (val.second);
}
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
value_type const &src
) {
dst = ::cruft::serialise::to_bytes (dst, src.first);
dst = ::cruft::serialise::to_bytes (dst, src.second);
return dst;
}
static
value_type
from_bytes (cruft::view<std::byte*> &src)
{
return {
cruft::serialise::converter<A>::from_bytes (src),
cruft::serialise::converter<B>::from_bytes (src),
};
}
};
///////////////////////////////////////////////////////////////////////////////
template <typename ...ElementT>
struct cruft::serialise::converter<std::tuple<ElementT...>> {
using value_type = std::tuple<ElementT...>;
static std::size_t
size (value_type const &val)
{
return size (val, std::make_index_sequence<sizeof ...(ElementT)> {});
}
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
value_type const &src
) {
return to_bytes (dst, src, std::make_index_sequence<sizeof... (ElementT)> {});
}
static
value_type
from_bytes (cruft::view<std::byte*> &src)
{
return from_bytes (src, std::make_index_sequence<sizeof... (ElementT)> {});
}
private:
template <std::size_t ...Index>
static std::size_t
size (value_type const &val, std::index_sequence<Index...> const &)
{
return (
converter<
std::tuple_element_t<Index, value_type>
>::size (
std::get<Index> (val)
) + ... + 0uz
);
}
template <std::size_t ...IndexV>
static
cruft::view<std::byte*>
to_bytes [[nodiscard]] (
cruft::view<std::byte*> dst,
value_type const &src,
std::index_sequence<IndexV...> const&
) {
return ::cruft::serialise::to_bytes (dst, std::get<IndexV> (src)...);
}
template <std::size_t ...IndexV>
static
value_type
from_bytes (cruft::view<std::byte*> &src, std::index_sequence<IndexV...> const&)
{
return std::tuple {
::cruft::serialise::from_bytes<ElementT> (src)...,
};
}
};

76
test/serialise.cpp Normal file
View File

@ -0,0 +1,76 @@
#include <cruft/util/tap.hpp>
#include <cruft/util/serialise/ops.hpp>
#include <cruft/util/serialise/std.hpp>
///////////////////////////////////////////////////////////////////////////////
static void
test_scalars (cruft::TAP::logger &tap)
{
std::vector<std::byte> store (sizeof (f32) + sizeof (i16) + sizeof (char));
{
auto remain = cruft::serialise::to_bytes (store, 2.f, i16 (3), char (4));
tap.expect (remain.empty (), "scalar serialise: no remainder");
}
{
cruft::view remain (store);
tap.expect_eq (cruft::serialise::from_bytes<f32> (remain), 2.f, "scalar extract: f32");
tap.expect_eq (cruft::serialise::from_bytes<i16> (remain), 3, "scalar extract: i16");
tap.expect_eq (cruft::serialise::from_bytes<char> (remain), 4, "scalar extract: char");
tap.expect (remain.empty (), "scalar extract: empty");
}
}
///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
static void
test_roundtrip (cruft::TAP::logger &tap, char const *label, ValueT const &val)
{
std::vector<std::byte> raw (cruft::serialise::size (val));
{
cruft::view remain (raw);
remain = cruft::serialise::to_bytes (remain, val);
tap.expect (remain.empty (), "round_trip {}: serialise", label);
}
{
cruft::view remain (raw);
auto const extracted = cruft::serialise::from_bytes<ValueT> (remain);
tap.expect_eq (extracted, val, "round_trip {}: extract", label);
}
}
///////////////////////////////////////////////////////////////////////////////
static void
test_stdlib (cruft::TAP::logger &tap)
{
test_roundtrip (tap, "string", std::string ("this is a string"));
test_roundtrip (tap, "pair", std::pair<int, bool> (1, false));
test_roundtrip (tap, "vector", std::vector<i16> { 1, 2, 3 });
test_roundtrip (
tap,
"tuple",
std::tuple (
std::string ("this is a string"),
std::pair<int, bool> { 1, false },
std::vector<i16> { 1, 2, 3 }
)
);
}
///////////////////////////////////////////////////////////////////////////////
int main ()
{
cruft::TAP::logger tap;
test_scalars (tap);
test_stdlib (tap);
return tap.status ();
}