CppSerdes  1.0
A serialization/deserialization library designed with embedded systems in mind
bitcpy_from_array.h
Go to the documentation of this file.
1 #ifndef _BITCPY_FROM_ARRAY_H_
9 #define _BITCPY_FROM_ARRAY_H_
10 
11 #include "bitcpy_common.h"
12 #include <memory>
13 
15 namespace serdes
16 {
17 
28  template <typename T_array, typename T_val, detail::requires_unsigned_type<T_val> * = nullptr>
29  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(T_val &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
30  {
31  constexpr size_t bits_per_T_array = sizeof(T_array) * 8u;
32  constexpr size_t bits_per_T_array_minus_one = bits_per_T_array - 1u;
33  size_t array_read_index = bit_offset / bits_per_T_array;
34  const size_t bit_offset_from_start_index = bit_offset & bits_per_T_array_minus_one; // same as (bit_offset % sizeof(T_array)*8)
35 
36  // shortcut 1: handle exact bit size and offset match
37  if (bits == bits_per_T_array && bit_offset_from_start_index == 0)
38  {
39  dest = static_cast<T_val>(source[array_read_index]);
40  return bits;
41  }
42 
43  // shortcut 2: if the write doesn't need to be split across array elements
44  const size_t number_of_bits_after_index = bit_offset_from_start_index + bits;
45  if (number_of_bits_after_index <= bits_per_T_array)
46  {
47  if (bits == 0) // unlikely. so we reduce the chances of needing to do this check until the last moment
48  return bits;
49  const T_array unaligned_mask = detail::bitmask<T_val>(bits);
50  const size_t alignment_shift = bits_per_T_array - number_of_bits_after_index;
51  dest = static_cast<T_val>(static_cast<T_array>(source[array_read_index] >> alignment_shift) & unaligned_mask);
52  return bits;
53  }
54 
55  // shortcut 3: if there is no left 0 padding, and we wouldn't exceed the buffer
56  // safety limit by possibly reading past the end of the array, we can avoid the loop
57  // by using big_endian_memcpy
58  constexpr size_t bits_per_T_val = sizeof(T_val) * 8u;
59  if (number_of_bits_after_index <= bits_per_T_val)
60  {
61  const size_t alignment_shift = bits_per_T_val - number_of_bits_after_index;
62  if (alignment_shift < bits_per_T_array)
63  {
64  dest = detail::big_endian_memcpy_unsigned<T_val, T_array>(&source[array_read_index]);
65  const T_val unaligned_mask = detail::bitmask<T_val>(bits);
66  dest = static_cast<T_val>(dest >> alignment_shift) & unaligned_mask;
67  return bits;
68  }
69  }
70 
71  // if the read DOES need to be split across > 1 array elements, or left 0 padding is needed
72  const size_t num_array_elements_touched_minus_one = (number_of_bits_after_index + bits_per_T_array - 1u) / bits_per_T_array - 1u;
73  const size_t bits_in_first_element = bits_per_T_array - bit_offset_from_start_index;
74  size_t bits_remaining = bits - bits_in_first_element;
75  size_t start_index = 1u;
76  if (bits_remaining < bits_per_T_val)
77  {
78  const T_array unaligned_mask = detail::bitmask<T_array>(bits_in_first_element);
79  dest = static_cast<T_val>(source[array_read_index] & unaligned_mask) << bits_remaining;
80  }
81  else
82  {
83  // in the case of left 0 padding, don't manually go through the for loop, just skip ahead to where
84  // in the loop it would start writting meaningful data
85  dest = 0;
86  const size_t start_index_minus_one = (bits_remaining - bits_per_T_val) / bits_per_T_array;
87  start_index = start_index_minus_one + 1u;
88  bits_remaining -= bits_per_T_array * start_index_minus_one;
89  array_read_index += start_index_minus_one;
90  }
91  for (size_t i = start_index; i < num_array_elements_touched_minus_one; i++)
92  {
93  bits_remaining -= bits_per_T_array;
94  ++array_read_index;
95  dest |= static_cast<T_val>(source[array_read_index]) << bits_remaining;
96  }
97  {
98  const size_t remainder_right_shift = bits_per_T_array - bits_remaining;
99  const T_array unaligned_mask = detail::bitmask<T_array>(bits_remaining);
100  ++array_read_index;
101  dest |= static_cast<T_val>((source[array_read_index] >> remainder_right_shift) & unaligned_mask);
102  }
103  return bits;
104  }
105 
116  template <typename T_array, typename T_val, detail::requires_bool_type<T_val> * = nullptr>
117  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(T_val &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
118  {
119  if (bits == 1)
120  {
121  constexpr size_t bits_per_T_array = sizeof(T_array) * 8u;
122  constexpr size_t bits_per_T_array_minus_one = bits_per_T_array - 1u;
123  const T_array bit_mask = static_cast<T_array>(1u) << (bits_per_T_array_minus_one - (bit_offset & bits_per_T_array_minus_one));
124  const size_t array_index = bit_offset / bits_per_T_array;
125  dest = (source[array_index] & bit_mask) == bit_mask;
126  return bits;
127  }
128  uint8_t temp_write_val = 0u;
129  bitcpy(temp_write_val, source, bit_offset, bits);
130  dest = temp_write_val != 0u;
131  return bits;
132  }
133 
144  template <typename T_array, typename T_val, detail::requires_small_non_integral_type<T_val> * = nullptr>
145  size_t bitcpy(T_val &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
146  {
147  // note: reinterpret_as_unsigned is undefined for non integral types, so a slower std::memcpy is used instead, since a reinterpret is
148  // more likely to not work on some platforms, the configCPP_SERDES_ALLOW_REINTERPRET_BUILTINS_UB option is not provided
149  typename detail::unsigned_type_sizeof<sizeof(T_val)>::type dest_copy{};
150  const size_t result_size = bitcpy(dest_copy, source, bit_offset, bits);
151  if (result_size == 0u)
152  return 0u;
153  std::memcpy(&dest, &dest_copy, sizeof(T_val));
154  return result_size;
155  }
156 
167  template <typename T_array, typename T_val, detail::requires_signed_type<T_val> * = nullptr>
168  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(T_val &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
169  {
170  #ifdef configCPP_SERDES_ALLOW_REINTERPRET_BUILTINS_UB
171  bitcpy(detail::reinterpret_as_unsigned(dest), source, bit_offset, bits);
172  detail::extend_sign(dest, bits);
173  #else
174  typename detail::unsigned_type_sizeof<sizeof(T_val)>::type dest_copy{};
175  bitcpy(dest_copy, source, bit_offset, bits);
176  std::memcpy(&dest, &dest_copy, sizeof(T_val));
177  detail::extend_sign(dest, bits);
178  #endif
179  return bits;
180  }
181 
192  template <typename T_array, typename T_val, detail::requires_unsigned_type<T_val> * = nullptr>
193  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(sized_pointer<T_val> &dest, const T_array *const source, const size_t bit_offset, const size_t bits) noexcept
194  {
195  constexpr size_t bits_per_T_val = sizeof(T_val) * 8;
196  const size_t total_bits_T_val = bits_per_T_val * dest.size;
197  if (bits == total_bits_T_val)
198  {
199  for (size_t i = 0; i < dest.size; i++)
200  bitcpy(dest.value[i], source, bit_offset + i * bits_per_T_val, bits_per_T_val);
201  }
202  else if (bits < total_bits_T_val)
203  {
204  const size_t bit_difference = total_bits_T_val - bits;
205  const size_t starting_source_word = bit_difference / bits_per_T_val;
206  const size_t last_bit_index = dest.size - 1u;
207  const size_t last_bit_suboffset = bits_per_T_val * last_bit_index - bit_difference;
208  const size_t remainder_bits = bits - last_bit_suboffset;
209  const std::make_signed<size_t>::type offset_per_loop = bit_offset - bit_difference;
210  for (size_t i = starting_source_word; i < dest.size - 1u; i++)
211  bitcpy(dest.value[i], source, offset_per_loop + i * bits_per_T_val, bits_per_T_val);
212  bitcpy(dest.value[last_bit_index], source, bit_offset + last_bit_suboffset, remainder_bits);
213  }
214  else
215  {
216  size_t adjusted_offset = bit_offset + bits - total_bits_T_val;
217  bitcpy(dest.value[0], source, adjusted_offset, bits_per_T_val);
218  for (size_t i = 1; i < dest.size; i++)
219  {
220  adjusted_offset += bits_per_T_val;
221  bitcpy(dest.value[i], source, adjusted_offset, bits_per_T_val);
222  }
223  }
224  return bits;
225  }
226 
237  template <typename T_array, typename T_val, detail::requires_unsigned_type<T_val> * = nullptr>
238  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(sized_pointer<T_val> &dest, const T_array *const source, const size_t bit_offset = 0) noexcept
239  {
240  return bitcpy(dest, source, bit_offset, dest.bit_capacity());
241  }
242 
253  template <typename T_array, typename T_val, detail::requires_large_non_integral_type<T_val> * = nullptr>
254 #ifndef __clang__
256 #endif
257  size_t
258  bitcpy(T_val &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
259  {
260  sized_pointer<uint8_t> dest_array(reinterpret_cast<uint8_t *>(&dest), sizeof(T_val));
261  return bitcpy(dest_array, source, bit_offset, bits);
262  }
263 
274  template <typename T_array = void, typename T_val = void, detail::requires_not_pointer_or_load_store<T_val> * = nullptr>
275  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(T_val &dest, const sized_pointer<T_array> source, size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
276  {
277  if (source.bit_capacity() < (bits + bit_offset))
278  return 0;
279  bitcpy(dest, source.value, bit_offset, bits);
280  return bits;
281  }
282 
283  // either the from_array or the to_array implementation needs a specialization to de-conflict
284  // pointer to pointer transfers the from_array was arbitrarily picked, the result is that for
285  // the ambiguous case, they can either casting to a uintptr_t. Or they can use sized_pointer
286  // to indicate the array object, like so:
287  //
288  // serdes::bitcpy(pointer_value, serdes::sized_pointer(buffer)); // from array
289  // serdes::bitcpy(serdes::sized_pointer(buffer), pointer_value); // to array
290  namespace detail
291  {
292  template <typename T_array, typename T_val>
293  size_t bitcpy_disambiguous_pointer_from_array(T_val &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
294  {
295  // note: reinterpret_as_unsigned is undefined for non integral types, so a slower std::memcpy is used instead, since a reinterpret is
296  // more likely to not work on some platforms, the configCPP_SERDES_ALLOW_REINTERPRET_BUILTINS_UB option is not provided
297  typename detail::unsigned_type_sizeof<sizeof(T_val)>::type dest_copy{};
298  const size_t result_size = bitcpy(dest_copy, source, bit_offset, bits);
299  if (result_size == 0u)
300  return 0u;
301  std::memcpy(&dest, &dest_copy, sizeof(T_val));
302  return result_size;
303  }
304  }
305 
317  template <typename T_array = void, typename T_val = void, detail::requires_pointer_type<T_val> * = nullptr>
318  size_t bitcpy(T_val &dest, const sized_pointer<T_array> source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
319  {
320  if (source.bit_capacity() < (bits + bit_offset))
321  return 0;
322  return detail::bitcpy_disambiguous_pointer_from_array(dest, source.value, bit_offset, bits);
323  }
324 
337  template <typename T_array, typename T_val, template<typename> class T_wrap, detail::requires_load_and_store_of_builtin<T_wrap<T_val>> * = nullptr>
338  size_t bitcpy(T_wrap<T_val> &dest, const T_array *const source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
339  {
340  T_val temp_value = 0;
341  const size_t bits_written = bitcpy(temp_value, source, bit_offset, bits);
342  if (bits_written > 0)
343  dest.store(temp_value);
344  return bits_written;
345  }
346 
357  template <typename T_val = void>
358  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(T_val &dest, const sized_pointer<void> &source, const size_t bit_offset = 0, const size_t bits = detail::default_bitsize<T_val>::value) noexcept
359  {
360  if (source.bit_capacity() < (bits + bit_offset))
361  return 0;
362  switch (source.element_size)
363  {
364  case 1:
365  return bitcpy(dest, reinterpret_cast<const uint8_t *>(source.value), bit_offset, bits);
366  case 2:
367  return bitcpy(dest, reinterpret_cast<const uint16_t *>(source.value), bit_offset, bits);
368  case 4:
369  return bitcpy(dest, reinterpret_cast<const uint32_t *>(source.value), bit_offset, bits);
370  case 8:
371  return bitcpy(dest, reinterpret_cast<const uint64_t *>(source.value), bit_offset, bits);
372  default:
373  break;
374  }
375  return 0;
376  }
377 }
378 
379 #endif // _BITCPY_FROM_ARRAY_H_
bitcpy_common.h
Defines common bitcpy details as well as a info::version number, and bit_length function.
serdes::sized_pointer< void >
Holds a pointer to a void array (with type information stored as a runtime element size paramenter) w...
Definition: bitcpy_sized_pointer.h:48
serdes::sized_pointer
Holds a pointer to an array (with its type information) with a constant size.
Definition: bitcpy_sized_pointer.h:19
CONSTEXPR_ABOVE_CPP11
#define CONSTEXPR_ABOVE_CPP11
resolves automatically to "constexpr" if C++14 or greater
Definition: bitcpy_common.h:24
serdes::sized_pointer::bit_capacity
size_t bit_capacity() const noexcept
returns the number of bits in the array
Definition: bitcpy_sized_pointer.h:39
serdes::bitcpy
CONSTEXPR_ABOVE_CPP11 size_t bitcpy(T_val &dest, const T_array *const source, const size_t bit_offset=0, const size_t bits=detail::default_bitsize< T_val >::value) noexcept
[[deserialize, uint destination]] copies the specified number of bits from an array into a value
Definition: bitcpy_from_array.h:29
serdes
CppSerdes library namespace.
Definition: bitcpy_common.h:68