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 memcpy is used instead, since a reinterpret may not always work on some platforms
148  typename detail::unsigned_type_sizeof<sizeof(T_val)>::type dest_copy;
149  const size_t result_size = bitcpy(dest_copy, source, bit_offset, bits);
150  if (result_size == 0u)
151  return 0u;
152  memcpy(&dest, &dest_copy, sizeof(T_val));
153  return result_size;
154  }
155 
166  template <typename T_array, typename T_val, detail::requires_signed_type<T_val> * = nullptr>
167  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
168  {
169  bitcpy(detail::reinterpret_as_unsigned(dest), source, bit_offset, bits);
170  detail::extend_sign(dest, bits);
171  return bits;
172  }
173 
184  template <typename T_array, typename T_val, detail::requires_unsigned_type<T_val> * = nullptr>
185  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
186  {
187  constexpr size_t bits_per_T_val = sizeof(T_val) * 8;
188  const size_t total_bits_T_val = bits_per_T_val * dest.size;
189  if (bits == total_bits_T_val)
190  {
191  for (size_t i = 0; i < dest.size; i++)
192  bitcpy(dest.value[i], source, bit_offset + i * bits_per_T_val, bits_per_T_val);
193  }
194  else if (bits < total_bits_T_val)
195  {
196  const size_t bit_difference = total_bits_T_val - bits;
197  const size_t starting_source_word = bit_difference / bits_per_T_val;
198  const size_t last_bit_index = dest.size - 1u;
199  const size_t last_bit_suboffset = bits_per_T_val * last_bit_index - bit_difference;
200  const size_t remainder_bits = bits - last_bit_suboffset;
201  const std::make_signed<size_t>::type offset_per_loop = bit_offset - bit_difference;
202  for (size_t i = starting_source_word; i < dest.size - 1u; i++)
203  bitcpy(dest.value[i], source, offset_per_loop + i * bits_per_T_val, bits_per_T_val);
204  bitcpy(dest.value[last_bit_index], source, bit_offset + last_bit_suboffset, remainder_bits);
205  }
206  else
207  {
208  size_t adjusted_offset = bit_offset + bits - total_bits_T_val;
209  bitcpy(dest.value[0], source, adjusted_offset, bits_per_T_val);
210  for (size_t i = 1; i < dest.size; i++)
211  {
212  adjusted_offset += bits_per_T_val;
213  bitcpy(dest.value[i], source, adjusted_offset, bits_per_T_val);
214  }
215  }
216  return bits;
217  }
218 
229  template <typename T_array, typename T_val, detail::requires_unsigned_type<T_val> * = nullptr>
230  CONSTEXPR_ABOVE_CPP11 size_t bitcpy(sized_pointer<T_val> &dest, const T_array *const source, const size_t bit_offset = 0) noexcept
231  {
232  return bitcpy(dest, source, bit_offset, dest.bit_capacity());
233  }
234 
245  template <typename T_array, typename T_val, detail::requires_large_non_integral_type<T_val> * = nullptr>
246 #ifndef __clang__
248 #endif
249  size_t
250  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
251  {
252  sized_pointer<uint8_t> dest_array(reinterpret_cast<uint8_t *>(&dest), sizeof(T_val));
253  return bitcpy(dest_array, source, bit_offset, bits);
254  }
255 
266  template <typename T_array = void, typename T_val = void, detail::requires_not_a_pointer_type<T_val> * = nullptr>
267  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
268  {
269  if (source.bit_capacity() < (bits + bit_offset))
270  return 0;
271  bitcpy(dest, source.value, bit_offset, bits);
272  return bits;
273  }
274 
275  // either the from_array or the to_array implimentation needs a specialization to de-conflict
276  // pointer to pointer transfers the from_array was arbitrarily picked, the result is that for
277  // the ambiguous case, they can either casting to a uintptr_t. Or they can use sized_pointer
278  // to indicate the array object, like so:
279  //
280  // serdes::bitcpy(pointer_value, serdes::sized_pointer(buffer)); // from array
281  // serdes::bitcpy(serdes::sized_pointer(buffer), pointer_value); // to array
282  namespace detail
283  {
284  template <typename T_array, typename T_val>
285  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
286  {
287  // note: reinterpret_as_unsigned is undefined for non integral types, so a slower memcpy is used instead, since a reinterpret may not always work on some platforms
288  typename detail::unsigned_type_sizeof<sizeof(T_val)>::type dest_copy;
289  const size_t result_size = bitcpy(dest_copy, source, bit_offset, bits);
290  if (result_size == 0u)
291  return 0u;
292  memcpy(&dest, &dest_copy, sizeof(T_val));
293  return result_size;
294  }
295  }
296 
308  template <typename T_array = void, typename T_val = void, detail::requires_pointer_type<T_val> * = nullptr>
309  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
310  {
311  if (source.bit_capacity() < (bits + bit_offset))
312  return 0;
313  return detail::bitcpy_disambiguous_pointer_from_array(dest, source.value, bit_offset, bits);
314  }
315 
326  template <typename T_array, typename T_val>
327  size_t bitcpy(std::atomic<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
328  {
329  T_val temp_value = 0;
330  const size_t bits_written = bitcpy(temp_value, source, bit_offset, bits);
331  if (bits_written > 0)
332  dest.store(temp_value);
333  return bits_written;
334  }
335 
346  template <typename T_val = void>
347  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
348  {
349  if (source.bit_capacity() < (bits + bit_offset))
350  return 0;
351  switch (source.element_size)
352  {
353  case 1:
354  return bitcpy(dest, reinterpret_cast<const uint8_t *>(source.value), bit_offset, bits);
355  case 2:
356  return bitcpy(dest, reinterpret_cast<const uint16_t *>(source.value), bit_offset, bits);
357  case 4:
358  return bitcpy(dest, reinterpret_cast<const uint32_t *>(source.value), bit_offset, bits);
359  case 8:
360  return bitcpy(dest, reinterpret_cast<const uint64_t *>(source.value), bit_offset, bits);
361  default:
362  break;
363  }
364  return 0;
365  }
366 }
367 
368 #endif // _BITCPY_FROM_ARRAY_H_
Defines common bitcpy details as well as a info::version number, and bit_length function.
CppSerdes library namespace.
Definition: bitcpy_common.h:69
T_array *const value
the underlying pointer
Definition: bitcpy_sized_pointer.h:22
size_t bit_capacity() const noexcept
returns the number of bits in the array
Definition: bitcpy_sized_pointer.h:76
const uint_fast8_t element_size
Number of bytes per element of the original array type, same as sizeof(T_original) ...
Definition: bitcpy_sized_pointer.h:57
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
void *const value
the underlying pointer reinterpreted as a void* type
Definition: bitcpy_sized_pointer.h:51
size_t bit_capacity() const noexcept
returns the number of bits in the array
Definition: bitcpy_sized_pointer.h:39
Holds a pointer to an array (with its type information) with a constant size.
Definition: bitcpy_sized_pointer.h:19
Holds a pointer to a void array (with type information stored as a runtime element size paramenter) w...
Definition: bitcpy_sized_pointer.h:48
#define CONSTEXPR_ABOVE_CPP11
resolves automatically to "constexpr" if C++14 or greater
Definition: bitcpy_common.h:31