/* * 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: typedef tt::integral_constant maximum_size_type; static constexpr maximum_size_type maximum_size = {}; }; template class enum_type { static_assert(tt::is_enum::value, "enum_type used on non-enum type"); public: typedef tt::integral_constant maximum_size_type; static constexpr maximum_size_type maximum_size = {}; }; 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 ::type> static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &, A1 &&); template ::type> static inline typename tt::enable_if::value, void>::type process_buffer(Accessor &, A1 &&); template ::type> 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; class endian_access { public: /* * 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 */ typedef tt::integral_constant foreign_endian_type; typedef tt::integral_constant little_endian_type; typedef tt::integral_constant big_endian_type; typedef tt::integral_constant native_endian_type; static constexpr auto foreign_endian = foreign_endian_type{}; static constexpr auto little_endian = little_endian_type{}; static constexpr auto big_endian = big_endian_type{}; static constexpr auto native_endian = native_endian_type{}; }; /* 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 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(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); } 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"); 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 &) { /* 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); } /* Never defined. Used only in unevaluated context for decltype. * If minimum_size exists, it is used. Otherwise, maximum_size is used. */ template typename T::minimum_size_type get_minimum_size(T *); template typename T::maximum_size_type get_minimum_size(...); } 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) \ template \ static inline void process_udt(Accessor &accessor, TYPE &NAME) \ { \ using serial::process_buffer; \ process_buffer(accessor, _SERIAL_UDT_UNWRAP_LIST MEMBERLIST); \ } \ \ __attribute_unused \ 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 class assert_udt_message_compatible2 : public tt::false_type { }; template class assert_udt_message_compatible2, std::tuple> : public udt_message_compatible_same_type { }; template class assert_udt_message_compatible2, std::tuple> : public assert_udt_message_compatible2::value, message, std::tuple> { }; template class assert_udt_message_compatible1; template class assert_udt_message_compatible1, 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 class assert_udt_message_compatible; template class assert_udt_message_compatible> : public 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; typename tt::conditional, typename tt::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, 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: typedef decltype(detail::get_minimum_size(nullptr)) minimum_size_type; typedef typename effective_type::maximum_size_type maximum_size_type; static constexpr minimum_size_type minimum_size = {}; static constexpr maximum_size_type maximum_size = {}; }; template constexpr typename message_type::maximum_size_type message_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()))> { }; template class array_type> { public: typedef tt::integral_constant::maximum_size * N> maximum_size_type; static constexpr maximum_size_type maximum_size = {}; }; template class array_type> : public array_type> { }; template class message_type> { public: typedef message as_message; typedef tt::integral_constant::minimum_size + message_type>::minimum_size> minimum_size_type; typedef tt::integral_constant::maximum_size + message_type>::maximum_size> maximum_size_type; static constexpr minimum_size_type minimum_size = {}; static constexpr maximum_size_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; } template static inline void unaligned_copy(const uint8_t *src, unaligned_storage &dst) { 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 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. */ template static inline void unaligned_copy(const unaligned_storage &src, uint8_t *dst) { 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 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, std::forward(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)>()); } /* Require at least two arguments to prevent self-selection */ template static void process_buffer(Accessor &accessor, A1 &&a1, A2 &&a2, An &&... an) { detail::sequence({ (process_buffer(accessor, std::forward(a1)), process_buffer(accessor, std::forward(a2)), static_cast(0)), (process_buffer(accessor, std::forward(an)), static_cast(0))... }); } }