Skip to main content

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}