// // Created by Stepan Usatiuk on 15.04.2023. // #ifndef SEMBACKUP_SERIALIZE_H #define SEMBACKUP_SERIALIZE_H #include #include #include #include #include #include "Exception.h" #include "stuff.hpp" /// Serialization library /** * To serialize the objects in Repository, we have to handle a couple of cases: * 1. Serializing integers (object ids, etc...) * 2. Serializing enums (object types) * 3. Serializing char vectors and strings * 4. Serializing other STL containers (which also requires serializing pairs) * 5. Serializing custom structs (including the objects themselves) * * With this library it is possible to do all of that. * */ namespace Serialize { template struct is_pair : std::false_type {}; template struct is_pair().first)>, std::void_t().second)>> : std::true_type {}; template class T> struct is_template_of : std::false_type {}; template class T> struct is_template_of, T> : std::true_type {}; template struct has_emplace_back : std::false_type {}; template struct has_emplace_back()))>> : std::true_type {}; template struct serializable : std::false_type {}; /// Checks if the object has the `serializable` type /// In that case, its serialization will be delegated to its .serialize() parameter, /// and deserialization to its T(char vector iterator in, const char vector iterator end) constructor, /// similar to Serialize::deserialize template struct serializable> : std::true_type {}; /// Deserializes object of type \p T starting from fist byte \p in, advances the iterator past the end of object /// \tparam T Type to deserialize /// \param in Iterator to the first byte of the object /// \param end End iterator of source container /// \return Deserialized value template> static std::optional deserializeOpt(typename C::const_iterator& in, const typename C::const_iterator& end); /// Deserializes object of type \p T starting from fist byte \p in, advances the iterator past the end of object /// \tparam T Type to deserialize /// \param in Iterator to the first byte of the object /// \param end End iterator of source container /// \return Deserialized value template> static T deserialize(typename C::const_iterator& in, const typename C::const_iterator& end); /// Serializes object of type \p T into vector \p out /// \tparam T Type to serialize /// \param what Constant reference to the serialized object /// \param out Reference to output vector template> static void serialize(const T& what, C& out); /// Serializes the object of type \p T and returns the resulting vector /// \tparam T Type to serialize /// \param o Constant reference to the serialized object /// \return Serialized data template> static C serialize(const T& o); /// Deserializes object of type \p T from input vector \p from /// \tparam T Type to deserialize /// \param from Constant reference to the serialized object /// \return Deserialized value template> static std::optional deserializeOpt(const C& from); /// Deserializes object of type \p T from input vector \p from /// \tparam T Type to deserialize /// \param from Constant reference to the serialized object /// \return Deserialized value template> static T deserialize(const C& from); // Deserializes an std::variant instance, with readIdx serving as an index of the alternative on the wire template std::optional deserializeVar(size_t readIdx, typename C::const_iterator& in, const typename C::const_iterator& end) requires is_template_of::value { if constexpr (I < std::variant_size_v) { if (readIdx == I) { auto res = deserializeOpt>(in, end); if (res) return std::make_optional(V{std::move(*res)}); return std::nullopt; } return deserializeVar(readIdx, in, end); } else { return std::nullopt; } } template std::optional deserializeOpt(typename C::const_iterator& in, const typename C::const_iterator& end) { if constexpr (serializable::value) { // If the object declares itself as serializable, call its constructor with in and end return T(in, end); } else if constexpr (std::is_same_v) { // No-op return std::make_optional(std::monostate{}); } else { if (in >= end) return std::nullopt; if constexpr (is_pair::value) { // If the object is pair, deserialize the first and second element and return the pair using KT = typename std::remove_const::type; using VT = typename std::remove_const::type; auto K = deserialize(in, end); auto V = deserialize(in, end); return T(std::move(K), std::move(V)); } else if constexpr (std::is_enum::value) { // If the object is an enum, deserialize an int and cast it to the enum auto tmp = deserialize(in, end); if (tmp >= 0 && tmp < static_cast(T::END)) return static_cast(tmp); else return std::nullopt; } else if constexpr (sizeof(T) == 1) { // If it's a single byte, just copy it if (std::distance(in, end) < checked_cast(sizeof(T))) return std::nullopt; return *(in++); } else if constexpr (std::is_integral::value) { static_assert(sizeof(T) <= sizeof(uint64_t)); uint64_t tmp; // If the object is a number, copy it byte-by-byte if (std::distance(in, end) < checked_cast(sizeof(tmp))) return std::nullopt; std::copy(in, in + sizeof(tmp), reinterpret_cast(&tmp)); in += sizeof(tmp); return static_cast(be64toh(tmp)); } else if constexpr (is_template_of::value) { auto index = deserializeOpt(in, end); if (!index.has_value()) return std::nullopt; return deserializeVar<0, C, T>(*index - 1, in, end); } else { // Otherwise we treat it as a container, in format of be auto size = deserializeOpt(in, end); if (!size) return std::nullopt; auto b = deserialize(in, end); if (!b || b != 'b') return std::nullopt; T out; if constexpr (sizeof(typename T::value_type) == 1) { // Optimization for char vectors if (std::distance(in, end) < checked_cast(*size)) return std::nullopt; out.insert(out.end(), in, in + checked_cast(*size)); in += checked_cast(*size); } else for (size_t i = 0; i < *size; i++) { using V = typename T::value_type; V v = deserialize(in, end); // Try either emplace_back or emplace if it doesn't exist if constexpr (has_emplace_back::value) out.emplace_back(std::move(v)); else out.emplace(std::move(v)); } b = deserialize(in, end); if (!b || b != 'e') return std::nullopt; return out; } } } template static T deserialize(typename C::const_iterator& in, const typename C::const_iterator& end) { std::optional out = deserializeOpt(in, end); if (!out) throw Exception("deserialize failed"); return out.value(); } template static T deserialize(const C& from) { std::optional out = deserializeOpt(from); if (!out) throw Exception("deserialize failed"); return out.value(); } template void serialize(const std::variant& what, C& out) { serialize(what.index() + 1, out); std::visit( [&out](auto&& arg) -> void { using I = std::decay_t; serialize(arg, out); }, what); } template void serialize(const T& what, C& out) { if constexpr (serializable::value) { // If the object declares itself as serializable, call its serialize method what.serialize(out); } else if constexpr (std::is_same_v) { // No-op } else if constexpr (is_pair::value) { // If the object is pair, serialize the first and second element serialize(what.first, out); serialize(what.second, out); } else if constexpr (std::is_enum::value) { // If the object is an enum, cast it to an int and serialize that serialize(static_cast(what), out); } else if constexpr (sizeof(T) == 1) { // If it's a single byte, just copy it out.push_back(static_cast(what)); } else if constexpr (std::is_integral::value) { static_assert(sizeof(T) <= sizeof(uint64_t)); // If the object is a number, copy it byte-by-byte uint64_t tmp = htobe64(static_cast(what)); static_assert(sizeof(tmp) == 8); out.insert(out.end(), (reinterpret_cast(&tmp)), (reinterpret_cast(&tmp) + sizeof(tmp))); } else { // Otherwise we treat it as a container, in format of be serialize(what.size(), out); serialize('b', out); if constexpr (sizeof(typename T::value_type) == 1) { // Optimization for char vectors out.insert(out.end(), what.begin(), what.end()); } else for (auto const& i: what) { serialize(i, out); } serialize('e', out); } } template C serialize(const T& o) { C out; serialize(o, out); return out; } template std::optional deserializeOpt(const C& from) { auto bgwr = from.begin(); return deserialize(bgwr, from.end()); } } // namespace Serialize #endif // SEMBACKUP_SERIALIZE_H