BitStream
bit_writer.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 <memory>
14 #include <type_traits>
15 
16 namespace bitstream
17 {
22  template<typename Policy>
23  class bit_writer
24  {
25  public:
26  static constexpr bool writing = true;
27  static constexpr bool reading = false;
28 
33  template<typename... Ts,
34  typename = std::enable_if_t<std::is_constructible_v<Policy, Ts...>>>
35  bit_writer(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_writer(const bit_writer&) = delete;
43 
44  bit_writer(bit_writer&& 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_writer& operator=(const bit_writer&) = delete;
56 
57  bit_writer& operator=(bit_writer&& 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]] uint8_t* get_buffer() const noexcept { return reinterpret_cast<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 
113  uint32_t flush() noexcept
114  {
115  if (m_ScratchBits > 0U)
116  {
117  uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
118  uint32_t ptr_value = static_cast<uint32_t>(m_Scratch >> 32U);
119  *ptr = utility::to_big_endian32(ptr_value);
120 
121  m_Scratch = 0U;
122  m_ScratchBits = 0U;
123  m_WordIndex++;
124  }
125 
126  return get_num_bits_serialized();
127  }
128 
133  [[nodiscard]] bool prepend_checksum() noexcept
134  {
136 
137  BS_ASSERT(m_Policy.extend(32U));
138 
139  // Advance the reader by the size of the checksum (32 bits / 1 word)
140  m_WordIndex++;
141 
142  return true;
143  }
144 
150  uint32_t serialize_checksum(uint32_t protocol_version) noexcept
151  {
152  uint32_t num_bits = flush();
153 
154  BS_ASSERT(num_bits > 32U);
155 
156  // Copy protocol version to buffer
157  uint32_t* buffer = m_Policy.get_buffer();
158  *buffer = protocol_version;
159 
160  // Generate checksum of version + data
161  uint32_t checksum = utility::crc_uint32(reinterpret_cast<uint8_t*>(buffer), get_num_bytes_serialized());
162 
163  // Put checksum at beginning
164  *buffer = checksum;
165 
166  return num_bits;
167  }
168 
174  [[nodiscard]] bool pad_to_size(uint32_t num_bytes) noexcept
175  {
176  uint32_t num_bits_written = get_num_bits_serialized();
177 
178  BS_ASSERT(num_bytes * 8U >= num_bits_written);
179 
180  BS_ASSERT(can_serialize_bits(num_bytes * 8U - num_bits_written));
181 
182  if (num_bits_written == 0)
183  {
184  BS_ASSERT(m_Policy.extend(num_bytes * 8U - num_bits_written));
185 
186  std::memset(m_Policy.get_buffer(), 0, num_bytes);
187 
188  m_Scratch = 0;
189  m_ScratchBits = 0;
190  m_WordIndex = num_bytes / 4;
191 
192  return true;
193  }
194 
195  uint32_t remainder = (num_bytes * 8U - num_bits_written) % 32U;
196  uint32_t zero = 0;
197 
198  // Align to byte
199  if (remainder != 0U)
200  BS_ASSERT(serialize_bits(zero, remainder));
201 
202  uint32_t offset = get_num_bits_serialized() / 32;
203  uint32_t max = num_bytes / 4;
204 
205  // Serialize words
206  for (uint32_t i = offset; i < max; i++)
207  BS_ASSERT(serialize_bits(zero, 32));
208 
209  return true;
210  }
211 
217  [[nodiscard]] bool pad(uint32_t num_bytes) noexcept
218  {
219  return pad_to_size(get_num_bytes_serialized() + num_bytes);
220  }
221 
226  [[nodiscard]] bool align() noexcept
227  {
228  uint32_t remainder = m_ScratchBits % 8U;
229  if (remainder != 0U)
230  {
231  uint32_t zero = 0U;
232  bool status = serialize_bits(zero, 8U - remainder);
233 
234  BS_ASSERT(status && get_num_bits_serialized() % 8U == 0U);
235  }
236  return true;
237  }
238 
245  [[nodiscard]] bool serialize_bits(uint32_t value, uint32_t num_bits) noexcept
246  {
247  BS_ASSERT(num_bits > 0U && num_bits <= 32U);
248 
249  BS_ASSERT(m_Policy.extend(num_bits));
250 
251  // Fast path
252  if (num_bits == 32U && m_ScratchBits == 0U)
253  {
254  uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
255 
256  *ptr = utility::to_big_endian32(value);
257 
258  m_WordIndex++;
259 
260  return true;
261  }
262 
263  uint32_t offset = 64U - num_bits - m_ScratchBits;
264  uint64_t ls_value = static_cast<uint64_t>(value) << offset;
265 
266  m_Scratch |= ls_value;
267  m_ScratchBits += num_bits;
268 
269  if (m_ScratchBits >= 32U)
270  {
271  uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
272  uint32_t ptr_value = static_cast<uint32_t>(m_Scratch >> 32U);
273  *ptr = utility::to_big_endian32(ptr_value);
274  m_Scratch <<= 32ULL;
275  m_ScratchBits -= 32U;
276  m_WordIndex++;
277  }
278 
279  return true;
280  }
281 
288  [[nodiscard]] bool serialize_bytes(const uint8_t* bytes, uint32_t num_bits) noexcept
289  {
290  BS_ASSERT(num_bits > 0U);
291 
292  BS_ASSERT(can_serialize_bits(num_bits));
293 
294  // Write the byte array as words
295  const uint32_t* word_buffer = reinterpret_cast<const uint32_t*>(bytes);
296  uint32_t num_words = num_bits / 32U;
297 
298  if (m_ScratchBits % 32U == 0U && num_words > 0U)
299  {
300  BS_ASSERT(m_Policy.extend(num_words * 32U));
301 
302  // If the written buffer is word-aligned, just memcpy it
303  std::memcpy(m_Policy.get_buffer() + m_WordIndex, word_buffer, num_words * 4U);
304 
305  m_WordIndex += num_words;
306  }
307  else
308  {
309  // If the buffer is not word-aligned, serialize a word at a time
310  for (uint32_t i = 0U; i < num_words; i++)
311  {
312  // Casting a byte-array to an int is wrong on little-endian systems
313  // We have to swap the bytes around
314  uint32_t value = utility::to_big_endian32(word_buffer[i]);
315  BS_ASSERT(serialize_bits(value, 32U));
316  }
317  }
318 
319  // Early exit if the word-count matches
320  if (num_bits % 32U == 0U)
321  return true;
322 
323  uint32_t remaining_bits = num_bits - num_words * 32U;
324 
325  uint32_t num_bytes = (remaining_bits - 1U) / 8U + 1U;
326  for (uint32_t i = 0U; i < num_bytes; i++)
327  {
328  uint32_t value = static_cast<uint32_t>(bytes[num_words * 4U + i]);
329  BS_ASSERT(serialize_bits(value, (std::min)(remaining_bits - i * 8U, 8U)));
330  }
331 
332  return true;
333  }
334 
340  [[nodiscard]] bool serialize_into(bit_writer& writer) const noexcept
341  {
342  uint8_t* buffer = reinterpret_cast<uint8_t*>(m_Policy.get_buffer());
343  uint32_t num_bits = get_num_bits_serialized();
344  uint32_t remainder_bits = num_bits % 8U;
345 
346  BS_ASSERT(writer.serialize_bytes(buffer, num_bits - remainder_bits));
347 
348  if (remainder_bits > 0U)
349  {
350  uint32_t byte_value = buffer[num_bits / 8U] >> (8U - remainder_bits);
351  BS_ASSERT(writer.serialize_bits(byte_value, remainder_bits));
352  }
353 
354  return true;
355  }
356 
365  template<typename Trait, typename... Args, typename = utility::has_serialize_t<Trait, bit_writer, Args...>>
366  [[nodiscard]] bool serialize(Args&&... args) noexcept(utility::is_serialize_noexcept_v<Trait, bit_writer, Args...>)
367  {
368  return serialize_traits<Trait>::serialize(*this, std::forward<Args>(args)...);
369  }
370 
381  template<typename... Args, typename Trait, typename = utility::has_deduce_serialize_t<Trait, bit_writer, Args...>>
382  [[nodiscard]] bool serialize(Trait&& arg, Args&&... args) noexcept(utility::is_deduce_serialize_noexcept_v<Trait, bit_writer, Args...>)
383  {
384  return serialize_traits<utility::deduce_trait_t<Trait, bit_writer, Args...>>::serialize(*this, std::forward<Trait>(arg), std::forward<Args>(args)...);
385  }
386 
387  private:
388  Policy m_Policy;
389 
390  uint64_t m_Scratch;
391  uint32_t m_ScratchBits;
392  uint32_t m_WordIndex;
393  };
394 
396 
397  template<typename T>
399 }
#define BS_ASSERT(x)
Definition: assert.h:15
A stream for writing objects tightly into a buffer.
Definition: bit_writer.h:24
uint8_t * get_buffer() const noexcept
Returns the buffer that this writer is currently serializing into.
Definition: bit_writer.h:75
bool serialize(Trait &&arg, Args &&... args) noexcept(utility::is_deduce_serialize_noexcept_v< Trait, bit_writer, Args... >)
Writes to the buffer, by trying to deduce the trait.
Definition: bit_writer.h:382
bit_writer & operator=(bit_writer &&rhs) noexcept
Definition: bit_writer.h:57
bool serialize_bits(uint32_t value, uint32_t num_bits) noexcept
Writes the first num_bits bits of value into the buffer.
Definition: bit_writer.h:245
uint32_t get_total_bits() const noexcept
Returns the size of the buffer, in bits.
Definition: bit_writer.h:107
uint32_t get_num_bytes_serialized() const noexcept
Returns the number of bytes which have been written to the buffer.
Definition: bit_writer.h:87
bool prepend_checksum() noexcept
Instructs the writer that you intend to use serialize_checksum() later on, and to reserve the first 3...
Definition: bit_writer.h:133
bit_writer & operator=(const bit_writer &)=delete
uint32_t get_remaining_bits() const noexcept
Returns the number of bits which have not been written yet.
Definition: bit_writer.h:101
uint32_t flush() noexcept
Flushes any remaining bits into the buffer. Use this when you no longer intend to write anything to t...
Definition: bit_writer.h:113
bool serialize_bytes(const uint8_t *bytes, uint32_t num_bits) noexcept
Writes the first num_bits bits of the given byte array, 32 bits at a time.
Definition: bit_writer.h:288
bit_writer(bit_writer &&other) noexcept
Definition: bit_writer.h:44
bool align() noexcept
Pads the buffer with up to 8 zeros, so that the next write is byte-aligned.
Definition: bit_writer.h:226
uint32_t serialize_checksum(uint32_t protocol_version) noexcept
Writes a checksum of the protocol_version and the rest of the buffer as the first 32 bits.
Definition: bit_writer.h:150
bool serialize(Args &&... args) noexcept(utility::is_serialize_noexcept_v< Trait, bit_writer, Args... >)
Writes to the buffer, using the given Trait.
Definition: bit_writer.h:366
static constexpr bool reading
Definition: bit_writer.h:27
bool serialize_into(bit_writer &writer) const noexcept
Writes the contents of the buffer into the given writer. Essentially copies the entire buffer without...
Definition: bit_writer.h:340
bool pad_to_size(uint32_t num_bytes) noexcept
Pads the buffer up to the given number of bytes with zeros.
Definition: bit_writer.h:174
static constexpr bool writing
Definition: bit_writer.h:26
bit_writer(const bit_writer &)=delete
bit_writer(Ts &&... args) noexcept(std::is_nothrow_constructible_v< Policy, Ts... >)
Construct a writer with the parameters passed to the underlying policy.
Definition: bit_writer.h:35
bool pad(uint32_t num_bytes) noexcept
Pads the buffer up with the given number of bytes.
Definition: bit_writer.h:217
bool can_serialize_bits(uint32_t num_bits) const noexcept
Returns whether the num_bits can fit in the buffer.
Definition: bit_writer.h:94
uint32_t get_num_bits_serialized() const noexcept
Returns the number of bits which have been written to the buffer.
Definition: bit_writer.h:81
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