/* * 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)), ...); } }