Skip to content

Commit a9a0c08

Browse files
committed
Bring SHA1 lib's C++ requirement back down to C++14.
Going lower means removing constexpr. We'll see. Also reduced required headers for operation, and optimized update(), giving a decent speed boost. Measured time previously for a ~1GB file was ~2.8s, now down to ~2.3s. Going lower would require SHA intrinsics. Will explore that eventually for funzies.
1 parent 47a2e6e commit a9a0c08

2 files changed

Lines changed: 79 additions & 48 deletions

File tree

‎include/utilities/SHA1.cpp‎

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,26 @@
88
*/
99

1010
#include "SHA1.hpp"
11+
#include <algorithm>
12+
13+
/*==================================================================*/
1114

15+
#if __has_include(<bit>)
1216
#include <bit>
13-
#include <algorithm>
17+
#endif
18+
19+
static constexpr std::uint32_t rotl(std::uint32_t value, std::uint32_t rotation) noexcept {
20+
#if __has_include(<bit>)
21+
return std::rotl(value, rotation);
22+
#else
23+
return (value << rotation) | (value >> (32 - rotation));
24+
#endif
25+
}
1426

1527
/*==================================================================*/
1628

1729
static constexpr std::uint32_t block_mix(std::uint32_t* block, std::size_t i) noexcept {
18-
return std::rotl(
30+
return rotl(
1931
block[(i + 0xD) & 0xF] ^
2032
block[(i + 0x8) & 0xF] ^
2133
block[(i + 0x2) & 0xF] ^
@@ -31,8 +43,8 @@ static constexpr void R0(
3143
std::uint32_t v, std::uint32_t& w, std::uint32_t x,
3244
std::uint32_t y, std::uint32_t& z, std::size_t i
3345
) noexcept {
34-
z += ((w & (x ^ y)) ^ y) + block[i] + 0x5A827999 + std::rotl(v, 5);
35-
w = std::rotl(w, 30);
46+
z += ((w & (x ^ y)) ^ y) + block[i] + 0x5A827999 + rotl(v, 5);
47+
w = rotl(w, 30);
3648
}
3749

3850
static constexpr void R1(
@@ -41,8 +53,8 @@ static constexpr void R1(
4153
std::uint32_t y, std::uint32_t& z, std::size_t i
4254
) noexcept {
4355
block[i] = block_mix(block, i);
44-
z += ((w & (x ^ y)) ^ y) + block[i] + 0x5A827999 + std::rotl(v, 5);
45-
w = std::rotl(w, 30);
56+
z += ((w & (x ^ y)) ^ y) + block[i] + 0x5A827999 + rotl(v, 5);
57+
w = rotl(w, 30);
4658
}
4759

4860
static constexpr void R2(
@@ -51,8 +63,8 @@ static constexpr void R2(
5163
std::uint32_t y, std::uint32_t& z, std::size_t i
5264
) noexcept {
5365
block[i] = block_mix(block, i);
54-
z += (w ^ x ^ y) + block[i] + 0x6ED9EBA1 + std::rotl(v, 5);
55-
w = std::rotl(w, 30);
66+
z += (w ^ x ^ y) + block[i] + 0x6ED9EBA1 + rotl(v, 5);
67+
w = rotl(w, 30);
5668
}
5769

5870
static constexpr void R3(
@@ -61,8 +73,8 @@ static constexpr void R3(
6173
std::uint32_t y, std::uint32_t& z, std::size_t i
6274
) noexcept {
6375
block[i] = block_mix(block, i);
64-
z += (((w | x) & y) | (w & x)) + block[i] + 0x8F1BBCDC + std::rotl(v, 5);
65-
w = std::rotl(w, 30);
76+
z += (((w | x) & y) | (w & x)) + block[i] + 0x8F1BBCDC + rotl(v, 5);
77+
w = rotl(w, 30);
6678
}
6779

6880
static constexpr void R4(
@@ -71,8 +83,8 @@ static constexpr void R4(
7183
std::uint32_t y, std::uint32_t& z, std::size_t i
7284
) noexcept {
7385
block[i] = block_mix(block, i);
74-
z += (w ^ x ^ y) + block[i] + 0xCA62C1D6 + std::rotl(v, 5);
75-
w = std::rotl(w, 30);
86+
z += (w ^ x ^ y) + block[i] + 0xCA62C1D6 + rotl(v, 5);
87+
w = rotl(w, 30);
7688
}
7789

7890
/*==================================================================*/
@@ -118,15 +130,13 @@ constexpr void SHA1::transform(std::uint32_t* block) noexcept {
118130
++m_transforms;
119131
}
120132

121-
// Hash a single 512-bit block
122-
constexpr void SHA1::buffer_to_block(std::uint32_t* block) noexcept {
123-
124-
// convert the string (byte buffer) to a u32 array (MSB)
133+
constexpr void SHA1::bytes_to_block(std::uint32_t* block, const char* src) noexcept {
134+
// read bytes as byteswapped ints
125135
for (auto i = 0u; i < c_block_size; ++i) {
126-
block[i] = (m_buffer[4 * i + 3] & 0xFF)
127-
| (m_buffer[4 * i + 2] & 0xFF) << 8
128-
| (m_buffer[4 * i + 1] & 0xFF) << 16
129-
| (m_buffer[4 * i + 0] & 0xFF) << 24;
136+
block[i] = (src[4 * i + 3] & 0xFF)
137+
| (src[4 * i + 2] & 0xFF) << 8
138+
| (src[4 * i + 1] & 0xFF) << 16
139+
| (src[4 * i + 0] & 0xFF) << 24;
130140
}
131141
}
132142

@@ -137,49 +147,62 @@ void SHA1::reset() noexcept {
137147
m_digest[3] = 0x10325476u;
138148
m_digest[4] = 0xC3D2E1F0u;
139149

140-
m_buffer.fill('\0');
141-
m_transforms = m_buf_offset = 0;
150+
std::fill_n(m_buffer, c_buffer_size, '\x00');
151+
m_transforms = m_tail_size = 0;
142152
}
143153

144154
void SHA1::update(const char* data_pointer, std::size_t byte_count) noexcept {
145-
while (byte_count > 0) {
155+
// if the buffer is partially filled, top it up first
156+
if (m_tail_size > 0) {
146157
const auto chunk_size = std::uint32_t(std::min(
147-
c_block_bytes - m_buf_offset, byte_count));
158+
c_block_bytes - m_tail_size, byte_count));
148159

149160
std::copy(data_pointer, data_pointer + chunk_size,
150-
m_buffer.data() + m_buf_offset);
161+
m_buffer + m_tail_size);
151162

152-
m_buf_offset += chunk_size;
163+
m_tail_size += chunk_size;
153164
data_pointer += chunk_size;
154165
byte_count -= chunk_size;
155166

156-
if (m_buf_offset == c_block_bytes) {
157-
std::uint32_t block_array[c_block_size]{};
158-
buffer_to_block(block_array);
167+
if (m_tail_size == c_block_bytes) {
168+
std::uint32_t block_array[c_block_size];
169+
bytes_to_block(block_array, m_buffer);
159170
transform(block_array);
160-
m_buf_offset = 0;
171+
m_tail_size = 0;
161172
}
162173
}
174+
175+
// process full blocks directly from the input — no copy
176+
while (byte_count >= c_block_bytes) {
177+
std::uint32_t block_array[c_block_size];
178+
bytes_to_block(block_array, data_pointer);
179+
transform(block_array);
180+
data_pointer += c_block_bytes;
181+
byte_count -= c_block_bytes;
182+
}
183+
184+
// stash any remaining partial block in the buffer
185+
if (byte_count > 0) {
186+
std::copy(data_pointer, data_pointer + byte_count, m_buffer);
187+
m_tail_size = std::uint32_t(byte_count);
188+
}
163189
}
164190

165191
std::string SHA1::final() noexcept {
166192
const auto total_hashed_bits =
167-
8 * (m_transforms * c_block_bytes + m_buf_offset);
193+
8 * (m_transforms * c_block_bytes + m_tail_size);
168194

169-
// insert message end bit, then pad with
170-
// zeroes until the end of the block
171-
m_buffer[m_buf_offset++] = '\x80';
172-
const auto buffer_size = m_buf_offset;
195+
// insert message end bit, then pad to end of block with zeroes
196+
m_buffer[m_tail_size++] = '\x80';
173197

174-
while (m_buf_offset < c_block_bytes) {
175-
m_buffer[m_buf_offset++] = '\x00';
176-
}
198+
std::fill(m_buffer + m_tail_size,
199+
m_buffer + c_buffer_size, '\x00');
177200

178201
// process the final block
179-
std::uint32_t block_array[c_block_size]{};
180-
buffer_to_block(block_array);
202+
std::uint32_t block_array[c_block_size];
203+
bytes_to_block(block_array, m_buffer);
181204

182-
if (buffer_size > c_block_bytes - 8) {
205+
if (m_tail_size > c_block_bytes - 8) {
183206
transform(block_array);
184207
for (auto i = 0u; i < c_block_size - 2; ++i) {
185208
block_array[i] = 0;
@@ -192,9 +215,9 @@ std::string SHA1::final() noexcept {
192215
transform(block_array);
193216

194217
// convert digest[] to a string
195-
std::string result(40, '\0');
218+
std::string result(40, '\x00');
196219
constexpr auto c_hex = "0123456789abcdef";
197-
for (auto i = 0u; i < std::size(m_digest); ++i) {
220+
for (auto i = 0u; i < c_digest_size; ++i) {
198221
for (auto j = 0u; j < 8; ++j) {
199222
result[i * 8 + j] = c_hex[(m_digest[i] >> (28 - j * 4)) & 0xF];
200223
}

‎include/utilities/SHA1.hpp‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,25 @@
1111

1212
#include <cstdint>
1313
#include <string>
14-
#include <array>
14+
15+
#if __has_include(<span>)
1516
#include <span>
17+
#endif
1618

1719
/*==================================================================*/
1820

1921
class SHA1 {
20-
std::array<char, 64> m_buffer{};
21-
std::uint32_t m_digest[5]{};
22-
std::uint32_t m_buf_offset = 0;
22+
static constexpr auto c_buffer_size = 64u;
23+
static constexpr auto c_digest_size = 5u;
24+
25+
char m_buffer[c_buffer_size]{};
26+
std::uint32_t m_digest[c_digest_size]{};
27+
28+
std::uint32_t m_tail_size = 0;
2329
std::uint64_t m_transforms = 0;
2430

2531
constexpr void transform(std::uint32_t* block) noexcept;
26-
constexpr void buffer_to_block(std::uint32_t* block) noexcept;
32+
constexpr void bytes_to_block(std::uint32_t* block, const char* src) noexcept;
2733

2834
public:
2935
static constexpr auto c_block_size = 16u; // number of 32-bit integers per SHA1 block
@@ -44,7 +50,9 @@ class SHA1 {
4450
return checksum.final();
4551
}
4652

53+
#if __has_include(<span>)
4754
static std::string from(std::span<const char> file_span) noexcept {
4855
return from(file_span.data(), file_span.size());
4956
}
57+
#endif
5058
};

0 commit comments

Comments
 (0)