// // Created by Stepan Usatiuk on 15.04.2023. // #ifndef SEMBACKUP_SERIALIZE_H #define SEMBACKUP_SERIALIZE_H #include #include #include #include #include #include #ifdef __APPLE__ #include #define htobe64(x) htonll(x) #define be64toh(x) ntohll(x) #else #include #endif #include "../Exception.h" /// 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. * One problem is that it isn't really portable, but it can be fixed by changing the std::is_integral::value case to use something like be64toh/htobe64 * */ namespace Serialize { template struct is_pair : std::false_type {}; template struct is_pair().first)>, std::void_t().second)>> : 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 T deserialize(std::vector::const_iterator &in, const std::vector::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, std::vector &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 std::vector 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 T deserialize(const std::vector &from); template T deserialize(std::vector::const_iterator &in, const std::vector::const_iterator &end) { if (in >= end) throw Exception("Unexpected end of object!"); 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 (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 throw Exception("Enum out of range!"); } else if constexpr (sizeof(T) == 1) { // If it's a single byte, just copy it if (std::distance(in, end) < sizeof(T)) throw Exception("Unexpected end of object!"); return *(in++); } else if constexpr (std::is_integral::value) { uint64_t tmp; static_assert(sizeof(tmp) == 8); // If the object is a number, copy it byte-by-byte if (std::distance(in, end) < sizeof(tmp)) throw Exception("Unexpected end of object!"); std::copy(in, in + sizeof(tmp), reinterpret_cast(&tmp)); in += sizeof(tmp); return static_cast(be64toh(tmp)); } else { // Otherwise we treat it as a container, in format of be size_t size = deserialize(in, end); char b = deserialize(in, end); if (b != 'b') throw Exception("Error deserializing!"); T out; if constexpr (sizeof(typename T::value_type) == 1) { // Optimization for char vectors if (std::distance(in, end) < size) throw Exception("Unexpected end of object!"); out.insert(out.end(), in, in + size); in += 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 != 'e') throw Exception("Error deserializing!"); return out; } } template void serialize(const T &what, std::vector &out) { if constexpr (serializable::value) { // If the object declares itself as serializable, call its serialize method what.serialize(out); } 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.emplace_back(what); } else if constexpr (std::is_integral::value) { // 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 std::vector serialize(const T &o) { std::vector out; serialize(o, out); return out; } template T deserialize(const std::vector &from) { auto bgwr = from.cbegin(); return deserialize(bgwr, from.cend()); } }// namespace Serialize #endif//SEMBACKUP_SERIALIZE_H