Program Listing for File SerializeMe.hpp
↰ Return to documentation for file (/tmp/ws/src/data_tamer/data_tamer_cpp/include/data_tamer/contrib/SerializeMe.hpp
)
#pragma once
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>
namespace SerializeMe
{
// Poor man version of Span
template <typename T>
class Span
{
public:
Span() = default;
Span(T* ptr, size_t size) : data_(ptr), size_(size) {}
template <size_t N>
Span(std::array<T, N>& v) : data_(v.data()), size_(N)
{}
Span(std::vector<T>& v) : data_(v.data()), size_(v.size()) {}
T const* data() const;
T* data();
size_t size() const;
void trimFront(size_t offset);
private:
T* data_ = nullptr;
size_t size_ = 0;
};
using SpanBytes = Span<uint8_t>;
using SpanBytesConst = Span<uint8_t const>;
using StringSize = uint16_t;
//------------- Forward declarations of BufferSize ------------------
template <typename T>
size_t BufferSize(const T& val);
template <>
size_t BufferSize(const std::string& str);
template <class T, size_t N>
size_t BufferSize(const std::array<T, N>& v);
template <template <class, class> class Container, class T, class... TArgs>
size_t BufferSize(const Container<T, TArgs...>& vect);
//---------- Forward declarations of DeserializeFromBuffer -----------
template <typename T>
void DeserializeFromBuffer(SpanBytesConst& buffer, T& dest);
template <>
void DeserializeFromBuffer(SpanBytesConst& buffer, std::string& str);
template <class T, size_t N>
void DeserializeFromBuffer(SpanBytesConst& buffer, std::array<T, N>& v);
template <template <class, class> class Container, class T, class... TArgs>
void DeserializeFromBuffer(SpanBytesConst& buffer, Container<T, TArgs...>& dest);
//---------- Forward declarations of SerializeIntoBuffer -----------
template <typename T>
void SerializeIntoBuffer(SpanBytes& buffer, const T& value);
template <>
void SerializeIntoBuffer(SpanBytes& buffer, const std::string& str);
template <class T, size_t N>
void SerializeIntoBuffer(SpanBytes& buffer, const std::array<T, N>& v);
template <template <class, class> class Container, class T, class... TArgs>
void SerializeIntoBuffer(SpanBytes& buffer, const Container<T, TArgs...>& vect);
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
template <typename T>
inline T const* Span<T>::data() const
{
return data_;
}
template <typename T>
inline T* Span<T>::data()
{
return data_;
}
template <typename T>
inline size_t Span<T>::size() const
{
return size_;
}
template <typename T>
inline void Span<T>::trimFront(size_t offset)
{
if (offset > size_)
{
throw std::runtime_error("Buffer overrun");
}
size_ -= offset;
data_ += offset;
}
// The wire format uses a little endian encoding (since that's efficient for
// the common platforms).
#if defined(__s390x__)
#define SERIALIZE_LITTLEENDIAN 0
#endif // __s390x__
#if !defined(SERIALIZE_LITTLEENDIAN)
#if defined(__GNUC__) || defined(__clang__) || defined(__ICCARM__)
#if (defined(__BIG_ENDIAN__) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
#define SERIALIZE_LITTLEENDIAN 0
#else
#define SERIALIZE_LITTLEENDIAN 1
#endif // __BIG_ENDIAN__
#elif defined(_MSC_VER)
#if defined(_M_PPC)
#define SERIALIZE_LITTLEENDIAN 0
#else
#define SERIALIZE_LITTLEENDIAN 1
#endif
#else
#error Unable to determine endianness, define SERIALIZE_LITTLEENDIAN.
#endif
#endif // !defined(SERIALIZE_LITTLEENDIAN)
template <typename T>
inline T EndianSwap(T t)
{
static_assert(std::is_arithmetic<T>::value, "This function accepts only numeric types");
#if defined(_MSC_VER)
#define DESERIALIZE_ME_BYTESWAP16 _byteswap_ushort
#define DESERIALIZE_ME_BYTESWAP32 _byteswap_ulong
#define DESERIALIZE_ME_BYTESWAP64 _byteswap_uint64
#else
#if defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ < 408 && !defined(__clang__)
// __builtin_bswap16 was missing prior to GCC 4.8.
#define DESERIALIZE_ME_BYTESWAP16(x) \
static_cast<uint16_t>(__builtin_bswap32(static_cast<uint32_t>(x) << 16))
#else
#define DESERIALIZE_ME_BYTESWAP16 __builtin_bswap16
#endif
#define DESERIALIZE_ME_BYTESWAP32 __builtin_bswap32
#define DESERIALIZE_ME_BYTESWAP64 __builtin_bswap64
#endif
if constexpr (sizeof(T) == 1)
{ // Compile-time if-then's.
return t;
}
else if constexpr (sizeof(T) == 2)
{
union
{
T t;
uint16_t i;
} u;
u.t = t;
u.i = DESERIALIZE_ME_BYTESWAP16(u.i);
return u.t;
}
else if constexpr (sizeof(T) == 4)
{
union
{
T t;
uint32_t i;
} u;
u.t = t;
u.i = DESERIALIZE_ME_BYTESWAP32(u.i);
return u.t;
}
else if (sizeof(T) == 8)
{
union
{
T t;
uint64_t i;
} u;
u.t = t;
u.i = DESERIALIZE_ME_BYTESWAP64(u.i);
return u.t;
}
else
{
std::runtime_error("Problem with IndianSwap");
}
}
template <class Type>
struct TypeDefinition
{
std::string typeName() const;
template <class Function>
void typeDef(const Type& obj, Function& addField);
};
template <typename T, class = void>
struct is_serializer_specialized : std::false_type
{
};
template <typename T>
struct is_serializer_specialized<T, decltype(TypeDefinition<T>(), void())>
: std::true_type
{
};
template <typename T>
inline constexpr bool is_number()
{
return std::is_arithmetic_v<T> || std::is_same_v<T, std::byte> || std::is_enum_v<T>;
}
template <typename _Tp, bool _is_container, int _size>
struct container_info_
{
static constexpr bool is_container = _is_container;
static constexpr int size = _size;
typedef _Tp value_type;
};
template <typename T>
struct container_info : container_info_<T, false, -1>
{
};
template <template <class, class> class Container, class T, class... TArgs>
struct container_info<Container<T, TArgs...>> : container_info_<T, true, 0>
{
};
template <typename T, size_t S>
struct container_info<std::array<T, S>> : container_info_<T, true, int(S)>
{
};
template <typename>
struct is_std_vector : std::false_type
{
};
template <typename T, typename... TArgs>
struct is_std_vector<std::vector<T, TArgs...>> : std::true_type
{
};
template <typename>
struct is_std_array : std::false_type
{
const static size_t Size = 0;
};
template <typename T, size_t S>
struct is_std_array<std::array<T, S>> : std::true_type
{
const static size_t Size = S;
};
template <typename T>
inline constexpr bool is_vector()
{
return (is_std_vector<T>::value || is_std_array<T>::value);
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
template <typename T>
inline size_t BufferSize(const T& val)
{
if constexpr (is_number<T>())
{
return sizeof(T);
}
else
{
size_t total_size = 0;
auto func = [&total_size](const char*, auto const* field) {
total_size += BufferSize(*field);
};
TypeDefinition<T>().typeDef(val, func);
return total_size;
}
}
template <>
inline size_t BufferSize(const std::string& str)
{
return sizeof(StringSize) + str.size();
}
template <class T, size_t N>
inline size_t BufferSize(const std::array<T, N>&)
{
return BufferSize(T{}) * N;
}
template <template <class, class> class Container, class T, class... TArgs>
inline size_t BufferSize(const Container<T, TArgs...>& vect)
{
if constexpr (std::is_trivially_copyable_v<T> && is_vector<Container<T, TArgs...>>())
{
return sizeof(uint32_t) + vect.size() * sizeof(T);
}
else
{
auto size = sizeof(uint32_t);
for (const auto& v : vect)
{
size += BufferSize(v);
}
return size;
}
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
template <typename T>
inline void DeserializeFromBuffer(SpanBytesConst& buffer, T& dest)
{
if constexpr (std::is_arithmetic_v<T> || std::is_same_v<T, std::byte>)
{
auto const S = sizeof(T);
if (S > buffer.size())
{
throw std::runtime_error("DeserializeFromBuffer: buffer overflow");
}
dest = *(reinterpret_cast<T const*>(buffer.data()));
#if SERIALIZE_LITTLEENDIAN == 0
dest = EndianSwap<T>(dest);
#endif
buffer = SpanBytesConst(buffer.data() + S, buffer.size() - S); // NOLINT
}
else
{
auto func = [&buffer](const char*, const auto* field) {
DeserializeFromBuffer(buffer, *field);
};
TypeDefinition<T>().typeDef(dest, func);
}
}
template <>
inline void DeserializeFromBuffer(SpanBytesConst& buffer, std::string& dest)
{
StringSize size = 0;
DeserializeFromBuffer(buffer, size);
if (size > buffer.size())
{
throw std::runtime_error("DeserializeFromBuffer: buffer overflow");
}
dest.assign(reinterpret_cast<char const*>(buffer.data()), size);
buffer.trimFront(size);
}
template <typename T, size_t N>
inline void DeserializeFromBuffer(SpanBytesConst& buffer, std::array<T, N>& dest)
{
if (N * BufferSize(T{}) > buffer.size())
{
throw std::runtime_error("DeserializeFromBuffer: buffer overflow");
}
if constexpr (sizeof(T) == 1)
{
memcpy(dest.data(), buffer.data(), N);
buffer.trimFront(N);
}
else
{
for (size_t i = 0; i < N; i++)
{
DeserializeFromBuffer(buffer, dest[i]);
}
}
}
template <template <class, class> class Container, class T, class... TArgs>
inline void DeserializeFromBuffer(SpanBytesConst& buffer, Container<T, TArgs...>& dest)
{
uint32_t num_values = 0;
DeserializeFromBuffer(buffer, num_values);
// if the container offers contiguous memory, you can just use memcpy
if constexpr (sizeof(T) == 1 && is_vector<Container<T, TArgs...>>())
{
if constexpr (container_info<Container<T, TArgs...>>::size == 0)
{
dest.resize(num_values);
}
else if constexpr (std::is_array_v<Container<T, TArgs...>>)
{
if (std::size(dest) != num_values)
{
throw std::runtime_error("DeserializeFromBuffer: wrong size in static container");
}
}
const size_t size = num_values * BufferSize(T{});
memcpy(dest.data(), buffer.data(), size);
buffer.trimFront(size);
}
else
{
dest.clear();
for (size_t i = 0; i < num_values; i++)
{
T temp;
DeserializeFromBuffer(buffer, temp);
std::back_inserter(dest) = std::move(temp);
}
}
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
template <typename T>
inline void SerializeIntoBuffer(SpanBytes& buffer, T const& value)
{
if constexpr (is_number<T>())
{
const size_t S = sizeof(T);
if (S > buffer.size())
{
throw std::runtime_error("SerializeIntoBuffer: buffer overflow");
}
#if SERIALIZE_LITTLEENDIAN == 0
*(reinterpret_cast<T*>(buffer.data())) = EndianSwap<T>(value);
#else
*(reinterpret_cast<T*>(buffer.data())) = value;
#endif
buffer.trimFront(S); // NOLINT
}
else
{
auto func = [&buffer](const char*, const auto* field) {
SerializeIntoBuffer(buffer, *field);
};
TypeDefinition<T>().typeDef(value, func);
}
}
template <>
inline void SerializeIntoBuffer(SpanBytes& buffer, std::string const& str)
{
if (str.size() > std::numeric_limits<StringSize>::max())
{
throw std::runtime_error("SerializeIntoBuffer: string exceeds maximum size");
}
if ((str.size() + sizeof(StringSize)) > buffer.size())
{
throw std::runtime_error("SerializeIntoBuffer: buffer overflow");
}
const auto size = static_cast<StringSize>(str.size());
SerializeIntoBuffer(buffer, size);
memcpy(buffer.data(), str.data(), size);
buffer.trimFront(size);
}
template <typename T, size_t N>
inline void SerializeIntoBuffer(SpanBytes& buffer, std::array<T, N> const& vect)
{
if (N > std::numeric_limits<uint32_t>::max())
{
throw std::runtime_error("SerializeIntoBuffer: array exceeds maximum size");
}
if constexpr (std::is_arithmetic_v<T> || std::is_same_v<T, std::byte>)
{
if (N * sizeof(T) > buffer.size())
{
throw std::runtime_error("SerializeIntoBuffer: buffer overflow");
}
}
if constexpr (sizeof(T) == 1)
{
std::memcpy(vect.data(), buffer.data(), N);
buffer.trimFront(N);
}
else
{
for (size_t i = 0; i < N; i++)
{
SerializeIntoBuffer(buffer, vect[i]);
}
}
}
template <template <class, class> class Container, class T, class... TArgs>
inline void SerializeIntoBuffer(SpanBytes& buffer, Container<T, TArgs...> const& vect)
{
const auto num_values = static_cast<uint32_t>(vect.size());
SerializeIntoBuffer(buffer, num_values);
// can use memcpy if the size of T is 1
if constexpr (sizeof(T) == 1 && is_vector<Container<T, TArgs...>>())
{
const size_t size = num_values;
if (size > buffer.size())
{
throw std::runtime_error("SerializeIntoBuffer: buffer overflow");
}
memcpy(buffer.data(), vect.data(), size);
buffer.trimFront(size);
}
else
{
for (const T& v : vect)
{
SerializeIntoBuffer(buffer, v);
}
}
}
} // namespace SerializeMe