From c3866ef632c3d06c44f77948d02458ac6a9e8d37 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Thu, 19 May 2022 10:01:15 +1000 Subject: [PATCH] serialise: add a simple binary serialisation framework --- CMakeLists.txt | 4 + serialise/converter.hpp | 61 +++++++++++++ serialise/ops.hpp | 50 +++++++++++ serialise/std.hpp | 193 ++++++++++++++++++++++++++++++++++++++++ test/serialise.cpp | 76 ++++++++++++++++ 5 files changed, 384 insertions(+) create mode 100644 serialise/converter.hpp create mode 100644 serialise/ops.hpp create mode 100644 serialise/std.hpp create mode 100644 test/serialise.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 02f94bf9..7c89e2dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -550,6 +550,9 @@ list ( registrar.hpp roots/bisection.hpp scoped.hpp + serialise/converter.hpp + serialise/ops.hpp + serialise/std.hpp set/dset.cpp set/dset.hpp signal.cpp @@ -782,6 +785,7 @@ if (TESTS) registrar roots/bisection scoped + serialise set/dset signal singleton diff --git a/serialise/converter.hpp b/serialise/converter.hpp new file mode 100644 index 00000000..c9060cf7 --- /dev/null +++ b/serialise/converter.hpp @@ -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 + */ + +#pragma once + +#include + +#include + + +namespace cruft::serialise { + template + struct converter { + static + cruft::view + to_bytes [[nodiscard]] ( + cruft::view dst, + ValueT const &src + ); + + static + ValueT + from_bytes (cruft::view &src); + + static std::size_t size (ValueT const&); + }; + + template struct converter : public converter {}; + template struct converter : public converter {}; + + template + requires (std::is_trivial_v) + struct converter { + static + cruft::view + to_bytes [[nodiscard]] ( + cruft::view dst, + ValueT const &src + ) { + if (dst.size () < sizeof (ValueT)) + throw std::bad_alloc (); + + return write (dst, src); + } + + static + ValueT + from_bytes (cruft::view &src) + { + return read (src); + } + + + static std::size_t size (ValueT const&) { return sizeof (ValueT); } + }; +} diff --git a/serialise/ops.hpp b/serialise/ops.hpp new file mode 100644 index 00000000..1449855d --- /dev/null +++ b/serialise/ops.hpp @@ -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 + */ + +#pragma once + +#include "./converter.hpp" + +#include + +#include + + +namespace cruft::serialise { + template + cruft::view + to_bytes [[nodiscard]] (cruft::view dst, ValueT const &src) + { + return converter::to_bytes (dst, src); + } + + template + cruft::view + to_bytes [[nodiscard]] (cruft::view dst, HeadT const &head, TailT&& ...tail) + { + dst = to_bytes (dst, head); + if constexpr (sizeof... (TailT)) + dst = to_bytes (dst, std::forward (tail)...); + return dst; + } + + + template + ValueT + from_bytes (cruft::view &src) + { + return converter::from_bytes (src); + } + + + template + std::size_t size (ArgsT &&...args) + { + return (converter::size (args) + ... + 0uz); + } +} diff --git a/serialise/std.hpp b/serialise/std.hpp new file mode 100644 index 00000000..9e910a45 --- /dev/null +++ b/serialise/std.hpp @@ -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 + */ + +#pragma once + +#include "./converter.hpp" +#include "./ops.hpp" + + +/////////////////////////////////////////////////////////////////////////////// +template +struct cruft::serialise::converter> { + using value_type = std::vector; + + 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::size (i); + return accum; + } + + static + cruft::view + to_bytes [[nodiscard]] ( + cruft::view 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 &src) + { + auto const len = ::cruft::serialise::from_bytes::size_type> (src); + + std::vector res; + res.reserve (len); + + for (std::size_t i = 0; i != len; ++i) + res.push_back (::cruft::serialise::from_bytes (src)); + + return res; + } +}; + + +/////////////////////////////////////////////////////////////////////////////// +template <> +struct cruft::serialise::converter { + 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 + to_bytes [[nodiscard]] ( + cruft::view 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 &src) + { + std::string res; + res.resize (::cruft::serialise::converter::from_bytes (src)); + for (auto &i: res) + i = ::cruft::serialise::converter::from_bytes (src); + return res; + } +}; + + +/////////////////////////////////////////////////////////////////////////////// +template +struct cruft::serialise::converter> { + using value_type = std::pair; + + static std::size_t + size (value_type const &val) + { + return converter::size (val.first) + converter::size (val.second); + } + + static + cruft::view + to_bytes [[nodiscard]] ( + cruft::view 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 &src) + { + return { + cruft::serialise::converter::from_bytes (src), + cruft::serialise::converter::from_bytes (src), + }; + } +}; + + +/////////////////////////////////////////////////////////////////////////////// +template +struct cruft::serialise::converter> { + using value_type = std::tuple; + + static std::size_t + size (value_type const &val) + { + return size (val, std::make_index_sequence {}); + } + + static + cruft::view + to_bytes [[nodiscard]] ( + cruft::view dst, + value_type const &src + ) { + return to_bytes (dst, src, std::make_index_sequence {}); + } + + static + value_type + from_bytes (cruft::view &src) + { + return from_bytes (src, std::make_index_sequence {}); + } + +private: + template + static std::size_t + size (value_type const &val, std::index_sequence const &) + { + return ( + converter< + std::tuple_element_t + >::size ( + std::get (val) + ) + ... + 0uz + ); + } + + + template + static + cruft::view + to_bytes [[nodiscard]] ( + cruft::view dst, + value_type const &src, + std::index_sequence const& + ) { + return ::cruft::serialise::to_bytes (dst, std::get (src)...); + } + + + template + static + value_type + from_bytes (cruft::view &src, std::index_sequence const&) + { + return std::tuple { + ::cruft::serialise::from_bytes (src)..., + }; + } +}; diff --git a/test/serialise.cpp b/test/serialise.cpp new file mode 100644 index 00000000..5a22102f --- /dev/null +++ b/test/serialise.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +static void +test_scalars (cruft::TAP::logger &tap) +{ + std::vector 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 (remain), 2.f, "scalar extract: f32"); + tap.expect_eq (cruft::serialise::from_bytes (remain), 3, "scalar extract: i16"); + tap.expect_eq (cruft::serialise::from_bytes (remain), 4, "scalar extract: char"); + tap.expect (remain.empty (), "scalar extract: empty"); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +template +static void +test_roundtrip (cruft::TAP::logger &tap, char const *label, ValueT const &val) +{ + std::vector 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 (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 (1, false)); + test_roundtrip (tap, "vector", std::vector { 1, 2, 3 }); + test_roundtrip ( + tap, + "tuple", + std::tuple ( + std::string ("this is a string"), + std::pair { 1, false }, + std::vector { 1, 2, 3 } + ) + ); +} + + +/////////////////////////////////////////////////////////////////////////////// +int main () +{ + cruft::TAP::logger tap; + + test_scalars (tap); + test_stdlib (tap); + + return tap.status (); +} \ No newline at end of file