/*
* This file is part of the DXX-Rebirth project .
* It is copyright by its individual contributors, as recorded in the
* project's Git history. See COPYING.txt at the top level for license
* terms and a link to the Git history.
*/
#pragma once
#include
#include
#include
#include
#include
#include
#include "dxxsconf.h"
#include "compiler-addressof.h"
#include "compiler-array.h"
#include "compiler-integer_sequence.h"
#include "compiler-range_for.h"
#include "compiler-static_assert.h"
#include "compiler-type_traits.h"
namespace serial {
template
class message;
/* Classifiers to identify whether a type is a message<...> */
template
class is_message : public tt::false_type
{
};
template
class is_message> : public tt::true_type
{
};
template
class integral_type
{
static_assert(tt::is_integral::value, "integral_type used on non-integral type");
public:
static const std::size_t maximum_size = sizeof(T);
};
template
class enum_type
{
static_assert(tt::is_enum::value, "enum_type used on non-enum type");
public:
static const std::size_t maximum_size = sizeof(T);
};
template
class is_cxx_array : public tt::false_type
{
};
template
class is_cxx_array> : public tt::true_type
{
};
template
class is_cxx_array : public is_cxx_array
{
};
template
class is_generic_class : public tt::conditional::value, tt::false_type, tt::is_class>::type
{
};
template
static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &, A1 &);
template
static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &, A1 &);
template
static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &, A1 &);
template
typename tt::enable_if::value, void>::type process_buffer(Accessor &, A1 &);
template
static void process_buffer(Accessor &, const message &);
template
class class_type;
template
class array_type;
struct endian_access
{
/*
* Endian access modes:
* - foreign_endian: assume buffered data is foreign endian
* Byte swap regardless of host byte order
* - little_endian: assume buffered data is little endian
* Copy on little endian host, byte swap on big endian host
* - big_endian: assume buffered data is big endian
* Copy on big endian host, byte swap on little endian host
* - native_endian: assume buffered data is native endian
* Copy regardless of host byte order
*/
static const uint16_t foreign_endian = 0;
static const uint16_t little_endian = 255;
static const uint16_t big_endian = 256;
static const uint16_t native_endian = 257;
};
/* Implementation details - avoid namespace pollution */
namespace detail {
/*
* gcc before 4.8 chokes on tuple> due to ambiguous
* base class after applying EBO
*/
template
class wrapped_empty_value : T
{
public:
wrapped_empty_value() = default;
wrapped_empty_value(T &&t) : T(std::forward(t)) {}
T &get() { return *this; }
const T &get() const { return *this; }
};
template
static inline T &extract_value(wrapped_empty_value &t)
{
return t.get();
}
template
static inline const T &extract_value(const wrapped_empty_value &t)
{
return t.get();
}
template ::type>
struct capture_type
{
typedef
typename tt::conditional::value,
std::reference_wrapper,
typename tt::conditional::value,
wrapped_empty_value,
std::tuple
>::type
>::type type;
};
template ::type>
static inline auto capture_value(Trr &t) -> decltype(std::ref(t))
{
return std::ref(t);
}
template ::type>
static inline typename tt::enable_if::value, detail::wrapped_empty_value>::type capture_value(Trr &&t)
{
return std::forward(t);
}
template ::type>
static inline typename tt::enable_if::value && tt::is_rvalue_reference::value, std::tuple>::type capture_value(Trr &&t)
{
return std::tuple{std::forward(t)};
}
template
class pad_type
{
};
template
message> udt_to_message(const pad_type &);
/*
* This can never be instantiated, but will be requested if a UDT
* specialization is missing.
*/
template
struct missing_udt_specialization
{
#ifndef DXX_HAVE_CXX11_EXPLICIT_DELETE
protected:
#endif
missing_udt_specialization() DXX_CXX11_EXPLICIT_DELETE;
};
template
void udt_to_message(T &, missing_udt_specialization = missing_udt_specialization());
template
void preprocess_udt(Accessor &, UDT &) {}
template
void postprocess_udt(Accessor &, UDT &) {}
template
static inline void process_udt(Accessor &accessor, UDT &udt)
{
process_buffer(accessor, udt_to_message(udt));
}
template
void check_enum(Accessor &, E) {}
template
struct base_bytebuffer_t : std::iterator, endian_access
{
public:
// Default bytebuffer_t usage to little endian
static uint16_t endian() { return little_endian; }
typedef typename std::iterator::pointer pointer;
typedef typename std::iterator::difference_type difference_type;
base_bytebuffer_t(pointer u) : p(u) {}
operator pointer() const { return p; }
D &operator+=(difference_type d)
{
p += d;
return *static_cast(this);
}
protected:
pointer p;
};
#define SERIAL_UDT_ROUND_UP(X,M) (((X) + (M) - 1) & ~((M) - 1))
template
union pad_storage
{
static_assert(amount % SERIAL_UDT_ROUND_MULTIPLIER ? SERIAL_UDT_ROUND_UP_AMOUNT > amount && SERIAL_UDT_ROUND_UP_AMOUNT < amount + SERIAL_UDT_ROUND_MULTIPLIER : SERIAL_UDT_ROUND_UP_AMOUNT == amount, "round up error");
static_assert(SERIAL_UDT_ROUND_UP_AMOUNT % SERIAL_UDT_ROUND_MULTIPLIER == 0, "round modulus error");
static_assert(amount % FULL_SIZE == REMAINDER_SIZE || FULL_SIZE == REMAINDER_SIZE, "padding alignment error");
array f;
array p;
pad_storage(tt::false_type, uint8_t value)
{
f.fill(value);
}
pad_storage(tt::true_type, uint8_t)
{
}
#undef SERIAL_UDT_ROUND_UP
};
template
static inline void process_udt(Accessor &accessor, const pad_type &udt)
{
/* If reading from accessor, accessor data is const and buffer is
* overwritten by read.
* If writing to accessor, accessor data is non-const, so initialize
* buffer to be written.
*/
pad_storage s(tt::is_const::type>(), value);
for (std::size_t count = amount; count; count -= s.f.size())
{
if (count < s.f.size())
{
assert(count == s.p.size());
process_buffer(accessor, s.p);
break;
}
process_buffer(accessor, s.f);
}
}
static inline void sequence(std::initializer_list) {}
template
static inline T &extract_value(std::reference_wrapper t)
{
return t;
}
template
static inline T &extract_value(std::tuple &t)
{
return std::get<0>(t);
}
template
static inline const T &extract_value(const std::tuple &t)
{
return std::get<0>(t);
}
}
template
static inline detail::pad_type pad()
{
return {};
}
#define DEFINE_SERIAL_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
DEFINE_SERIAL_MUTABLE_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
#define _DEFINE_SERIAL_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
static inline auto udt_to_message(TYPE &NAME) -> decltype(serial::make_message MEMBERLIST) { \
return serial::make_message MEMBERLIST; \
}
#define DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
_DEFINE_SERIAL_UDT_TO_MESSAGE(const TYPE, NAME, MEMBERLIST)
#define DEFINE_SERIAL_MUTABLE_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
_DEFINE_SERIAL_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST)
#define ASSERT_SERIAL_UDT_MESSAGE_SIZE(T, SIZE) \
assert_equal(serial::class_type::maximum_size, SIZE, "sizeof(" #T ") is not " #SIZE)
template ::type>::type>
struct udt_message_compatible_same_type : tt::is_same
{
static_assert(tt::is_same::value, "parameter type mismatch");
};
template
class assert_udt_message_compatible2;
template
struct assert_udt_message_compatible2 : tt::false_type
{
};
template
struct assert_udt_message_compatible2, std::tuple> : udt_message_compatible_same_type
{
};
template
struct assert_udt_message_compatible2, std::tuple> :
assert_udt_message_compatible2::value, message, std::tuple>
{
};
template
class assert_udt_message_compatible1;
template
struct assert_udt_message_compatible1, std::tuple> : assert_udt_message_compatible2, std::tuple>
{
static_assert(sizeof...(Mn) <= sizeof...(Tn), "too few types in tuple");
static_assert(sizeof...(Mn) >= sizeof...(Tn), "too few types in message");
};
template
class assert_udt_message_compatible;
template
struct assert_udt_message_compatible> : assert_udt_message_compatible1::as_message, std::tuple>
{
};
#define _SERIAL_UDT_UNWRAP_LIST(A1,...) A1, ## __VA_ARGS__
#define ASSERT_SERIAL_UDT_MESSAGE_TYPE(T, TYPELIST) \
ASSERT_SERIAL_UDT_MESSAGE_CONST_TYPE(T, TYPELIST); \
ASSERT_SERIAL_UDT_MESSAGE_MUTABLE_TYPE(T, TYPELIST); \
#define _ASSERT_SERIAL_UDT_MESSAGE_TYPE(T, TYPELIST) \
static_assert(serial::assert_udt_message_compatible>::value, "udt/message mismatch")
#define ASSERT_SERIAL_UDT_MESSAGE_CONST_TYPE(T, TYPELIST) \
_ASSERT_SERIAL_UDT_MESSAGE_TYPE(const T, TYPELIST)
#define ASSERT_SERIAL_UDT_MESSAGE_MUTABLE_TYPE(T, TYPELIST) \
_ASSERT_SERIAL_UDT_MESSAGE_TYPE(T, TYPELIST)
union endian_skip_byteswap_u
{
uint8_t c[2];
uint16_t s;
constexpr endian_skip_byteswap_u(const uint16_t &u) : s(u)
{
static_assert(offsetof(endian_skip_byteswap_u, c) == offsetof(endian_skip_byteswap_u, s), "union layout error");
}
};
static inline constexpr uint8_t endian_skip_byteswap(const uint16_t &endian)
{
return endian_skip_byteswap_u{endian}.c[0];
}
template
union unaligned_storage
{
T a;
uint8_t u[N];
assert_equal(sizeof(a), sizeof(u), "sizeof(T) is not N");
};
template
class message_dispatch_type;
template
class message_dispatch_type::value, void>::type>
{
protected:
typedef integral_type effective_type;
};
template
class message_dispatch_type::value, void>::type>
{
protected:
typedef enum_type effective_type;
};
template
class message_dispatch_type::value, void>::type>
{
protected:
typedef array_type effective_type;
};
template
class message_dispatch_type::value && !is_message::value, void>::type>
{
protected:
typedef class_type effective_type;
};
template
class message_type : message_dispatch_type::type>
{
typedef message_dispatch_type::type> base_type;
typedef typename base_type::effective_type effective_type;
public:
static const std::size_t maximum_size = effective_type::maximum_size;
};
template
class message_dispatch_type, void>
{
protected:
typedef message_type effective_type;
public:
typedef message as_message;
};
template
class class_type : public message_type(*static_cast(nullptr))))>
{
};
template
class array_type>
{
public:
static const std::size_t maximum_size = message_type::maximum_size * N;
};
template
class array_type> : public array_type>
{
};
template
class message_type>
{
public:
typedef message as_message;
static const std::size_t maximum_size = message_type::maximum_size + message_type>::maximum_size;
};
template
class message
{
typedef std::tuple::type, typename detail::capture_type::type...> tuple_type;
template
static void check_type()
{
static_assert(message_type::maximum_size > 0, "empty field in message");
}
static void check_types()
{
check_type();
detail::sequence({(check_type(), static_cast(0))...});
}
tuple_type t;
public:
message(A1 &&a1, Args &&... args) :
t(detail::capture_value(std::forward(a1)), detail::capture_value(std::forward(args))...)
{
check_types();
}
const tuple_type &get_tuple() const
{
return t;
}
};
template
static inline message make_message(A1 &&a1, Args &&... args)
{
return {std::forward(a1), std::forward(args)...};
}
#define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN(HBITS,BITS) \
static inline constexpr uint##BITS##_t bswap(const uint##BITS##_t &u) \
{ \
return __builtin_bswap##BITS(u); \
}
#define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT(HBITS,BITS) \
static inline constexpr uint##BITS##_t bswap(const uint##BITS##_t &u) \
{ \
return (static_cast(bswap(static_cast(u))) << HBITS) | \
static_cast(bswap(static_cast(u >> HBITS))); \
}
#define SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(HBITS,BITS) \
SERIAL_DEFINE_SIZE_SPECIFIC_USWAP(HBITS,BITS); \
static inline constexpr int##BITS##_t bswap(const int##BITS##_t &i) \
{ \
return bswap(static_cast(i)); \
}
static inline constexpr uint8_t bswap(const uint8_t &u)
{
return u;
}
static inline constexpr int8_t bswap(const int8_t &u)
{
return u;
}
#ifdef DXX_HAVE_BUILTIN_BSWAP16
#define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN
#else
#define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT
#endif
SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(8, 16);
#undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP
#ifdef DXX_HAVE_BUILTIN_BSWAP
#define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN
#else
#define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT
#endif
SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(16, 32);
SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(32, 64);
#undef SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP
#undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP
#undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN
#undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT
namespace reader {
class bytebuffer_t : public detail::base_bytebuffer_t
{
public:
bytebuffer_t(pointer u) : base_bytebuffer_t(u) {}
explicit bytebuffer_t(const bytebuffer_t &) = default;
bytebuffer_t(bytebuffer_t &&) = default;
};
template
static inline void unaligned_copy(const uint8_t *src, unaligned_storage &dst)
{
dst.u[0] = *src;
}
#define SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(BITS) \
template \
static inline void unaligned_copy(const uint8_t *src, unaligned_storage &dst) \
{ \
std::copy_n(src, sizeof(dst.u), dst.u); \
}
SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(16);
SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(32);
SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(64);
#undef SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY
template
static inline void process_integer(Accessor &buffer, A1 &a1)
{
using std::advance;
unaligned_storage::maximum_size> u;
unaligned_copy(buffer, u);
a1 = endian_skip_byteswap(buffer.endian()) ? u.a : bswap(u.a);
advance(buffer, sizeof(u.u));
}
template
static inline typename tt::enable_if::value, void>::type process_array(Accessor &accessor, A &a)
{
std::copy_n(static_cast(accessor), a.size(), &a[0]);
advance(accessor, a.size());
}
}
namespace writer {
class bytebuffer_t : public detail::base_bytebuffer_t
{
public:
bytebuffer_t(pointer u) : base_bytebuffer_t(u) {}
explicit bytebuffer_t(const bytebuffer_t &) = default;
bytebuffer_t(bytebuffer_t &&) = default;
};
template
static inline void unaligned_copy(const unaligned_storage &src, uint8_t *dst)
{
*dst = src.u[0];
}
/* If inline unaligned_copy, gcc inlining of copy_n creates a loop instead
* of a store.
*/
#define SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(BITS) \
template \
static inline void unaligned_copy(unaligned_storage src, uint8_t *dst) \
{ \
std::copy_n(src.u, sizeof(src.u), dst); \
}
SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(16);
SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(32);
SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY(64);
#undef SERIAL_DEFINE_SIZE_SPECIFIC_UNALIGNED_COPY
template
static inline void process_integer(Accessor &buffer, const A1 &a1)
{
using std::advance;
unaligned_storage::maximum_size> u{endian_skip_byteswap(buffer.endian()) ? a1 : bswap(a1)};
unaligned_copy(u, buffer);
advance(buffer, sizeof(u.u));
}
template
static inline typename tt::enable_if::value, void>::type process_array(Accessor &accessor, const A &a)
{
std::copy_n(&a[0], a.size(), static_cast(accessor));
advance(accessor, a.size());
}
}
template
static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &accessor, A1 &a1)
{
process_integer(accessor, a1);
}
template
static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &accessor, A1 &a1)
{
using detail::check_enum;
process_integer(accessor, a1);
/* Hook for enum types to check that the given value is legal */
check_enum(accessor, a1);
}
template
static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &accessor, A1 &a1)
{
using detail::preprocess_udt;
using detail::process_udt;
using detail::postprocess_udt;
preprocess_udt(accessor, a1);
process_udt(accessor, a1);
postprocess_udt(accessor, a1);
}
template
static typename tt::enable_if::value), void>::type process_array(Accessor &accessor, A &a)
{
range_for (auto &i, a)
process_buffer(accessor, i);
}
template
typename tt::enable_if::value, void>::type process_buffer(Accessor &accessor, A1 &a1)
{
process_array(accessor, a1);
}
template
static inline void process_message_tuple(Accessor &accessor, const std::tuple &t, index_sequence)
{
detail::sequence({(process_buffer(accessor, detail::extract_value(std::get(t))), static_cast(0))...});
}
template
static void process_buffer(Accessor &accessor, const message &m)
{
process_message_tuple(accessor, m.get_tuple(), make_tree_index_sequence<1 + sizeof...(Args)>());
}
}