Skip to content

Commit ef11575

Browse files
authored
hash.crc32: add crc32c, crc32k, and crc32q variants (#27149)
1 parent 6cafb40 commit ef11575

2 files changed

Lines changed: 240 additions & 16 deletions

File tree

vlib/hash/crc32/crc32.v

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
// Use of this source code is governed by an MIT license
33
// that can be found in the LICENSE file.
44

5-
// This is a very basic crc32 implementation
6-
// at the moment with no architecture optimizations
5+
// This is a fairly basic crc32 implementation, with 4 variants of the crc32 algorithm, and a way
6+
// to create custom crc32 tables from user-provided polynomials.
77
module crc32
88

99
// polynomials
1010
pub const ieee = u32(0xedb88320)
1111
pub const castagnoli = u32(0x82f63b78)
1212
pub const koopman = u32(0xeb31d82e)
13+
// q is the standard CRC-32Q polynomial (MSB-first).
14+
pub const q = u32(0x814141ab)
15+
// q_reflected is the reflected (LSB-first) form of CRC-32Q polynomial.
16+
pub const q_reflected = u32(0xd5828281)
1317

14-
// The size of a CRC-32 checksum in bytes.
15-
const size = 4
18+
// Named aliases for common CRC-32 variants.
19+
pub const crc32c = castagnoli
20+
pub const crc32k = koopman
21+
pub const crc32q = q
22+
pub const crc32q_reflected = q_reflected
1623

1724
struct Crc32 {
1825
mut:
@@ -21,44 +28,118 @@ mut:
2128

2229
// generate_table populates a 256-word table from the specified polynomial `poly`
2330
// to represent the polynomial for efficient processing.
24-
fn (mut c Crc32) generate_table(poly int) {
31+
@[direct_array_access]
32+
fn (mut c Crc32) generate_table(poly u32) {
33+
c.table = []u32{len: 256}
2534
for i in 0 .. 256 {
2635
mut crc := u32(i)
2736
for _ in 0 .. 8 {
2837
if crc & u32(1) == u32(1) {
29-
crc = (crc >> 1) ^ u32(poly)
38+
crc = (crc >> 1) ^ poly
3039
} else {
3140
crc >>= u32(1)
3241
}
3342
}
34-
c.table << crc
43+
c.table[i] = crc
3544
}
3645
}
3746

3847
@[direct_array_access]
39-
fn (c &Crc32) sum32(b []u8) u32 {
40-
mut crc := ~u32(0)
48+
fn (c &Crc32) update32(crc u32, b []u8) u32 {
49+
mut next := crc
4150
for i in 0 .. b.len {
42-
crc = c.table[u8(crc) ^ b[i]] ^ (crc >> 8)
51+
next = c.table[u8(next) ^ b[i]] ^ (next >> 8)
4352
}
44-
return ~crc
53+
return next
54+
}
55+
56+
// update_state updates an internal CRC state with the bytes in `b`.
57+
// Start from `~u32(0)` and finalize with `~state`.
58+
pub fn (c &Crc32) update_state(state u32, b []u8) u32 {
59+
return c.update32(state, b)
4560
}
4661

4762
// checksum returns the CRC-32 checksum of data `b` by using the polynomial represented by `c`'s table.
4863
pub fn (c &Crc32) checksum(b []u8) u32 {
49-
return c.sum32(b)
64+
return ~c.update_state(~u32(0), b)
65+
}
66+
67+
// update returns the updated CRC-32 checksum for `b`, starting from `crc`.
68+
// Use `crc = 0` for a fresh checksum, or pass a previous result to continue streaming.
69+
pub fn (c &Crc32) update(crc u32, b []u8) u32 {
70+
state := c.update_state(~crc, b)
71+
return ~state
5072
}
5173

5274
// new creates a `Crc32` polynomial.
53-
pub fn new(poly int) &Crc32 {
75+
pub fn new(poly u32) &Crc32 {
5476
mut c := &Crc32{}
5577
c.generate_table(poly)
5678
return c
5779
}
5880

59-
const ieee_poly = new(int(ieee))
81+
// sum_with_poly calculates the CRC-32 checksum of `b` for the provided polynomial.
82+
// Built-in constants use their canonical parameter sets.
83+
pub fn sum_with_poly(poly u32, b []u8) u32 {
84+
return match poly {
85+
ieee { ieee_poly.checksum(b) }
86+
crc32c { crc32c_poly.checksum(b) }
87+
crc32k { crc32k_poly.checksum(b) }
88+
crc32q { crc32q_sum_internal(b) }
89+
crc32q_reflected { crc32q_reflected_poly.checksum(b) }
90+
else { new(poly).checksum(b) }
91+
}
92+
}
93+
94+
const ieee_poly = new(ieee)
95+
const crc32c_poly = new(crc32c)
96+
const crc32k_poly = new(crc32k)
97+
const crc32q_reflected_poly = new(crc32q_reflected)
98+
const crc32q_table = crc32q_generate_table(q)
99+
100+
@[direct_array_access]
101+
fn crc32q_generate_table(poly u32) []u32 {
102+
mut table := []u32{len: 256}
103+
for i in 0 .. 256 {
104+
mut crc := u32(i) << 24
105+
for _ in 0 .. 8 {
106+
if crc & u32(0x80000000) != 0 {
107+
crc = (crc << 1) ^ poly
108+
} else {
109+
crc <<= 1
110+
}
111+
}
112+
table[i] = crc
113+
}
114+
return table
115+
}
116+
117+
@[direct_array_access]
118+
fn crc32q_sum_internal(b []u8) u32 {
119+
mut crc := u32(0)
120+
for byte in b {
121+
idx := u8((crc >> 24) ^ byte)
122+
crc = crc32q_table[idx] ^ (crc << 8)
123+
}
124+
return crc
125+
}
60126

61127
// sum calculates the CRC-32 checksum of `b` by using the IEEE polynomial.
62128
pub fn sum(b []u8) u32 {
63-
return ieee_poly.sum32(b)
129+
return ieee_poly.checksum(b)
130+
}
131+
132+
// sum_crc32c calculates the CRC-32C checksum of `b`.
133+
pub fn sum_crc32c(b []u8) u32 {
134+
return crc32c_poly.checksum(b)
135+
}
136+
137+
// sum_crc32k calculates the CRC-32K checksum of `b`.
138+
pub fn sum_crc32k(b []u8) u32 {
139+
return crc32k_poly.checksum(b)
140+
}
141+
142+
// sum_crc32q calculates the CRC-32Q checksum of `b`.
143+
pub fn sum_crc32q(b []u8) u32 {
144+
return crc32q_sum_internal(b)
64145
}

vlib/hash/crc32/crc32_test.v

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,157 @@
11
import hash.crc32
22

3+
const reflected_test_polys = [crc32.ieee, crc32.crc32c, crc32.crc32k, crc32.crc32q_reflected]
4+
5+
fn sum_for_reflected_poly(poly u32, data []u8) u32 {
6+
return match poly {
7+
crc32.ieee { crc32.sum(data) }
8+
crc32.crc32c { crc32.sum_crc32c(data) }
9+
crc32.crc32k { crc32.sum_crc32k(data) }
10+
crc32.crc32q_reflected { crc32.sum_with_poly(crc32.crc32q_reflected, data) }
11+
else { panic('unexpected polynomial in test') }
12+
}
13+
}
14+
15+
fn expected_reflected_crc_123456789(poly u32) u32 {
16+
return match poly {
17+
crc32.ieee { u32(0xcbf43926) }
18+
crc32.crc32c { u32(0xe3069283) }
19+
crc32.crc32k { u32(0x2d3dd0ae) }
20+
crc32.crc32q_reflected { u32(0xa9cc8179) }
21+
else { panic('unexpected polynomial in test') }
22+
}
23+
}
24+
25+
fn expected_reflected_crc_a(poly u32) u32 {
26+
return match poly {
27+
crc32.ieee { u32(0xe8b7be43) }
28+
crc32.crc32c { u32(0xc1d04330) }
29+
crc32.crc32k { u32(0x0da2aa8a) }
30+
crc32.crc32q_reflected { u32(0x248ca0a3) }
31+
else { panic('unexpected polynomial in test') }
32+
}
33+
}
34+
35+
fn assert_reflected_poly_paths_match(poly u32, data []u8) {
36+
c := crc32.new(poly)
37+
by_new := c.checksum(data)
38+
assert by_new == crc32.sum_with_poly(poly, data)
39+
assert by_new == sum_for_reflected_poly(poly, data)
40+
}
41+
342
fn test_hash_crc32() {
443
b1 := 'testing crc32'.bytes()
544
sum1 := crc32.sum(b1)
645
assert sum1 == u32(1212124400)
746
assert sum1.hex() == '483f8cf0'
847

9-
c := crc32.new(int(crc32.ieee))
48+
c := crc32.new(crc32.ieee)
1049
b2 := 'testing crc32 again'.bytes()
1150
sum2 := c.checksum(b2)
1251
assert sum2 == u32(1420327025)
1352
assert sum2.hex() == '54a87871'
1453
}
54+
55+
fn test_hash_crc32_variants() {
56+
data := '123456789'.bytes()
57+
for poly in reflected_test_polys {
58+
expected := expected_reflected_crc_123456789(poly)
59+
assert sum_for_reflected_poly(poly, data) == expected
60+
assert_reflected_poly_paths_match(poly, data)
61+
}
62+
}
63+
64+
fn test_hash_crc32q_standard() {
65+
data := '123456789'.bytes()
66+
assert crc32.sum_crc32q(data) == u32(0x3010bf7f)
67+
assert crc32.sum_with_poly(crc32.crc32q, data) == u32(0x3010bf7f)
68+
assert crc32.sum_crc32q('a'.bytes()) == u32(0xd1112b6b)
69+
}
70+
71+
fn test_hash_crc32_update() {
72+
data := '123456789'.bytes()
73+
part1 := data[..4]
74+
part2 := data[4..]
75+
76+
c := crc32.new(crc32.ieee)
77+
mut acc := u32(0)
78+
acc = c.update(acc, part1)
79+
acc = c.update(acc, part2)
80+
81+
assert acc == c.checksum(data)
82+
assert acc.hex() == 'cbf43926'
83+
}
84+
85+
fn test_hash_crc32_edge_cases() {
86+
empty := ''.bytes()
87+
one := 'a'.bytes()
88+
for poly in reflected_test_polys {
89+
assert sum_for_reflected_poly(poly, empty) == u32(0)
90+
assert sum_for_reflected_poly(poly, one) == expected_reflected_crc_a(poly)
91+
}
92+
assert crc32.sum_crc32q(empty) == u32(0)
93+
}
94+
95+
fn test_hash_crc32_sum_with_poly() {
96+
data := 'variant helper'.bytes()
97+
for poly in reflected_test_polys {
98+
assert_reflected_poly_paths_match(poly, data)
99+
}
100+
assert crc32.sum_with_poly(crc32.crc32q, data) == crc32.sum_crc32q(data)
101+
}
102+
103+
fn test_hash_crc32_sum_with_poly_custom() {
104+
data := 'custom poly checksum'.bytes()
105+
poly := u32(0xa833982b)
106+
107+
assert crc32.sum_with_poly(poly, data) == crc32.new(poly).checksum(data)
108+
}
109+
110+
fn test_hash_crc32_all_polys_consistent() {
111+
data := 'all polys consistent'.bytes()
112+
part1 := data[..7]
113+
part2 := data[7..]
114+
115+
for poly in reflected_test_polys {
116+
c := crc32.new(poly)
117+
full := c.checksum(data)
118+
119+
mut split := u32(0)
120+
split = c.update(split, part1)
121+
split = c.update(split, part2)
122+
123+
assert full == split
124+
assert full == sum_for_reflected_poly(poly, data)
125+
}
126+
}
127+
128+
fn test_hash_crc32_streaming_chunk_sizes() {
129+
data := ('streaming data block '.repeat(64)).bytes()
130+
for poly in reflected_test_polys {
131+
c := crc32.new(poly)
132+
expected := c.checksum(data)
133+
for chunk_size in [1, 2, 3, 5, 7, 16, 31, 64, 128] {
134+
mut state := ~u32(0)
135+
mut start := 0
136+
for start < data.len {
137+
end := if start + chunk_size < data.len { start + chunk_size } else { data.len }
138+
state = c.update_state(state, data[start..end])
139+
start = end
140+
}
141+
assert ~state == expected
142+
}
143+
}
144+
}
145+
146+
fn test_hash_crc32_update_state() {
147+
data := 'stateful streaming'.bytes()
148+
part1 := data[..5]
149+
part2 := data[5..]
150+
c := crc32.new(crc32.crc32c)
151+
152+
mut state := ~u32(0)
153+
state = c.update_state(state, part1)
154+
state = c.update_state(state, part2)
155+
156+
assert ~state == c.checksum(data)
157+
}

0 commit comments

Comments
 (0)