1 /*
2 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3 * It is copyright by its individual contributors, as recorded in the
4 * project's Git history. See COPYING.txt at the top level for license
5 * terms and a link to the Git history.
6 */
7 #pragma once
8 #include <algorithm>
9 #include <cassert>
10 #include <cstddef>
11 #include <cstdint>
12 #include <functional>
13 #include <initializer_list>
14 #include <tuple>
15 #include <type_traits>
16
17 #include "dxxsconf.h"
18 #include "compiler-range_for.h"
19 #include "compiler-static_assert.h"
20 #include <array>
21 #include <memory>
22 #include <utility>
23
24 namespace serial {
25
26 template <typename... Args>
27 class message;
28
29 template <typename>
30 class message_type;
31
32 /* Classifiers to identify whether a type is a message<...> */
33 template <typename>
34 class is_message : public std::false_type
35 {
36 };
37
38 template <typename... Args>
39 class is_message<message<Args...>> : public std::true_type
40 {
41 };
42
43 namespace detail {
44
45 template <std::size_t maximum, std::size_t minimum = maximum>
46 struct size_base
47 {
48 static constexpr std::integral_constant<std::size_t, maximum> maximum_size = {};
49 static constexpr std::integral_constant<std::size_t, minimum> minimum_size = {};
50 };
51
52 template <std::size_t maximum, std::size_t minimum>
53 constexpr std::integral_constant<std::size_t, maximum> size_base<maximum, minimum>::maximum_size;
54
55 template <std::size_t maximum, std::size_t minimum>
56 constexpr std::integral_constant<std::size_t, minimum> size_base<maximum, minimum>::minimum_size;
57
58 }
59
60 template <typename>
61 class is_cxx_array : public std::false_type
62 {
63 };
64
65 template <typename T, std::size_t N>
66 class is_cxx_array<std::array<T, N>> : public std::true_type
67 {
68 public:
69 using array_type = std::array<T, N>;
70 };
71
72 template <typename T>
73 class is_cxx_array<const T> : public is_cxx_array<T>
74 {
75 };
76
77 template <typename T>
78 using is_generic_class = typename std::conditional<is_cxx_array<T>::value, std::false_type, std::is_class<T>>::type;
79
80 template <typename Accessor, typename A1>
process_buffer(Accessor && accessor,A1 && a1)81 static inline typename std::enable_if<std::is_integral<typename std::remove_reference<A1>::type>::value, void>::type process_buffer(Accessor &&accessor, A1 &&a1)
82 {
83 process_integer(std::forward<Accessor &&>(accessor), a1);
84 }
85
86 template <typename Accessor, typename A1, typename A1rr = typename std::remove_reference<A1>::type>
87 static inline typename std::enable_if<std::is_enum<A1rr>::value, void>::type process_buffer(Accessor &, A1 &&);
88
89 template <typename Accessor, typename A1, typename A1rr = typename std::remove_reference<A1>::type>
90 static inline typename std::enable_if<is_generic_class<A1rr>::value, void>::type process_buffer(Accessor &, A1 &&);
91
92 template <typename Accessor, typename A1>
93 static typename std::enable_if<is_cxx_array<A1>::value, void>::type process_buffer(Accessor &&, A1 &);
94
95 template <typename Accessor, typename... Args>
96 static void process_buffer(Accessor &, const message<Args...> &);
97
98 class endian_access
99 {
100 public:
101 /*
102 * Endian access modes:
103 * - foreign_endian: assume buffered data is foreign endian
104 * Byte swap regardless of host byte order
105 * - little_endian: assume buffered data is little endian
106 * Copy on little endian host, byte swap on big endian host
107 * - big_endian: assume buffered data is big endian
108 * Copy on big endian host, byte swap on little endian host
109 * - native_endian: assume buffered data is native endian
110 * Copy regardless of host byte order
111 */
112 typedef std::integral_constant<uint16_t, 0> foreign_endian_type;
113 typedef std::integral_constant<uint16_t, 255> little_endian_type;
114 typedef std::integral_constant<uint16_t, 256> big_endian_type;
115 typedef std::integral_constant<uint16_t, 257> native_endian_type;
116
117 static constexpr auto foreign_endian = foreign_endian_type{};
118 static constexpr auto little_endian = little_endian_type{};
119 static constexpr auto big_endian = big_endian_type{};
120 static constexpr auto native_endian = native_endian_type{};
121 };
122
123 /* Implementation details - avoid namespace pollution */
124 namespace detail {
125
126 template <typename T, typename Trr = typename std::remove_reference<T>::type>
127 using capture_type = typename std::conditional<std::is_lvalue_reference<T>::value,
128 std::reference_wrapper<Trr>,
129 std::tuple<Trr>
130 >;
131
132 template <typename T, typename Trr = typename std::remove_reference<T>::type>
133 static inline auto capture_value(Trr &t) -> decltype(std::ref(t))
134 {
135 return std::ref(t);
136 }
137
138 template <typename T, typename Trr = typename std::remove_reference<T>::type>
capture_value(Trr && t)139 static inline typename std::enable_if<std::is_rvalue_reference<T>::value, std::tuple<Trr>>::type capture_value(Trr &&t)
140 {
141 return std::tuple<Trr>{std::forward<T>(t)};
142 }
143
144 template <typename extended_signed_type, typename wrapped_type>
145 class sign_extend_type : std::reference_wrapper<wrapped_type>
146 {
147 static_assert(sizeof(extended_signed_type) > sizeof(wrapped_type), "cannot sign-extend into a type of equal or smaller size");
148 static_assert(std::is_signed<extended_signed_type>::value, "cannot sign-extend into an unsigned type");
149 using base_type = std::reference_wrapper<wrapped_type>;
150 public:
151 using base_type::base_type;
152 using base_type::get;
153 };
154
155 template <typename extended_signed_type, typename wrapped_type>
156 message<std::array<uint8_t, sizeof(extended_signed_type)>> udt_to_message(const sign_extend_type<extended_signed_type, wrapped_type> &);
157
158 template <std::size_t amount, uint8_t value>
159 class pad_type
160 {
161 };
162
163 template <std::size_t amount, uint8_t value>
164 message<std::array<uint8_t, amount>> udt_to_message(const pad_type<amount, value> &);
165
166 /*
167 * This can never be instantiated, but will be requested if a UDT
168 * specialization is missing.
169 */
170 template <typename T>
171 class missing_udt_specialization
172 {
173 public:
174 missing_udt_specialization() = delete;
175 };
176
177 template <typename T>
178 void udt_to_message(T &, missing_udt_specialization<T> = missing_udt_specialization<T>());
179
180 template <typename Accessor, typename UDT>
preprocess_udt(Accessor &,UDT &)181 void preprocess_udt(Accessor &, UDT &) {}
182
183 template <typename Accessor, typename UDT>
postprocess_udt(Accessor &,UDT &)184 void postprocess_udt(Accessor &, UDT &) {}
185
186 template <typename Accessor, typename UDT>
process_udt(Accessor && accessor,UDT & udt)187 static inline void process_udt(Accessor &&accessor, UDT &udt)
188 {
189 process_buffer(std::forward<Accessor &&>(accessor), udt_to_message(udt));
190 }
191
192 template <typename Accessor, typename E>
check_enum(Accessor &,E)193 void check_enum(Accessor &, E) {}
194
195 template <typename T, typename D>
196 struct base_bytebuffer_t : endian_access
197 {
198 public:
199 using iterator_category = std::random_access_iterator_tag;
200 using value_type = T;
201 using difference_type = std::ptrdiff_t;
202 using pointer = T *;
203 using reference = T &;
204 // Default bytebuffer_t usage to little endian
205 static constexpr endian_access::little_endian_type endian{};
base_bytebuffer_tbase_bytebuffer_t206 base_bytebuffer_t(pointer u) : p(u) {}
pointerbase_bytebuffer_t207 operator pointer() const { return p; }
208 D &operator++()
209 {
210 ++p;
211 return *static_cast<D *>(this);
212 }
213 D &operator--()
214 {
215 --p;
216 return *static_cast<D *>(this);
217 }
218 D &operator+=(const difference_type d)
219 {
220 p += d;
221 return *static_cast<D *>(this);
222 }
223 operator const void *() const = delete;
224 protected:
225 pointer p;
226 };
227
228 #define SERIAL_UDT_ROUND_UP(X,M) (((X) + (M) - 1) & ~((M) - 1))
229 template <std::size_t amount,
230 std::size_t SERIAL_UDT_ROUND_MULTIPLIER = sizeof(void *),
231 std::size_t SERIAL_UDT_ROUND_UP_AMOUNT = SERIAL_UDT_ROUND_UP(amount, SERIAL_UDT_ROUND_MULTIPLIER),
232 std::size_t FULL_SIZE = amount / 64 ? 64 : SERIAL_UDT_ROUND_UP_AMOUNT,
233 std::size_t REMAINDER_SIZE = amount % 64>
234 union pad_storage
235 {
236 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");
237 static_assert(SERIAL_UDT_ROUND_UP_AMOUNT % SERIAL_UDT_ROUND_MULTIPLIER == 0, "round modulus error");
238 static_assert(amount % FULL_SIZE == REMAINDER_SIZE || FULL_SIZE == REMAINDER_SIZE, "padding alignment error");
239 std::array<uint8_t, FULL_SIZE> f;
240 std::array<uint8_t, REMAINDER_SIZE> p;
241 #undef SERIAL_UDT_ROUND_UP
242 };
243
244 template <typename Accessor, std::size_t amount, uint8_t value>
process_udt(Accessor && accessor,const pad_type<amount,value> &)245 static inline void process_udt(Accessor &&accessor, const pad_type<amount, value> &)
246 {
247 /* If reading from accessor, accessor data is const and buffer is
248 * overwritten by read.
249 * If writing to accessor, accessor data is non-const, so initialize
250 * buffer to be written.
251 */
252 pad_storage<amount> s;
253 if constexpr (!std::is_const<
254 typename std::remove_pointer<
255 /* rvalue reference `Accessor &&` causes `Accessor` to be `T &`
256 * for some type T. Use std::remove_reference to get T. Then
257 * take the type `pointer` from type T to use as input to
258 * std::remove_pointer.
259 */
260 typename std::remove_reference<Accessor>::type
261 ::pointer
262 >::type
263 >::value)
264 s.f.fill(value);
265 for (std::size_t count = amount; count; count -= s.f.size())
266 {
267 if (count < s.f.size())
268 {
269 assert(count == s.p.size());
270 process_buffer(accessor, s.p);
271 break;
272 }
273 process_buffer(accessor, s.f);
274 }
275 }
276
277 template <typename T>
extract_value(std::reference_wrapper<T> t)278 static inline T &extract_value(std::reference_wrapper<T> t)
279 {
280 return t;
281 }
282
283 template <typename T>
extract_value(std::tuple<T> & t)284 static inline T &extract_value(std::tuple<T> &t)
285 {
286 return std::get<0>(t);
287 }
288
289 template <typename T>
extract_value(const std::tuple<T> & t)290 static inline const T &extract_value(const std::tuple<T> &t)
291 {
292 return std::get<0>(t);
293 }
294
295 template <typename T>
296 struct message_dispatch_base
297 {
298 using effective_type = T;
299 };
300
301 }
302
303 template <std::size_t amount, uint8_t value = 0xcc>
304 using pad = detail::pad_type<amount, value>;
305
306 template <typename extended_signed_type, typename wrapped_type>
sign_extend(wrapped_type & t)307 static inline detail::sign_extend_type<extended_signed_type, wrapped_type> sign_extend(wrapped_type &t)
308 {
309 return {t};
310 }
311
312 #define DEFINE_SERIAL_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
313 DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
314 DEFINE_SERIAL_MUTABLE_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
315
316 #define _DEFINE_SERIAL_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
317 template <typename Accessor> \
318 static inline void process_udt(Accessor &&accessor, TYPE &NAME) \
319 { \
320 using serial::process_buffer; \
321 process_buffer(std::forward<Accessor &&>(accessor), _SERIAL_UDT_UNWRAP_LIST MEMBERLIST); \
322 } \
323 \
324 __attribute_unused \
325 static inline auto udt_to_message(TYPE &NAME) { \
326 return serial::message MEMBERLIST; \
327 }
328
329 #define DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
330 _DEFINE_SERIAL_UDT_TO_MESSAGE(const TYPE, NAME, MEMBERLIST)
331 #define DEFINE_SERIAL_MUTABLE_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST) \
332 _DEFINE_SERIAL_UDT_TO_MESSAGE(TYPE, NAME, MEMBERLIST)
333
334 #define ASSERT_SERIAL_UDT_MESSAGE_SIZE(T, SIZE) \
335 assert_equal(serial::class_type<T>::maximum_size, SIZE, "sizeof(" #T ") is not " #SIZE)
336
337 template <typename M1, typename T1, typename base_type = std::is_same<typename std::remove_cv<typename std::remove_reference<M1>::type>::type, T1>>
338 struct udt_message_compatible_same_type : base_type
339 {
340 static_assert(base_type::value, "parameter type mismatch");
341 };
342
343 template <bool, typename M, typename T>
344 class assert_udt_message_compatible2;
345
346 template <typename M, typename T>
347 class assert_udt_message_compatible2<false, M, T> : public std::false_type
348 {
349 };
350
351 template <typename... Mn, typename... Tn>
352 class assert_udt_message_compatible2<true, message<Mn...>, std::tuple<Tn...>> :
353 public std::integral_constant<bool, (udt_message_compatible_same_type<Mn, Tn>::value && ...)>
354 {
355 };
356
357 template <typename M, typename T>
358 class assert_udt_message_compatible;
359
360 template <typename... Mn, typename... Tn>
361 class assert_udt_message_compatible<message<Mn...>, std::tuple<Tn...>> : public assert_udt_message_compatible2<sizeof...(Mn) == sizeof...(Tn), message<Mn...>, std::tuple<Tn...>>
362 {
363 static_assert(sizeof...(Mn) <= sizeof...(Tn), "too few types in tuple");
364 static_assert(sizeof...(Mn) >= sizeof...(Tn), "too few types in message");
365 };
366
367 template <typename T>
368 using class_type = message_type<decltype(udt_to_message(std::declval<T>()))>;
369
370 #define _SERIAL_UDT_UNWRAP_LIST(A1,...) A1, ## __VA_ARGS__
371
372 #define ASSERT_SERIAL_UDT_MESSAGE_TYPE(T, TYPELIST) \
373 ASSERT_SERIAL_UDT_MESSAGE_CONST_TYPE(T, TYPELIST); \
374 ASSERT_SERIAL_UDT_MESSAGE_MUTABLE_TYPE(T, TYPELIST); \
375
376 #define _ASSERT_SERIAL_UDT_MESSAGE_TYPE(T, TYPELIST) \
377 static_assert(serial::assert_udt_message_compatible<typename class_type<T>::as_message, std::tuple<_SERIAL_UDT_UNWRAP_LIST TYPELIST>>::value, "udt/message mismatch")
378
379 #define ASSERT_SERIAL_UDT_MESSAGE_CONST_TYPE(T, TYPELIST) \
380 _ASSERT_SERIAL_UDT_MESSAGE_TYPE(const T, TYPELIST)
381 #define ASSERT_SERIAL_UDT_MESSAGE_MUTABLE_TYPE(T, TYPELIST) \
382 _ASSERT_SERIAL_UDT_MESSAGE_TYPE(T, TYPELIST)
383
384 union endian_skip_byteswap_u
385 {
386 uint8_t c[2];
387 uint16_t s;
endian_skip_byteswap_u(const uint16_t & u)388 constexpr endian_skip_byteswap_u(const uint16_t &u) : s(u)
389 {
390 static_assert((offsetof(endian_skip_byteswap_u, c) == offsetof(endian_skip_byteswap_u, s)), "union layout error");
391 }
392 };
393
endian_skip_byteswap(const uint16_t & endian)394 static inline constexpr uint8_t endian_skip_byteswap(const uint16_t &endian)
395 {
396 return endian_skip_byteswap_u{endian}.c[0];
397 }
398
399 template <typename T, std::size_t N>
400 union unaligned_storage
401 {
402 T a;
403 typename std::conditional<N < 4,
404 typename std::conditional<N == 1, uint8_t, uint16_t>,
405 typename std::conditional<N == 4, uint32_t, uint64_t>>::type::type i;
406 uint8_t u[N];
407 assert_equal(sizeof(i), N, "sizeof(i) is not N");
408 assert_equal(sizeof(a), sizeof(u), "sizeof(T) is not N");
409 };
410
411 template <typename T, typename = void>
412 class message_dispatch_type;
413
414 template <typename T>
415 class message_dispatch_type<T, typename std::enable_if<std::is_integral<T>::value or std::is_enum<T>::value, void>::type> :
416 public detail::message_dispatch_base<detail::size_base<sizeof(T)>>
417 {
418 };
419
420 template <typename T>
421 class message_dispatch_type<T, typename std::enable_if<is_cxx_array<T>::value, void>::type> :
422 public detail::message_dispatch_base<
423 detail::size_base<
424 message_type<typename T::value_type>::maximum_size * std::tuple_size<typename is_cxx_array<T>::array_type>::value,
425 message_type<typename T::value_type>::minimum_size * std::tuple_size<typename is_cxx_array<T>::array_type>::value
426 >
427 >
428 {
429 };
430
431 template <typename T>
432 class message_dispatch_type<T, typename std::enable_if<is_generic_class<T>::value && !is_message<T>::value, void>::type> :
433 public detail::message_dispatch_base<class_type<T>>
434 {
435 };
436
437 template <typename T>
438 class message_type :
439 message_dispatch_type<typename std::remove_reference<T>::type>::effective_type
440 {
441 using effective_type = typename message_dispatch_type<typename std::remove_reference<T>::type>::effective_type;
442 public:
443 using effective_type::maximum_size;
444 using effective_type::minimum_size;
445 };
446
447 template <typename A1>
448 class message_dispatch_type<message<A1>, void> :
449 public detail::message_dispatch_base<message_type<A1>>
450 {
451 public:
452 typedef message<A1> as_message;
453 };
454
455 template <typename... Args>
456 class message_type<message<Args...>> :
457 public detail::size_base<
458 (0 + ... + message_dispatch_type<message<Args>>::effective_type::maximum_size),
459 (0 + ... + message_dispatch_type<message<Args>>::effective_type::minimum_size)
460 >
461 {
462 public:
463 using as_message = message<Args...>;
464 };
465
466 template <typename... Args>
467 class message
468 {
469 static_assert(sizeof...(Args) > 0, "message must have at least one template argument");
470 using tuple_type = std::tuple<typename detail::capture_type<Args &&>::type...>;
471 template <typename T1>
check_type()472 static void check_type()
473 {
474 static_assert(message_type<T1>::maximum_size > 0, "empty field in message");
475 }
476 tuple_type t;
477 public:
message(Args &&...args)478 message(Args &&... args) :
479 t(detail::capture_value<Args>(std::forward<Args>(args))...)
480 {
481 (check_type<Args>(), ...);
482 }
get_tuple()483 const tuple_type &get_tuple() const
484 {
485 return t;
486 }
487 };
488
489 template <typename... Args>
490 message(Args &&... args) -> message<Args && ...>;
491
492 #define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN(HBITS,BITS) \
493 static inline constexpr uint##BITS##_t bswap(const uint##BITS##_t &u) \
494 { \
495 return __builtin_bswap##BITS(u); \
496 }
497
498 #define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT(HBITS,BITS) \
499 static inline constexpr uint##BITS##_t bswap(const uint##BITS##_t &u) \
500 { \
501 return (static_cast<uint##BITS##_t>(bswap(static_cast<uint##HBITS##_t>(u))) << HBITS) | \
502 static_cast<uint##BITS##_t>(bswap(static_cast<uint##HBITS##_t>(u >> HBITS))); \
503 }
504
505 #define SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(HBITS,BITS) \
506 SERIAL_DEFINE_SIZE_SPECIFIC_USWAP(HBITS,BITS); \
507 static inline constexpr int##BITS##_t bswap(const int##BITS##_t &i) \
508 { \
509 return bswap(static_cast<uint##BITS##_t>(i)); \
510 }
511
bswap(const uint8_t & u)512 static inline constexpr uint8_t bswap(const uint8_t &u)
513 {
514 return u;
515 }
516
bswap(const int8_t & u)517 static inline constexpr int8_t bswap(const int8_t &u)
518 {
519 return u;
520 }
521
522 #ifdef DXX_HAVE_BUILTIN_BSWAP16
523 #define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN
524 #else
525 #define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT
526 #endif
527
528 SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(8, 16);
529 #undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP
530
531 #ifdef DXX_HAVE_BUILTIN_BSWAP
532 #define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN
533 #else
534 #define SERIAL_DEFINE_SIZE_SPECIFIC_USWAP SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT
535 #endif
536
537 SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(16, 32);
538 SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP(32, 64);
539
540 #undef SERIAL_DEFINE_SIZE_SPECIFIC_BSWAP
541 #undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP
542 #undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_BUILTIN
543 #undef SERIAL_DEFINE_SIZE_SPECIFIC_USWAP_EXPLICIT
544
545 namespace reader {
546
547 class bytebuffer_t : public detail::base_bytebuffer_t<const uint8_t, bytebuffer_t>
548 {
549 public:
550 using base_bytebuffer_t::base_bytebuffer_t;
551 explicit bytebuffer_t(const bytebuffer_t &) = default;
552 bytebuffer_t(bytebuffer_t &&) = default;
553 };
554
555 template <typename A1, std::size_t BYTES>
unaligned_copy(const uint8_t * src,unaligned_storage<A1,BYTES> & dst)556 static inline void unaligned_copy(const uint8_t *src, unaligned_storage<A1, BYTES> &dst)
557 {
558 if constexpr (BYTES == 1)
559 dst.u[0] = *src;
560 else
561 std::copy_n(src, sizeof(dst.u), dst.u);
562 }
563
564 template <typename Accessor, typename A1>
process_integer(Accessor & buffer,A1 & a1)565 static inline void process_integer(Accessor &buffer, A1 &a1)
566 {
567 using std::advance;
568 unaligned_storage<A1, message_type<A1>::maximum_size> u;
569 unaligned_copy(buffer, u);
570 if (!endian_skip_byteswap(buffer.endian()))
571 u.i = bswap(u.i);
572 a1 = u.a;
573 advance(buffer, sizeof(u.u));
574 }
575
576 template <typename Accessor, typename A, typename T = typename A::value_type>
process_array(Accessor & accessor,A & a)577 static inline typename std::enable_if<sizeof(T) == 1 && std::is_integral<T>::value, void>::type process_array(Accessor &accessor, A &a)
578 {
579 using std::advance;
580 std::copy_n(static_cast<typename Accessor::pointer>(accessor), a.size(), &a[0]);
581 advance(accessor, a.size());
582 }
583
584 template <typename Accessor, typename extended_signed_type, typename wrapped_type>
process_udt(Accessor && accessor,const detail::sign_extend_type<extended_signed_type,wrapped_type> & v)585 static inline void process_udt(Accessor &&accessor, const detail::sign_extend_type<extended_signed_type, wrapped_type> &v)
586 {
587 extended_signed_type est;
588 process_integer<Accessor, extended_signed_type>(static_cast<Accessor &&>(accessor), est);
589 v.get() = static_cast<wrapped_type>(est);
590 }
591
592 }
593
594 namespace writer {
595
596 class bytebuffer_t : public detail::base_bytebuffer_t<uint8_t, bytebuffer_t>
597 {
598 public:
599 using base_bytebuffer_t::base_bytebuffer_t;
600 explicit bytebuffer_t(const bytebuffer_t &) = default;
601 bytebuffer_t(bytebuffer_t &&) = default;
602 };
603
604 /* If unaligned_copy is manually inlined into the caller, then gcc
605 * inlining of copy_n creates a loop instead of a store.
606 */
607 template <typename A1, std::size_t BYTES>
unaligned_copy(const unaligned_storage<A1,BYTES> & src,uint8_t * dst)608 static inline void unaligned_copy(const unaligned_storage<A1, BYTES> &src, uint8_t *dst)
609 {
610 if constexpr (BYTES == 1)
611 *dst = src.u[0];
612 else
613 std::copy_n(src.u, sizeof(src.u), dst);
614 }
615
616 template <typename Accessor, typename A1>
process_integer(Accessor & buffer,const A1 & a1)617 static inline void process_integer(Accessor &buffer, const A1 &a1)
618 {
619 using std::advance;
620 unaligned_storage<A1, message_type<A1>::maximum_size> u{a1};
621 if (!endian_skip_byteswap(buffer.endian()))
622 u.i = bswap(u.i);
623 unaligned_copy(u, buffer);
624 advance(buffer, sizeof(u.u));
625 }
626
627 template <typename Accessor, typename A, typename T = typename A::value_type>
process_array(Accessor & accessor,const A & a)628 static inline typename std::enable_if<sizeof(T) == 1 && std::is_integral<T>::value, void>::type process_array(Accessor &accessor, const A &a)
629 {
630 using std::advance;
631 std::copy_n(&a[0], a.size(), static_cast<typename Accessor::pointer>(accessor));
632 advance(accessor, a.size());
633 }
634
635 template <typename Accessor, typename extended_signed_type, typename wrapped_type>
process_udt(Accessor && accessor,const detail::sign_extend_type<extended_signed_type,const wrapped_type> & v)636 static inline void process_udt(Accessor &&accessor, const detail::sign_extend_type<extended_signed_type, const wrapped_type> &v)
637 {
638 const typename std::make_signed<wrapped_type>::type swt = v.get();
639 const extended_signed_type est = swt;
640 process_integer<Accessor, extended_signed_type>(static_cast<Accessor &&>(accessor), est);
641 }
642
643 }
644
645 template <typename Accessor, typename A1, typename A1rr>
process_buffer(Accessor & accessor,A1 && a1)646 static inline typename std::enable_if<std::is_enum<A1rr>::value, void>::type process_buffer(Accessor &accessor, A1 &&a1)
647 {
648 using detail::check_enum;
649 process_integer(accessor, a1);
650 /* Hook for enum types to check that the given value is legal */
651 check_enum(accessor, a1);
652 }
653
654 template <typename Accessor, typename A1, typename A1rr>
process_buffer(Accessor & accessor,A1 && a1)655 static inline typename std::enable_if<is_generic_class<A1rr>::value, void>::type process_buffer(Accessor &accessor, A1 &&a1)
656 {
657 using detail::preprocess_udt;
658 using detail::process_udt;
659 using detail::postprocess_udt;
660 preprocess_udt(accessor, a1);
661 process_udt(accessor, std::forward<A1>(a1));
662 postprocess_udt(accessor, a1);
663 }
664
665 template <typename Accessor, typename A, typename T = typename A::value_type>
process_array(Accessor & accessor,A & a)666 static typename std::enable_if<!(sizeof(T) == 1 && std::is_integral<T>::value), void>::type process_array(Accessor &accessor, A &a)
667 {
668 range_for (auto &i, a)
669 process_buffer(accessor, i);
670 }
671
672 template <typename Accessor, typename A1>
process_buffer(Accessor && accessor,A1 & a1)673 static typename std::enable_if<is_cxx_array<A1>::value, void>::type process_buffer(Accessor &&accessor, A1 &a1)
674 {
675 process_array(std::forward<Accessor &&>(accessor), a1);
676 }
677
678 template <typename Accessor, typename... Args, std::size_t... N>
process_message_tuple(Accessor && accessor,const std::tuple<Args...> & t,std::index_sequence<N...>)679 static inline void process_message_tuple(Accessor &&accessor, const std::tuple<Args...> &t, std::index_sequence<N...>)
680 {
681 (process_buffer(accessor, detail::extract_value(std::get<N>(t))), ...);
682 }
683
684 template <typename Accessor, typename... Args>
process_buffer(Accessor && accessor,const message<Args...> & m)685 static void process_buffer(Accessor &&accessor, const message<Args...> &m)
686 {
687 process_message_tuple(std::forward<Accessor &&>(accessor), m.get_tuple(), std::make_index_sequence<sizeof...(Args)>());
688 }
689
690 /* Require at least two arguments to prevent self-selection */
691 template <typename Accessor, typename... An>
process_buffer(Accessor && accessor,An &&...an)692 static typename std::enable_if<(sizeof...(An) > 1)>::type process_buffer(Accessor &&accessor, An &&... an)
693 {
694 (process_buffer(accessor, std::forward<An>(an)), ...);
695 }
696
697 }
698