compress_lib/checksum/adler32.rs
1//! Adler-32 (RFC 1950), four lanes with deferred modulo.
2
3/// The largest prime below 2^16.
4const MOD: u32 = 65_521;
5
6/// The most bytes per lane before the sums must be reduced: the largest `n`
7/// with `255 * n * (n + 1) / 2 + (n + 1) * (MOD - 1) < 2^32` (zlib's NMAX).
8const NMAX: usize = 5_552;
9
10/// Lanes processed side by side; the compiler keeps them in one vector
11/// register.
12const LANES: usize = 4;
13
14/// Advances `(a, b)` over `data`.
15fn update(a: u32, b: u32, data: &[u8]) -> (u32, u32) {
16 let mut a = a;
17 let mut b = b;
18 let split = data.len() - data.len() % LANES;
19 let (body, tail) = data.split_at(split);
20
21 let mut lane_a = [0u32; LANES];
22 let mut lane_b = [0u32; LANES];
23 for block in body.chunks(NMAX * LANES) {
24 for group in block.chunks_exact(LANES) {
25 for i in 0..LANES {
26 lane_a[i] += u32::from(group[i]);
27 lane_b[i] += lane_a[i];
28 }
29 }
30 // `a` stays the value before this run of lanes; every byte of the
31 // block adds it once to `b`. `block.len() * a` fits: at most
32 // 22,208 * 65,520.
33 b += (block.len() as u32) * a;
34 for i in 0..LANES {
35 lane_a[i] %= MOD;
36 lane_b[i] %= MOD;
37 }
38 b %= MOD;
39 }
40
41 // Fold the lanes: byte `4k + j` of the run contributes `(m - k)` times
42 // to lane `j` of `b`, and should contribute `(4m - 4k - j)` times.
43 for i in 0..LANES {
44 b += LANES as u32 * lane_b[i] + (MOD - lane_a[i]) * i as u32;
45 a += lane_a[i];
46 }
47 for &byte in tail {
48 a += u32::from(byte);
49 b += a;
50 }
51 (a % MOD, b % MOD)
52}
53
54/// Computes the Adler-32 of `data` in one call (the zlib checksum, RFC 1950
55/// section 8).
56///
57/// # Examples
58///
59/// ```
60/// use compress_lib::checksum::adler32;
61///
62/// assert_eq!(adler32(b"Wikipedia"), 0x11E6_0398);
63/// assert_eq!(adler32(b""), 1);
64/// ```
65#[must_use]
66pub fn adler32(data: &[u8]) -> u32 {
67 let (a, b) = update(1, 0, data);
68 (b << 16) | a
69}
70
71/// An Adler-32 computed over data that arrives in pieces.
72///
73/// # Examples
74///
75/// ```
76/// use compress_lib::checksum::{Adler32, adler32};
77///
78/// let mut sum = Adler32::new();
79/// sum.update(b"Wiki");
80/// sum.update(b"pedia");
81/// assert_eq!(sum.finish(), adler32(b"Wikipedia"));
82/// ```
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
84pub struct Adler32 {
85 a: u32,
86 b: u32,
87 len: u64,
88}
89
90impl Adler32 {
91 /// An Adler-32 over no data yet (value 1).
92 ///
93 /// # Examples
94 ///
95 /// ```
96 /// assert_eq!(compress_lib::checksum::Adler32::new().finish(), 1);
97 /// ```
98 #[must_use]
99 pub const fn new() -> Self {
100 Self { a: 1, b: 0, len: 0 }
101 }
102
103 /// Continues from an Adler-32 value already computed over `len` bytes.
104 /// Out-of-range halves (at least 65,521) are reduced first.
105 ///
106 /// # Examples
107 ///
108 /// ```
109 /// use compress_lib::checksum::{Adler32, adler32};
110 ///
111 /// let mut sum = Adler32::from_value(adler32(b"Wiki"), 4);
112 /// sum.update(b"pedia");
113 /// assert_eq!(sum.finish(), adler32(b"Wikipedia"));
114 /// ```
115 #[must_use]
116 pub const fn from_value(adler: u32, len: u64) -> Self {
117 Self {
118 a: (adler & 0xFFFF) % MOD,
119 b: (adler >> 16) % MOD,
120 len,
121 }
122 }
123
124 /// Feeds more data.
125 ///
126 /// # Examples
127 ///
128 /// ```
129 /// let mut sum = compress_lib::checksum::Adler32::new();
130 /// sum.update(b"abc");
131 /// assert_eq!(sum.finish(), 0x024D_0127);
132 /// ```
133 pub fn update(&mut self, data: &[u8]) {
134 let (a, b) = update(self.a, self.b, data);
135 self.a = a;
136 self.b = b;
137 self.len = self.len.wrapping_add(data.len() as u64);
138 }
139
140 /// The Adler-32 of everything fed so far. Does not reset.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// let mut sum = compress_lib::checksum::Adler32::new();
146 /// sum.update(b"a");
147 /// assert_eq!(sum.finish(), 0x0062_0062);
148 /// ```
149 #[must_use]
150 pub const fn finish(&self) -> u32 {
151 (self.b << 16) | self.a
152 }
153
154 /// The number of bytes fed so far (wrapping at 2^64).
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// let mut sum = compress_lib::checksum::Adler32::new();
160 /// sum.update(b"abc");
161 /// assert_eq!(sum.len(), 3);
162 /// ```
163 #[must_use]
164 pub const fn len(&self) -> u64 {
165 self.len
166 }
167
168 /// `true` when no data has been fed.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// assert!(compress_lib::checksum::Adler32::new().is_empty());
174 /// ```
175 #[must_use]
176 pub const fn is_empty(&self) -> bool {
177 self.len == 0
178 }
179
180 /// Appends the checksum of a second piece of data computed separately.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use compress_lib::checksum::{Adler32, adler32};
186 ///
187 /// let mut left = Adler32::new();
188 /// left.update(b"hello ");
189 /// let mut right = Adler32::new();
190 /// right.update(b"world");
191 /// left.combine(&right);
192 /// assert_eq!(left.finish(), adler32(b"hello world"));
193 /// ```
194 pub fn combine(&mut self, other: &Adler32) {
195 *self = Self::from_value(adler32_combine(self.finish(), other.finish(), other.len), 0);
196 self.len = self.len.wrapping_add(other.len);
197 }
198}
199
200impl Default for Adler32 {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205
206/// Combines `adler1` (over data A) and `adler2` (over data B, `len2` bytes)
207/// into the Adler-32 of A followed by B, in constant time.
208///
209/// # Examples
210///
211/// ```
212/// use compress_lib::checksum::{adler32, adler32_combine};
213///
214/// let ab = adler32_combine(adler32(b"hello "), adler32(b"world"), 5);
215/// assert_eq!(ab, adler32(b"hello world"));
216/// ```
217#[must_use]
218pub const fn adler32_combine(adler1: u32, adler2: u32, len2: u64) -> u32 {
219 let rem = (len2 % MOD as u64) as u32;
220 let a1 = (adler1 & 0xFFFF) % MOD;
221 let b1 = (adler1 >> 16) % MOD;
222 let a2 = (adler2 & 0xFFFF) % MOD;
223 let b2 = (adler2 >> 16) % MOD;
224 // a = a1 + a2 - 1; b = b1 + b2 + rem * (a1 - 1) (mod MOD)
225 let a = (a1 + a2 + MOD - 1) % MOD;
226 let b = (b1 + b2 + (rem * a1) % MOD + MOD - rem) % MOD;
227 (b << 16) | a
228}
229
230#[cfg(test)]
231mod tests {
232 use alloc::vec;
233
234 use proptest::prelude::*;
235
236 use super::*;
237
238 fn reference(data: &[u8]) -> u32 {
239 let (mut a, mut b) = (1u32, 0u32);
240 for &x in data {
241 a = (a + u32::from(x)) % MOD;
242 b = (b + a) % MOD;
243 }
244 (b << 16) | a
245 }
246
247 #[test]
248 fn test_adler32_known_strings_match() {
249 assert_eq!(adler32(b""), 1);
250 assert_eq!(adler32(b"a"), 0x0062_0062);
251 assert_eq!(adler32(b"abc"), 0x024D_0127);
252 assert_eq!(adler32(b"Wikipedia"), 0x11E6_0398);
253 }
254
255 #[test]
256 fn test_adler32_all_ff_past_nmax_does_not_overflow() {
257 // 0xFF bytes are the worst case for the deferred modulo.
258 for n in [
259 NMAX - 1,
260 NMAX,
261 NMAX + 1,
262 NMAX * LANES,
263 NMAX * LANES + 3,
264 100_000,
265 ] {
266 let data = vec![0xFFu8; n];
267 assert_eq!(adler32(&data), reference(&data), "length {n}");
268 }
269 }
270
271 #[test]
272 fn test_adler32_from_value_reduces_out_of_range() {
273 // 0xFFFF in either half is not a valid sum; it reduces modulo 65,521.
274 let sum = Adler32::from_value(0xFFFF_FFFF, 0);
275 assert_eq!(sum.finish(), (14 << 16) | 14);
276 assert_eq!(Adler32::default(), Adler32::new());
277 }
278
279 #[test]
280 fn test_adler32_combine_empty_second_is_identity() {
281 let x = adler32(b"data");
282 assert_eq!(adler32_combine(x, 1, 0), x);
283 }
284
285 proptest! {
286 #[test]
287 fn prop_adler32_matches_reference(data in proptest::collection::vec(any::<u8>(), 0..30_000)) {
288 prop_assert_eq!(adler32(&data), reference(&data));
289 }
290
291 #[test]
292 fn prop_adler32_split_invariant(data in proptest::collection::vec(any::<u8>(), 0..4096), cut in any::<usize>()) {
293 let cut = if data.is_empty() { 0 } else { cut % (data.len() + 1) };
294 let mut sum = Adler32::new();
295 sum.update(&data[..cut]);
296 sum.update(&data[cut..]);
297 prop_assert_eq!(sum.finish(), adler32(&data));
298 prop_assert_eq!(sum.len(), data.len() as u64);
299 }
300
301 #[test]
302 fn prop_adler32_combine_matches_concatenation(a in proptest::collection::vec(any::<u8>(), 0..512), b in proptest::collection::vec(any::<u8>(), 0..70_000)) {
303 let mut whole = a.clone();
304 whole.extend_from_slice(&b);
305 prop_assert_eq!(adler32_combine(adler32(&a), adler32(&b), b.len() as u64), adler32(&whole));
306 }
307 }
308}