/*
* 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
#include
#include
#include "dxxsconf.h"
#include "compiler-range_for.h"
#include "compiler-static_assert.h"
#include
#include
#include
namespace serial {
template
class message;
template
class message_type;
/* Classifiers to identify whether a type is a message<...> */
template
class is_message : public std::false_type
{
};
template
class is_message> : public std::true_type
{
};
namespace detail {
template
struct size_base
{
static constexpr std::integral_constant maximum_size = {};
static constexpr std::integral_constant minimum_size = {};
};
template
constexpr std::integral_constant size_base::maximum_size;
template
constexpr std::integral_constant size_base::minimum_size;
}
template
class is_cxx_array : public std::false_type
{
};
template
class is_cxx_array> : public std::true_type
{
public:
using array_type = std::array;
};
template
class is_cxx_array : public is_cxx_array
{
};
template
using is_generic_class = typename std::conditional::value, std::false_type, std::is_class>::type;
template
requires(std::is_integral::type>::value)
static inline void process_buffer(Accessor &&accessor, A1 &&a1)
{
process_integer(std::forward(accessor), a1);
}
template ::type>
requires(std::is_enum::value)
static inline void process_buffer(Accessor &, A1 &&);
template ::type>
requires(is_generic_class::value)
static inline void process_buffer(Accessor &, A1 &&);
template
requires(is_cxx_array::value)
static void process_buffer(Accessor &&, A1 &);
template
static void process_buffer(Accessor &, const message &);
class endian_access
{
public:
/*
* Endian access modes:
* - 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
*/
using little_endian_type = std::integral_constant;
using big_endian_type = std::integral_constant;
using native_endian_type = std::integral_constant;
/* If this static_assert fails, then endian_skip_byteswap may return the
* wrong result.
*/
static_assert(std::endian::little == std::endian::native || std::endian::big == std::endian::native, "host byte order must be little endian or big endian");
};
/* Implementation details - avoid namespace pollution */
namespace detail {
template ::type>
using capture_type = typename std::conditional::value,
std::reference_wrapper,
std::tuple
>;
template ::type>
static inline auto capture_value(Trr &t)
{
return std::ref(t);
}
template ::type>
requires(std::is_rvalue_reference::value)
static inline auto capture_value(Trr &&t)
{
return std::tuple{std::forward(t)};
}
template
class sign_extend_type : std::reference_wrapper
{
static_assert(sizeof(extended_signed_type) > sizeof(wrapped_type), "cannot sign-extend into a type of equal or smaller size");
static_assert(std::is_signed::value, "cannot sign-extend into an unsigned type");
using base_type = std::reference_wrapper;
public:
using base_type::base_type;
using base_type::get;
};
template
message> udt_to_message(const sign_extend_type &);
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
class missing_udt_specialization
{
public:
missing_udt_specialization() = 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(std::forward(accessor), udt_to_message(udt));
}
template
void check_enum(Accessor &, E) {}
template
struct base_bytebuffer_t : endian_access
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T *;
using reference = T &;
// Default bytebuffer_t usage to little endian
static constexpr endian_access::little_endian_type endian{};
base_bytebuffer_t(pointer u) : p(u) {}
operator pointer() const { return p; }
D &operator++()
{
++p;
return *static_cast(this);
}
D &operator--()
{
--p;
return *static_cast(this);
}
D &operator+=(const difference_type d)
{
p += d;
return *static_cast(this);
}
operator const void *() const = delete;
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");
std::array f;
std::array p;
#undef SERIAL_UDT_ROUND_UP
};
template
static inline void process_udt(Accessor &&accessor, const pad_type &)
{
/* 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;
if constexpr (!std::is_const<
typename std::remove_pointer<
/* rvalue reference `Accessor &&` causes `Accessor` to be `T &`
* for some type T. Use std::remove_reference to get T. Then
* take the type `pointer` from type T to use as input to
* std::remove_pointer.
*/
typename std::remove_reference::type
::pointer
>::type
>::value)
s.f.fill(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);
}
}
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
struct message_dispatch_base
{
using effective_type = T;
};
}
template
using pad = detail::pad_type;
template
static inline detail::sign_extend_type sign_extend(wrapped_type &t)
{
return {t};
}
#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) \
template \
static inline void process_udt(Accessor &&accessor, TYPE &NAME) \
{ \
using serial::process_buffer; \
process_buffer(std::forward(accessor), _SERIAL_UDT_UNWRAP_LIST MEMBERLIST); \
} \
\
__attribute_unused \
static inline auto udt_to_message(TYPE &NAME) { \
return serial::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, T1>>
struct udt_message_compatible_same_type : base_type
{
static_assert(base_type::value, "parameter type mismatch");
};
template
class assert_udt_message_compatible2;
template
class assert_udt_message_compatible2 : public std::false_type
{
};
template
class assert_udt_message_compatible2, std::tuple> :
public std::integral_constant::value && ...)>
{
};
template
class assert_udt_message_compatible;
template
class assert_udt_message_compatible, std::tuple> : public 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
using class_type = message_type()))>;
#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::as_message, std::tuple<_SERIAL_UDT_UNWRAP_LIST TYPELIST>>::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)
static constexpr uint8_t endian_skip_byteswap(std::endian E)
{
return E == std::endian::native;
}
template
union unaligned_storage
{
T a;
typename std::conditional,
typename std::conditional>::type::type i;
uint8_t u[N];
assert_equal(sizeof(i), N, "sizeof(i) is not N");
assert_equal(sizeof(a), sizeof(u), "sizeof(T) is not N");
};
template
class message_dispatch_type;
template
class message_dispatch_type::value or std::is_enum::value, void>::type> :
public detail::message_dispatch_base>
{
};
template
class message_dispatch_type::value, void>::type> :
public detail::message_dispatch_base<
detail::size_base<
message_type::maximum_size * std::tuple_size::array_type>::value,
message_type::minimum_size * std::tuple_size::array_type>::value
>
>
{
};
template
class message_dispatch_type::value && !is_message::value, void>::type> :
public detail::message_dispatch_base>
{
};
template
class message_type :
message_dispatch_type::type>::effective_type
{
using effective_type = typename message_dispatch_type::type>::effective_type;
public:
using effective_type::maximum_size;
using effective_type::minimum_size;
};
template
class message_dispatch_type, void> :
public detail::message_dispatch_base>
{
public:
typedef message as_message;
};
template
class message_type> :
public detail::size_base<
(0 + ... + message_dispatch_type>::effective_type::maximum_size),
(0 + ... + message_dispatch_type>::effective_type::minimum_size)
>
{
public:
using as_message = message;
};
template
class message
{
static_assert(sizeof...(Args) > 0, "message must have at least one template argument");
using tuple_type = std::tuple::type...>;
template
static void check_type()
{
static_assert(message_type::maximum_size > 0, "empty field in message");
}
tuple_type t;
public:
message(Args &&... args) :
t(detail::capture_value(std::forward(args))...)
{
(check_type(), ...);
}
const tuple_type &get_tuple() const
{
return t;
}
};
template
message(Args &&... args) -> message;
template
static constexpr T bswap(const T u)
{
if constexpr (std::is_same::value || std::is_same::value)
/* Swapping a byte-sized value is a no-op. This is permitted here so
* that callers can swap without checking the size of the value.
*/
return u;
if constexpr (std::is_same::value || std::is_same::value)
{
#ifdef DXX_HAVE_BUILTIN_BSWAP
return __builtin_bswap16(u);
#else
return (static_cast(static_cast(u)) << 8) | static_cast(static_cast(u >> 8));
#endif
}
if constexpr (std::is_same::value || std::is_same::value)
{
#ifdef DXX_HAVE_BUILTIN_BSWAP
return __builtin_bswap32(u);
#else
return (static_cast(bswap(static_cast(u))) << 16) | static_cast(bswap(static_cast(u >> 16)));
#endif
}
if constexpr (std::is_same::value || std::is_same::value)
{
#ifdef DXX_HAVE_BUILTIN_BSWAP
return __builtin_bswap64(u);
#else
return (static_cast(bswap(static_cast(u))) << 32) | static_cast(bswap(static_cast(u >> 32)));
#endif
}
/* Unsupported type. Fall off the end of the function and trigger a
* compile error due to the missing `return` statement.
*/
}
assert_equal(bswap(static_cast(1)), 1, "");
assert_equal(bswap(static_cast(0x12)), 0x1200, "");
assert_equal(bswap(static_cast(0x92)), 0x9200, "");
assert_equal(bswap(static_cast(0x9200)), 0x92, "");
assert_equal(bswap(static_cast(0x102)), 0x201, "");
assert_equal(bswap(static_cast(0x102)), 0x2010000, "");
assert_equal(bswap(static_cast(0x102)), 0x201000000000000ull, "");
namespace reader {
class bytebuffer_t : public detail::base_bytebuffer_t
{
public:
using base_bytebuffer_t::base_bytebuffer_t;
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)
{
if constexpr (BYTES == 1)
dst.u[0] = *src;
else
std::copy_n(src, sizeof(dst.u), dst.u);
}
template
static inline void process_integer(Accessor &buffer, A1 &a1)
{
using std::advance;
unaligned_storage::maximum_size> u;
unaligned_copy(buffer, u);
if (!endian_skip_byteswap(buffer.endian()))
u.i = bswap(u.i);
a1 = u.a;
advance(buffer, sizeof(u.u));
}
template
requires(sizeof(T) == 1 && std::is_integral::value)
static inline void process_array(Accessor &accessor, A &a)
{
using std::advance;
std::copy_n(static_cast(accessor), a.size(), &a[0]);
advance(accessor, a.size());
}
template
static inline void process_udt(Accessor &&accessor, const detail::sign_extend_type &v)
{
extended_signed_type est;
process_integer(static_cast(accessor), est);
v.get() = static_cast(est);
}
}
namespace writer {
class bytebuffer_t : public detail::base_bytebuffer_t
{
public:
using base_bytebuffer_t::base_bytebuffer_t;
explicit bytebuffer_t(const bytebuffer_t &) = default;
bytebuffer_t(bytebuffer_t &&) = default;
};
/* If unaligned_copy is manually inlined into the caller, then gcc
* inlining of copy_n creates a loop instead of a store.
*/
template
static inline void unaligned_copy(const unaligned_storage &src, uint8_t *dst)
{
if constexpr (BYTES == 1)
*dst = src.u[0];
else
std::copy_n(src.u, sizeof(src.u), dst);
}
template
static inline void process_integer(Accessor &buffer, const A1 &a1)
{
using std::advance;
unaligned_storage::maximum_size> u{a1};
if (!endian_skip_byteswap(buffer.endian()))
u.i = bswap(u.i);
unaligned_copy(u, buffer);
advance(buffer, sizeof(u.u));
}
template
requires(sizeof(T) == 1 && std::is_integral::value)
static inline void process_array(Accessor &accessor, const A &a)
{
using std::advance;
std::copy_n(&a[0], a.size(), static_cast(accessor));
advance(accessor, a.size());
}
template
static inline void process_udt(Accessor &&accessor, const detail::sign_extend_type &v)
{
const typename std::make_signed::type swt = v.get();
const extended_signed_type est = swt;
process_integer(static_cast(accessor), est);
}
}
template
requires(std::is_enum::value)
static inline void 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
requires(is_generic_class::value)
static inline void 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, std::forward(a1));
postprocess_udt(accessor, a1);
}
template
requires(!(sizeof(T) == 1 && std::is_integral::value))
static void process_array(Accessor &accessor, A &a)
{
range_for (auto &i, a)
process_buffer(accessor, i);
}
template
requires(is_cxx_array::value)
static void process_buffer(Accessor &&accessor, A1 &a1)
{
process_array(std::forward(accessor), a1);
}
template
static inline void process_message_tuple(Accessor &&accessor, const std::tuple &t, std::index_sequence)
{
(process_buffer(accessor, detail::extract_value(std::get(t))), ...);
}
template
static void process_buffer(Accessor &&accessor, const message &m)
{
process_message_tuple(std::forward(accessor), m.get_tuple(), std::make_index_sequence());
}
/* Require at least two arguments to prevent self-selection */
template
requires(sizeof...(An) > 1)
static void process_buffer(Accessor &&accessor, An &&... an)
{
(process_buffer(accessor, std::forward(an)), ...);
}
}