Skip to content

Commit 1edbb7a

Browse files
authored
Merge pull request #18 from KredeGC/checksum
Move checksum logic into trait
2 parents 86ddd63 + d6bb706 commit 1edbb7a

File tree

12 files changed

+211
-119
lines changed

12 files changed

+211
-119
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Based on [Glenn Fiedler's articles](https://gafferongames.com/post/reading_and_w
3535
* [Half-precision float - half_precision](#half-precision-float---half_precision)
3636
* [Bounded float - bounded_range](#bounded-float---bounded_range)
3737
* [Quaternion - smallest_three\<Q, BitsPerElement\>](#quaternion---smallest_threeq-bitsperelement)
38+
* [Checksum\<V\>](#checksumversion)
3839
* [Extensibility](#extensibility)
3940
* [Adding new serializables types](#adding-new-serializables-types)
4041
* [Unified serialization](#unified-serialization)
@@ -443,6 +444,27 @@ bool status_write = writer.serialize<smallest_three<quaternion, 12>>(in_value);
443444
bool status_read = reader.serialize<smallest_three<quaternion, 12>>(out_value);
444445
```
445446
447+
## Checksum\<Version\>
448+
A trait that creates a checksum based on the 32-bit number given.<br/>
449+
If the checksum that was written does not match when reading, it returns false.
450+
Must be called before anything else is serizalized, and again once everything is fully serialized.
451+
When reading you can omit the last serialize, as it is a noop.
452+
453+
The call signature can be seen below:
454+
```cpp
455+
bool serialize<checksum<Version>>();
456+
```
457+
As well as a short example of its usage:
458+
```cpp
459+
bool status_write = writer.serialize<checksum<0x12345678>>();
460+
// Serialize some stuff...
461+
status_write = writer.serialize<checksum<0x12345678>>();
462+
463+
bool status_read = reader.serialize<checksum<0x12345678>>();
464+
// Deserialize some stuff...
465+
status_read = reader.serialize<checksum<0x12345678>>(); // Last deserialize on read is optional (a noop)
466+
```
467+
446468
# Extensibility
447469
The library is made with extensibility in mind.
448470
The `bit_writer<T>` and `bit_reader<T>` use a template trait specialization of the given type to deduce how to serialize and deserialize the object.

generate.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
call vendor\bin\premake\premake5.exe vs2019
1+
call vendor\bin\premake\premake5.exe vs2022
22
pause

include/bitstream/bitstream.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// Traits
1616
#include "traits/array_traits.h"
1717
#include "traits/bool_trait.h"
18+
#include "traits/checksum_trait.h"
1819
#include "traits/enum_trait.h"
1920
#include "traits/float_trait.h"
2021
#include "traits/integral_traits.h"

include/bitstream/stream/bit_reader.h

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -106,35 +106,6 @@ namespace bitstream
106106
*/
107107
[[nodiscard]] uint32_t get_total_bits() const noexcept { return m_Policy.get_total_bits(); }
108108

109-
/**
110-
* @brief Reads the first 32 bits of the buffer and compares it to a checksum of the @p protocol_version and the rest of the buffer
111-
* @param protocol_version A unique version number
112-
* @return Whether the checksum matches what was written
113-
*/
114-
[[nodiscard]] bool serialize_checksum(uint32_t protocol_version) noexcept
115-
{
116-
BS_ASSERT(get_num_bits_serialized() == 0);
117-
118-
BS_ASSERT(can_serialize_bits(32U));
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-
138109
/**
139110
* @brief Pads the buffer up to the given number of bytes
140111
* @param num_bytes The byte number to pad to

include/bitstream/stream/bit_writer.h

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -126,46 +126,6 @@ namespace bitstream
126126
return get_num_bits_serialized();
127127
}
128128

129-
/**
130-
* @brief Instructs the writer that you intend to use `serialize_checksum()` later on, and to reserve the first 32 bits.
131-
* @return Returns false if anything has already been written to the buffer or if there's no space to write the checksum
132-
*/
133-
[[nodiscard]] bool prepend_checksum() noexcept
134-
{
135-
BS_ASSERT(get_num_bits_serialized() == 0);
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-
145-
/**
146-
* @brief Writes a checksum of the @p protocol_version and the rest of the buffer as the first 32 bits
147-
* @param protocol_version A unique version number
148-
* @return The number of bytes written to the buffer
149-
*/
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-
169129
/**
170130
* @brief Pads the buffer up to the given number of bytes with zeros
171131
* @param num_bytes The byte number to pad to
@@ -338,7 +298,8 @@ namespace bitstream
338298
* @param writer The writer to copy into
339299
* @return Returns false if writing would overflow the buffer
340300
*/
341-
[[nodiscard]] bool serialize_into(bit_writer& writer) const noexcept
301+
template<typename T>
302+
[[nodiscard]] bool serialize_into(bit_writer<T>& writer) const noexcept
342303
{
343304
uint8_t* buffer = reinterpret_cast<uint8_t*>(m_Policy.get_buffer());
344305
uint32_t num_bits = get_num_bits_serialized();
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#pragma once
2+
#include "../utility/assert.h"
3+
#include "../utility/crc.h"
4+
#include "../utility/meta.h"
5+
#include "../utility/parameter.h"
6+
7+
#include "../stream/serialize_traits.h"
8+
9+
namespace bitstream
10+
{
11+
/**
12+
* @brief Type for checksums
13+
* @tparam Version A unique version number
14+
*/
15+
template<uint32_t Version>
16+
struct checksum;
17+
18+
/**
19+
* @brief A trait used to serialize a checksum of the @p Version and the rest of the buffer as the first 32 bits.
20+
* This should be called both first and last when reading and writing to a buffer.
21+
* @tparam Version A unique version number
22+
*/
23+
template<uint32_t Version>
24+
struct serialize_traits<checksum<Version>>
25+
{
26+
constexpr static uint32_t protocol_version = utility::to_big_endian32_const(Version);
27+
constexpr static uint32_t protocol_size = sizeof(uint32_t);
28+
29+
template<typename Stream>
30+
typename utility::is_writing_t<Stream>
31+
static serialize(Stream& writer) noexcept
32+
{
33+
if (writer.get_num_bits_serialized() == 0)
34+
return writer.pad_to_size(4);
35+
36+
uint32_t num_bits = writer.flush();
37+
38+
BS_ASSERT(num_bits >= 32U);
39+
40+
// Get buffer info
41+
uint8_t* byte_buffer = writer.get_buffer();
42+
uint32_t num_bytes = writer.get_num_bytes_serialized();
43+
44+
// Generate checksum of version + data
45+
uint32_t generated_checksum = utility::crc_uint32(protocol_version, byte_buffer + protocol_size, writer.get_num_bytes_serialized() - protocol_size);
46+
47+
// Put checksum at beginning
48+
uint32_t* buffer = reinterpret_cast<uint32_t*>(byte_buffer);
49+
*buffer = utility::to_big_endian32(generated_checksum);
50+
51+
return true;
52+
}
53+
54+
template<typename Stream>
55+
typename utility::is_reading_t<Stream>
56+
static serialize(Stream& reader) noexcept
57+
{
58+
if (reader.get_num_bits_serialized() > 0)
59+
return true;
60+
61+
BS_ASSERT(reader.can_serialize_bits(32U));
62+
63+
// Get buffer info
64+
const uint8_t* byte_buffer = reader.get_buffer();
65+
uint32_t num_bytes = (reader.get_total_bits() - 1U) / 8U + 1U;
66+
67+
// Generate checksum to compare against
68+
uint32_t generated_checksum = utility::crc_uint32(protocol_version, byte_buffer + protocol_size, num_bytes - protocol_size);
69+
70+
// Read the checksum
71+
uint32_t given_checksum;
72+
BS_ASSERT(reader.serialize_bits(given_checksum, 32U));
73+
74+
// Compare the checksum
75+
return generated_checksum == given_checksum;
76+
}
77+
};
78+
}

include/bitstream/utility/crc.h

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <array>
44
#include <cstdint>
5+
#include <cstring>
56

67
namespace bitstream::utility
78
{
@@ -22,22 +23,15 @@ namespace bitstream::utility
2223
return table;
2324
}();
2425

25-
inline constexpr uint32_t crc_uint32(const uint8_t* bytes, uint32_t size)
26+
inline uint32_t crc_uint32(uint32_t checksum, const uint8_t* bytes, uint32_t size)
2627
{
2728
uint32_t result = 0xFFFFFFFF;
2829

29-
for (uint32_t i = 0; i < size; i++)
30-
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(bytes + i)] ^ (result >> 8);
31-
32-
return ~result;
33-
}
34-
35-
inline constexpr uint32_t crc_uint32(const uint8_t* checksum, const uint8_t* bytes, uint32_t size)
36-
{
37-
uint32_t result = 0xFFFFFFFF;
30+
uint8_t checksum_table[4]{};
31+
std::memcpy(&checksum_table, &checksum, sizeof(uint32_t));
3832

3933
for (uint32_t i = 0; i < 4; i++)
40-
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(checksum + i)] ^ (result >> 8);
34+
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(checksum_table + i)] ^ (result >> 8);
4135

4236
for (uint32_t i = 0; i < size; i++)
4337
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(bytes + i)] ^ (result >> 8);

include/bitstream/utility/endian.h

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include "platform.h"
4+
35
#include <cstdint>
46

57
#if defined(__cpp_lib_endian) && __cpp_lib_endian >= 201907L
@@ -63,23 +65,43 @@ namespace bitstream::utility
6365
#endif // defined(BS_LITTLE_ENDIAN)
6466
}
6567

66-
inline uint32_t endian_swap32(uint32_t value)
68+
constexpr inline uint32_t endian_swap32_const(uint32_t value)
6769
{
68-
#if defined(_WIN32)
69-
return _byteswap_ulong(value);
70-
#elif defined(__linux__)
71-
return __builtin_bswap32(value);
72-
#else
7370
const uint32_t first = (value << 24) & 0xFF000000;
7471
const uint32_t second = (value << 8) & 0x00FF0000;
7572
const uint32_t third = (value >> 8) & 0x0000FF00;
7673
const uint32_t fourth = (value >> 24) & 0x000000FF;
7774

7875
return first | second | third | fourth;
76+
}
77+
78+
BS_CONSTEXPR inline uint32_t endian_swap32(uint32_t value)
79+
{
80+
if BS_CONST_EVALUATED()
81+
{
82+
return endian_swap32_const(value);
83+
}
84+
else
85+
{
86+
#if defined(_WIN32)
87+
return _byteswap_ulong(value);
88+
#elif defined(__linux__)
89+
return __builtin_bswap32(value);
90+
#else
91+
return endian_swap32_const(value);
7992
#endif // _WIN32 || __linux__
93+
}
94+
}
95+
96+
constexpr inline uint32_t to_big_endian32_const(uint32_t value)
97+
{
98+
if constexpr (little_endian())
99+
return endian_swap32_const(value);
100+
else
101+
return value;
80102
}
81103

82-
inline uint32_t to_big_endian32(uint32_t value)
104+
BS_CONSTEXPR inline uint32_t to_big_endian32(uint32_t value)
83105
{
84106
if constexpr (little_endian())
85107
return endian_swap32(value);

include/bitstream/utility/parameter.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
#pragma once
22

33
#include "assert.h"
4+
#include "platform.h"
45

56
#include <utility>
67
#include <type_traits>
78

8-
#ifdef __cpp_constexpr_dynamic_alloc
9-
#define BS_CONSTEXPR constexpr
10-
#else // __cpp_constexpr_dynamic_alloc
11-
#define BS_CONSTEXPR
12-
#endif // __cpp_constexpr_dynamic_alloc
13-
149
namespace bitstream
1510
{
1611
#ifdef BS_DEBUG_BREAK
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include <type_traits>
4+
5+
#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L
6+
# define BS_CONST_EVALUATED() (std::is_constant_evaluated())
7+
# define BS_CONSTEXPR constexpr
8+
#else // __cpp_lib_is_constant_evaluated
9+
# define BS_CONST_EVALUATED() constexpr (false)
10+
# define BS_CONSTEXPR
11+
#endif // __cpp_lib_is_constant_evaluated

0 commit comments

Comments
 (0)