BitStream
bit_reader.h
Go to the documentation of this file.
1 #pragma once
2 #include "../utility/assert.h"
3 #include "../utility/crc.h"
4 #include "../utility/endian.h"
5 #include "../utility/meta.h"
6 
7 #include "byte_buffer.h"
8 #include "serialize_traits.h"
9 #include "stream_traits.h"
10 
11 #include <cstdint>
12 #include <cstring>
13 #include <string>
14 #include <type_traits>
15 
16 namespace bitstream
17 {
22  template<typename Policy>
23  class bit_reader
24  {
25  public:
26  static constexpr bool writing = false;
27  static constexpr bool reading = true;
28 
33  template<typename... Ts,
34  typename = std::enable_if_t<std::is_constructible_v<Policy, Ts...>>>
35  bit_reader(Ts&&... args)
36  noexcept(std::is_nothrow_constructible_v<Policy, Ts...>) :
37  m_Policy(std::forward<Ts>(args) ...),
38  m_Scratch(0),
39  m_ScratchBits(0),
40  m_WordIndex(0) {}
41 
42  bit_reader(const bit_reader&) = delete;
43 
44  bit_reader(bit_reader&& other) noexcept :
45  m_Policy(std::move(other.m_Policy)),
46  m_Scratch(other.m_Scratch),
47  m_ScratchBits(other.m_ScratchBits),
48  m_WordIndex(other.m_WordIndex)
49  {
50  other.m_Scratch = 0;
51  other.m_ScratchBits = 0;
52  other.m_WordIndex = 0;
53  }
54 
55  bit_reader& operator=(const bit_reader&) = delete;
56 
57  bit_reader& operator=(bit_reader&& rhs) noexcept
58  {
59  m_Policy = std::move(rhs.m_Policy);
60  m_Scratch = rhs.m_Scratch;
61  m_ScratchBits = rhs.m_ScratchBits;
62  m_WordIndex = rhs.m_WordIndex;
63 
64  rhs.m_Scratch = 0;
65  rhs.m_ScratchBits = 0;
66  rhs.m_WordIndex = 0;
67 
68  return *this;
69  }
70 
75  [[nodiscard]] const uint8_t* get_buffer() const noexcept { return reinterpret_cast<const uint8_t*>(m_Policy.get_buffer()); }
76 
81  [[nodiscard]] uint32_t get_num_bits_serialized() const noexcept { return m_Policy.get_num_bits_serialized(); }
82 
87  [[nodiscard]] uint32_t get_num_bytes_serialized() const noexcept { return get_num_bits_serialized() > 0U ? ((get_num_bits_serialized() - 1U) / 8U + 1U) : 0U; }
88 
94  [[nodiscard]] bool can_serialize_bits(uint32_t num_bits) const noexcept { return m_Policy.can_serialize_bits(num_bits); }
95 
101  [[nodiscard]] uint32_t get_remaining_bits() const noexcept { return get_total_bits() - get_num_bits_serialized(); }
102 
107  [[nodiscard]] uint32_t get_total_bits() const noexcept { return m_Policy.get_total_bits(); }
108 
114  [[nodiscard]] bool serialize_checksum(uint32_t protocol_version) noexcept
115  {
117 
119 
120  uint32_t num_bytes = (get_total_bits() - 1U) / 8U + 1U;
121  const uint32_t* buffer = m_Policy.get_buffer();
122 
123  // Generate checksum to compare against
124  uint32_t generated_checksum = utility::crc_uint32(reinterpret_cast<const uint8_t*>(&protocol_version), reinterpret_cast<const uint8_t*>(buffer + 1), num_bytes - 4);
125 
126  // Advance the reader by the size of the checksum (32 bits / 1 word)
127  m_WordIndex++;
128 
129  BS_ASSERT(m_Policy.extend(32U));
130 
131  // Read the checksum
132  uint32_t checksum = *buffer;
133 
134  // Compare the checksum
135  return generated_checksum == checksum;
136  }
137 
143  [[nodiscard]] bool pad_to_size(uint32_t num_bytes) noexcept
144  {
145  uint32_t num_bits_read = get_num_bits_serialized();
146 
147  BS_ASSERT(num_bytes * 8U >= num_bits_read);
148 
149  BS_ASSERT(can_serialize_bits(num_bytes * 8U - num_bits_read));
150 
151  uint32_t remainder = (num_bytes * 8U - num_bits_read) % 32U;
152  uint32_t zero;
153 
154  // Test the last word more carefully, as it may have data
155  if (remainder != 0U)
156  {
157  bool status = serialize_bits(zero, remainder);
158  BS_ASSERT(status && zero == 0);
159  }
160 
161  uint32_t offset = get_num_bits_serialized() / 32;
162  uint32_t max = num_bytes / 4;
163 
164  // Test for zeros in padding
165  for (uint32_t i = offset; i < max; i++)
166  {
167  bool status = serialize_bits(zero, 32);
168  BS_ASSERT(status && zero == 0);
169  }
170 
171  return true;
172  }
173 
179  [[nodiscard]] bool pad(uint32_t num_bytes) noexcept
180  {
181  return pad_to_size(get_num_bytes_serialized() + num_bytes);
182  }
183 
189  [[nodiscard]] bool align() noexcept
190  {
191  uint32_t remainder = get_num_bits_serialized() % 8U;
192  if (remainder != 0U)
193  {
194  uint32_t zero;
195  bool status = serialize_bits(zero, 8U - remainder);
196 
197  BS_ASSERT(status && zero == 0U && get_num_bits_serialized() % 8U == 0U);
198  }
199 
200  return true;
201  }
202 
209  [[nodiscard]] bool serialize_bits(uint32_t& value, uint32_t num_bits) noexcept
210  {
211  BS_ASSERT(num_bits > 0U && num_bits <= 32U);
212 
213  BS_ASSERT(m_Policy.extend(num_bits));
214 
215  // Fast path
216  if (num_bits == 32U && m_ScratchBits == 0U)
217  {
218  const uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
219 
220  value = utility::to_big_endian32(*ptr);
221 
222  m_WordIndex++;
223 
224  return true;
225  }
226 
227  if (m_ScratchBits < num_bits)
228  {
229  const uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
230 
231  uint64_t ptr_value = static_cast<uint64_t>(utility::to_big_endian32(*ptr)) << (32U - m_ScratchBits);
232  m_Scratch |= ptr_value;
233  m_ScratchBits += 32U;
234  m_WordIndex++;
235  }
236 
237  uint32_t offset = 64U - num_bits;
238  value = static_cast<uint32_t>(m_Scratch >> offset);
239 
240  m_Scratch <<= num_bits;
241  m_ScratchBits -= num_bits;
242 
243  return true;
244  }
245 
252  [[nodiscard]] bool serialize_bytes(uint8_t* bytes, uint32_t num_bits) noexcept
253  {
254  BS_ASSERT(num_bits > 0U);
255 
256  BS_ASSERT(can_serialize_bits(num_bits));
257 
258  // Read the byte array as words
259  uint32_t* word_buffer = reinterpret_cast<uint32_t*>(bytes);
260  uint32_t num_words = num_bits / 32U;
261 
262  if (m_ScratchBits % 32U == 0U && num_words > 0U)
263  {
264  BS_ASSERT(m_Policy.extend(num_words * 32U));
265 
266  // If the read buffer is word-aligned, just memcpy it
267  std::memcpy(word_buffer, m_Policy.get_buffer() + m_WordIndex, num_words * 4U);
268 
269  m_WordIndex += num_words;
270  }
271  else
272  {
273  // If the buffer is not word-aligned, serialize a word at a time
274  for (uint32_t i = 0U; i < num_words; i++)
275  {
276  uint32_t value;
277  BS_ASSERT(serialize_bits(value, 32U));
278 
279  // Casting a byte-array to an int is wrong on little-endian systems
280  // We have to swap the bytes around
281  word_buffer[i] = utility::to_big_endian32(value);
282  }
283  }
284 
285  // Early exit if the word-count matches
286  if (num_bits % 32 == 0)
287  return true;
288 
289  uint32_t remaining_bits = num_bits - num_words * 32U;
290 
291  uint32_t num_bytes = (remaining_bits - 1U) / 8U + 1U;
292  for (uint32_t i = 0; i < num_bytes; i++)
293  {
294  uint32_t value;
295  BS_ASSERT(serialize_bits(value, (std::min)(remaining_bits - i * 8U, 8U)));
296 
297  bytes[num_words * 4 + i] = static_cast<uint8_t>(value);
298  }
299 
300  return true;
301  }
302 
311  template<typename Trait, typename... Args, typename = utility::has_serialize_t<Trait, bit_reader, Args...>>
312  [[nodiscard]] bool serialize(Args&&... args) noexcept(utility::is_serialize_noexcept_v<Trait, bit_reader, Args...>)
313  {
314  return serialize_traits<Trait>::serialize(*this, std::forward<Args>(args)...);
315  }
316 
327  template<typename... Args, typename Trait, typename = utility::has_deduce_serialize_t<Trait, bit_reader, Args...>>
328  [[nodiscard]] bool serialize(Trait&& arg, Args&&... args) noexcept(utility::is_deduce_serialize_noexcept_v<Trait, bit_reader, Args...>)
329  {
330  return serialize_traits<utility::deduce_trait_t<Trait, bit_reader, Args...>>::serialize(*this, std::forward<Trait>(arg), std::forward<Args>(args)...);
331  }
332 
333  private:
334  Policy m_Policy;
335 
336  uint64_t m_Scratch;
337  uint32_t m_ScratchBits;
338  uint32_t m_WordIndex;
339  };
340 
342 }
#define BS_ASSERT(x)
Definition: assert.h:15
A stream for reading objects from a tightly packed buffer.
Definition: bit_reader.h:24
static constexpr bool writing
Definition: bit_reader.h:26
bool serialize_bytes(uint8_t *bytes, uint32_t num_bits) noexcept
Reads the first num_bits bits of the given byte array, 32 bits at a time.
Definition: bit_reader.h:252
uint32_t get_num_bits_serialized() const noexcept
Returns the number of bits which have been read from the buffer.
Definition: bit_reader.h:81
uint32_t get_remaining_bits() const noexcept
Returns the number of bits which have not been read yet.
Definition: bit_reader.h:101
bool serialize_checksum(uint32_t protocol_version) noexcept
Reads the first 32 bits of the buffer and compares it to a checksum of the protocol_version and the r...
Definition: bit_reader.h:114
bool serialize(Trait &&arg, Args &&... args) noexcept(utility::is_deduce_serialize_noexcept_v< Trait, bit_reader, Args... >)
Reads from the buffer, by trying to deduce the trait.
Definition: bit_reader.h:328
uint32_t get_num_bytes_serialized() const noexcept
Returns the number of bytes which have been read from the buffer.
Definition: bit_reader.h:87
bool can_serialize_bits(uint32_t num_bits) const noexcept
Returns whether the num_bits be read from the buffer.
Definition: bit_reader.h:94
bit_reader(Ts &&... args) noexcept(std::is_nothrow_constructible_v< Policy, Ts... >)
Construct a reader with the parameters passed to the underlying policy.
Definition: bit_reader.h:35
bool pad(uint32_t num_bytes) noexcept
Pads the buffer up with the given number of bytes.
Definition: bit_reader.h:179
bool serialize(Args &&... args) noexcept(utility::is_serialize_noexcept_v< Trait, bit_reader, Args... >)
Reads from the buffer, using the given Trait.
Definition: bit_reader.h:312
static constexpr bool reading
Definition: bit_reader.h:27
bit_reader(const bit_reader &)=delete
bit_reader & operator=(const bit_reader &)=delete
bool align() noexcept
Pads the buffer with up to 8 zeros, so that the next read is byte-aligned @notes Return false if the ...
Definition: bit_reader.h:189
bool serialize_bits(uint32_t &value, uint32_t num_bits) noexcept
Reads the first num_bits bits of value from the buffer.
Definition: bit_reader.h:209
uint32_t get_total_bits() const noexcept
Returns the size of the buffer, in bits.
Definition: bit_reader.h:107
bool pad_to_size(uint32_t num_bytes) noexcept
Pads the buffer up to the given number of bytes.
Definition: bit_reader.h:143
bit_reader & operator=(bit_reader &&rhs) noexcept
Definition: bit_reader.h:57
bit_reader(bit_reader &&other) noexcept
Definition: bit_reader.h:44
const uint8_t * get_buffer() const noexcept
Returns the buffer that this reader is currently serializing from.
Definition: bit_reader.h:75
constexpr uint32_t crc_uint32(const uint8_t *bytes, uint32_t size)
Definition: crc.h:25
has_serialize_t< deduce_trait_t< Trait, Stream, Args... >, Stream, Trait, Args... > has_deduce_serialize_t
Definition: meta.h:101
uint32_t to_big_endian32(uint32_t value)
Definition: endian.h:82
typename deduce_trait< void, Trait, Stream, Args... >::type deduce_trait_t
Definition: meta.h:96
std::void_t< decltype(serialize_traits< T >::serialize(std::declval< Stream & >(), std::declval< Args >()...))> has_serialize_t
Definition: meta.h:17
Definition: bounded_range.h:28
A class for specializing trait serialization functions.
Definition: serialize_traits.h:11