koopman_checksum/lib.rs
1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4// Copyright (c) 2025 the koopman-checksum authors, all rights reserved.
5// See README.md for licensing information.
6
7use core::num::{NonZeroU32, NonZeroU64};
8
9// ============================================================================
10// Constants
11// ============================================================================
12
13/// Recommended modulus for 8-bit Koopman checksum.
14/// Detects all 1-bit and 2-bit errors for data up to 13 bytes.
15pub const MODULUS_8: u32 = 253;
16
17/// Recommended modulus for 16-bit Koopman checksum.
18/// Detects all 1-bit and 2-bit errors for data up to 4092 bytes.
19pub const MODULUS_16: u32 = 65519;
20
21/// Recommended modulus for 32-bit Koopman checksum.
22/// Detects all 1-bit and 2-bit errors for data up to 134,217,720 bytes.
23pub const MODULUS_32: u64 = 4294967291;
24
25/// Modulus for 7-bit Koopman checksum with parity.
26/// Detects all 1-bit, 2-bit, and 3-bit errors for data up to 5 bytes.
27pub const MODULUS_7P: u32 = 125;
28
29/// Modulus for 15-bit Koopman checksum with parity.
30/// Detects all 1-bit, 2-bit, and 3-bit errors for data up to 2044 bytes.
31pub const MODULUS_15P: u32 = 32749;
32
33/// Modulus for 31-bit Koopman checksum with parity.
34/// Detects all 1-bit, 2-bit, and 3-bit errors for data up to 134,217,720 bytes.
35pub const MODULUS_31P: u64 = 2147483629;
36
37const NONZERO_MODULUS_8: NonZeroU32 = NonZeroU32::new(MODULUS_8).unwrap();
38const NONZERO_MODULUS_7P: NonZeroU32 = NonZeroU32::new(MODULUS_7P).unwrap();
39const NONZERO_MODULUS_15P: NonZeroU32 = NonZeroU32::new(MODULUS_15P).unwrap();
40const NONZERO_MODULUS_31P: NonZeroU64 = NonZeroU64::new(MODULUS_31P).unwrap();
41
42// ============================================================================
43// Fast Modular Reduction
44//
45// The moduli are of the form 2^k - c where c is small:
46// - 65519 = 2^16 - 17
47// - 4294967291 = 2^32 - 5
48//
49// This allows fast reduction: x % (2^k - c) ≡ (x >> k) * c + (x & (2^k - 1))
50// ============================================================================
51
52/// Fast reduction for modulus 65519 = 2^16 - 17
53/// Input: x up to (MODULUS_16 - 1) << 16 + 0xFFFF ~= 4_293_918_719 (remains < 2^32)
54#[inline(always)]
55fn fast_mod_65519(x: u32) -> u32 {
56 // First reduction: x = hi * 2^16 + lo, result = hi * 17 + lo
57 let hi: u32 = x >> 16;
58 let lo: u32 = x & 0xFFFF;
59 let r: u32 = hi * 17 + lo;
60 // r < 17 * 256 + 65536 = 69888
61 // Second reduction
62 let hi2: u32 = r >> 16;
63 let lo2: u32 = r & 0xFFFF;
64 let r2: u32 = hi2 * 17 + lo2;
65 // r2 < 17 * 2 + 65536 = 65570
66 if r2 >= MODULUS_16 { r2 - MODULUS_16 } else { r2 }
67}
68
69/// Fast reduction for modulus 4294967291 = 2^32 - 5
70/// Input: x < 2^40 (after shift+add)
71#[inline(always)]
72fn fast_mod_4294967291(x: u64) -> u64 {
73 // x = hi * 2^32 + lo, result = hi * 5 + lo
74 let hi: u64 = x >> 32;
75 let lo: u64 = x & 0xFFFFFFFF;
76 let r: u64 = hi * 5 + lo;
77 // r < 5 * 2^8 + 2^32, need one check
78 if r >= MODULUS_32 { r - MODULUS_32 } else { r }
79}
80
81/// Compute an 8-bit Koopman checksum.
82///
83/// Detects all 1-bit and 2-bit errors for data up to 13 bytes with modulus 253.
84///
85/// # Arguments
86/// * `data` - The data bytes to checksum
87/// * `initial_seed` - Initial seed value
88///
89/// # Returns
90/// 8-bit checksum value, or 0 if data is empty
91///
92/// # Example
93/// ```rust
94/// use koopman_checksum::koopman8;
95///
96/// let checksum = koopman8(b"test data", 0xee);
97/// assert_eq!(koopman8(&[], 0xee), 0); // Empty data returns 0
98/// ```
99#[inline]
100#[must_use]
101pub fn koopman8(data: &[u8], initial_seed: u8) -> u8 {
102 koopman8_with_modulus(data, initial_seed, NONZERO_MODULUS_8)
103}
104
105/// Compute an 8-bit Koopman checksum with a custom modulus.
106///
107/// # Arguments
108/// * `data` - The data bytes to checksum
109/// * `initial_seed` - Initial seed value
110/// * `modulus` - The modulus to use (recommended: 253 or 239). Must be non-zero.
111///
112/// # Returns
113/// 8-bit checksum value, or 0 if data is empty
114///
115/// # Example
116/// ```rust
117/// use std::num::NonZeroU32;
118/// use koopman_checksum::koopman8_with_modulus;
119///
120/// let modulus = NonZeroU32::new(239).unwrap();
121/// let checksum = koopman8_with_modulus(b"test", 0xee, modulus);
122/// ```
123#[inline]
124#[must_use]
125pub fn koopman8_with_modulus(data: &[u8], initial_seed: u8, modulus: NonZeroU32) -> u8 {
126 if data.is_empty() {
127 return 0;
128 }
129
130 let modulus = modulus.get();
131 let mut sum: u32 = (data[0] ^ initial_seed) as u32;
132
133 for &byte in &data[1..] {
134 sum = ((sum << 8) + byte as u32) % modulus;
135 }
136
137 // Append implicit zero byte
138 sum = (sum << 8) % modulus;
139
140 sum as u8
141}
142
143/// Compute a 16-bit Koopman checksum.
144///
145/// Detects all 1-bit and 2-bit errors for data up to 4092 bytes.
146///
147/// # Arguments
148/// * `data` - The data bytes to checksum
149/// * `initial_seed` - Initial seed value
150///
151/// # Returns
152/// 16-bit checksum value, or 0 if data is empty
153///
154/// # Example
155/// ```rust
156/// use koopman_checksum::koopman16;
157///
158/// let checksum = koopman16(b"test data", 0xee);
159/// assert_eq!(koopman16(&[], 0xee), 0); // Empty data returns 0
160/// ```
161#[inline]
162#[must_use]
163pub fn koopman16(data: &[u8], initial_seed: u8) -> u16 {
164 if data.is_empty() {
165 return 0;
166 }
167
168 let mut sum: u64 = (data[0] ^ initial_seed) as u64;
169
170 // Process bytes with delayed modulo reduction every 2 bytes
171 // This reduces the number of modulo operations by half
172 let mut count = 0;
173 for &byte in &data[1..] {
174 sum = (sum << 8) + byte as u64;
175 count += 1;
176 if count == 2 {
177 sum = fast_mod_65519(sum as u32) as u64;
178 count = 0;
179 }
180 }
181
182 // Final reduction if needed
183 if count > 0 {
184 sum = fast_mod_65519(sum as u32) as u64;
185 }
186
187 // Append two implicit zero bytes
188 sum = fast_mod_65519((sum << 8) as u32) as u64;
189 sum = fast_mod_65519((sum << 8) as u32) as u64;
190
191 sum as u16
192}
193
194/// Compute a 16-bit Koopman checksum with a custom modulus.
195///
196/// # Arguments
197/// * `data` - The data bytes to checksum
198/// * `initial_seed` - Initial seed value
199/// * `modulus` - The modulus to use. Must be non-zero.
200///
201/// # Returns
202/// 16-bit checksum value, or 0 if data is empty
203///
204/// # Example
205/// ```rust
206/// use std::num::NonZeroU32;
207/// use koopman_checksum::koopman16_with_modulus;
208///
209/// let modulus = NonZeroU32::new(65519).unwrap();
210/// let checksum = koopman16_with_modulus(b"test", 0xee, modulus);
211/// ```
212#[inline]
213#[must_use]
214pub fn koopman16_with_modulus(data: &[u8], initial_seed: u8, modulus: NonZeroU32) -> u16 {
215 if data.is_empty() {
216 return 0;
217 }
218
219 let modulus = modulus.get();
220 let mut sum: u32 = (data[0] ^ initial_seed) as u32;
221
222 for &byte in &data[1..] {
223 sum = ((sum << 8) + byte as u32) % modulus;
224 }
225
226 // Append two implicit zero bytes
227 sum = (sum << 8) % modulus;
228 sum = (sum << 8) % modulus;
229
230 sum as u16
231}
232
233/// Compute a 32-bit Koopman checksum.
234///
235/// Detects all 1-bit and 2-bit errors for data up to 134,217,720 bytes.
236///
237/// # Arguments
238/// * `data` - The data bytes to checksum
239/// * `initial_seed` - Initial seed value
240///
241/// # Returns
242/// 32-bit checksum value, or 0 if data is empty
243///
244/// # Example
245/// ```rust
246/// use koopman_checksum::koopman32;
247///
248/// let checksum = koopman32(b"test data", 0xee);
249/// assert_eq!(koopman32(&[], 0xee), 0); // Empty data returns 0
250/// ```
251#[inline]
252#[must_use]
253pub fn koopman32(data: &[u8], initial_seed: u8) -> u32 {
254 if data.is_empty() {
255 return 0;
256 }
257
258 let mut sum: u64 = (data[0] ^ initial_seed) as u64;
259
260 // Use fast modular reduction for the default modulus
261 for &byte in &data[1..] {
262 sum = fast_mod_4294967291((sum << 8) + byte as u64);
263 }
264
265 // Append four implicit zero bytes
266 sum = fast_mod_4294967291(sum << 8);
267 sum = fast_mod_4294967291(sum << 8);
268 sum = fast_mod_4294967291(sum << 8);
269 sum = fast_mod_4294967291(sum << 8);
270
271 sum as u32
272}
273
274/// Compute a 32-bit Koopman checksum with a custom modulus.
275///
276/// # Arguments
277/// * `data` - The data bytes to checksum
278/// * `initial_seed` - Initial seed value
279/// * `modulus` - The modulus to use. Must be non-zero.
280///
281/// # Returns
282/// 32-bit checksum value, or 0 if data is empty
283///
284/// # Example
285/// ```rust
286/// use std::num::NonZeroU64;
287/// use koopman_checksum::koopman32_with_modulus;
288///
289/// let modulus = NonZeroU64::new(4294967291).unwrap();
290/// let checksum = koopman32_with_modulus(b"test", 0xee, modulus);
291/// ```
292#[inline]
293#[must_use]
294pub fn koopman32_with_modulus(data: &[u8], initial_seed: u8, modulus: NonZeroU64) -> u32 {
295 if data.is_empty() {
296 return 0;
297 }
298
299 let modulus = modulus.get();
300 let mut sum: u64 = (data[0] ^ initial_seed) as u64;
301
302 for &byte in &data[1..] {
303 sum = ((sum << 8) + byte as u64) % modulus;
304 }
305
306 // Append four implicit zero bytes
307 sum = (sum << 8) % modulus;
308 sum = (sum << 8) % modulus;
309 sum = (sum << 8) % modulus;
310 sum = (sum << 8) % modulus;
311
312 sum as u32
313}
314
315// ============================================================================
316// Parity Variants (HD=4)
317// ============================================================================
318
319/// Compute parity of a byte (number of set bits mod 2).
320#[inline]
321fn parity8(x: u8) -> u8 {
322 (x.count_ones() & 1) as u8
323}
324
325/// Compute an 8-bit Koopman checksum with parity (7-bit checksum + 1 parity bit).
326///
327/// Detects all 1-bit, 2-bit, and 3-bit errors for data up to 5 bytes.
328/// Uses modulus 125 for the 7-bit checksum portion.
329///
330/// # Arguments
331/// * `data` - The data bytes to checksum
332/// * `initial_seed` - Initial seed value
333///
334/// # Returns
335/// 8-bit value: 7-bit checksum in upper bits, parity in LSB, or 0 if data is empty
336///
337/// # Example
338/// ```rust
339/// use koopman_checksum::koopman8p;
340///
341/// let checksum = koopman8p(b"test", 0xee);
342/// let parity_bit = checksum & 1;
343/// let checksum_bits = checksum >> 1;
344/// ```
345#[inline]
346#[must_use]
347pub fn koopman8p(data: &[u8], initial_seed: u8) -> u8 {
348 koopman8p_with_modulus(data, initial_seed, NONZERO_MODULUS_7P)
349}
350
351/// Compute an 8-bit Koopman checksum with parity using a custom modulus.
352///
353/// # Arguments
354/// * `data` - The data bytes to checksum
355/// * `initial_seed` - Initial seed value
356/// * `modulus` - The modulus for the 7-bit checksum. Must be non-zero and <= 127.
357///
358/// # Returns
359/// 8-bit value: 7-bit checksum in upper bits, parity in LSB, or 0 if data is empty
360///
361/// # Example
362/// ```rust
363/// use std::num::NonZeroU32;
364/// use koopman_checksum::koopman8p_with_modulus;
365///
366/// let modulus = NonZeroU32::new(125).unwrap();
367/// let checksum = koopman8p_with_modulus(b"test", 0xee, modulus);
368/// ```
369#[inline]
370#[must_use]
371pub fn koopman8p_with_modulus(data: &[u8], initial_seed: u8, modulus: NonZeroU32) -> u8 {
372 if data.is_empty() {
373 return 0;
374 }
375
376 let modulus = modulus.get();
377 let mut sum: u32 = (data[0] ^ initial_seed) as u32;
378 let mut psum: u8 = sum as u8;
379
380 for &byte in &data[1..] {
381 sum = ((sum << 8) + byte as u32) % modulus;
382 psum ^= byte;
383 }
384
385 // Append implicit zero byte
386 sum = (sum << 8) % modulus;
387
388 // Pack: checksum in upper 7 bits, parity in LSB
389 // Parity covers the same byte stream as the checksum core, i.e. data[0] ^ seed
390 ((sum as u8) << 1) | parity8(psum)
391}
392
393/// Compute a 16-bit Koopman checksum with parity (15-bit checksum + 1 parity bit).
394///
395/// Detects all 1-bit, 2-bit, and 3-bit errors for data up to 2044 bytes.
396/// Uses modulus 32749 for the 15-bit checksum portion.
397///
398/// # Arguments
399/// * `data` - The data bytes to checksum
400/// * `initial_seed` - Initial seed value
401///
402/// # Returns
403/// 16-bit value: 15-bit checksum in upper bits, parity in LSB, or 0 if data is empty
404///
405/// # Example
406/// ```rust
407/// use koopman_checksum::koopman16p;
408///
409/// let checksum = koopman16p(b"test data", 0xee);
410/// let parity_bit = checksum & 1;
411/// let checksum_bits = checksum >> 1;
412/// ```
413#[inline]
414#[must_use]
415pub fn koopman16p(data: &[u8], initial_seed: u8) -> u16 {
416 koopman16p_with_modulus(data, initial_seed, NONZERO_MODULUS_15P)
417}
418
419/// Compute a 16-bit Koopman checksum with parity using a custom modulus.
420///
421/// # Arguments
422/// * `data` - The data bytes to checksum
423/// * `initial_seed` - Initial seed value
424/// * `modulus` - The modulus for the 15-bit checksum. Must be non-zero and ≤ 32767.
425///
426/// # Returns
427/// 16-bit value: 15-bit checksum in upper bits, parity in LSB, or 0 if data is empty
428///
429/// # Example
430/// ```rust
431/// use std::num::NonZeroU32;
432/// use koopman_checksum::koopman16p_with_modulus;
433///
434/// let modulus = NonZeroU32::new(32749).unwrap();
435/// let checksum = koopman16p_with_modulus(b"test", 0xee, modulus);
436/// ```
437#[inline]
438#[must_use]
439pub fn koopman16p_with_modulus(data: &[u8], initial_seed: u8, modulus: NonZeroU32) -> u16 {
440 if data.is_empty() {
441 return 0;
442 }
443
444 let modulus = modulus.get();
445 let mut sum: u32 = (data[0] ^ initial_seed) as u32;
446 let mut psum: u8 = sum as u8;
447
448 for &byte in &data[1..] {
449 sum = ((sum << 8) + byte as u32) % modulus;
450 psum ^= byte;
451 }
452
453 // Append two implicit zero bytes
454 sum = (sum << 8) % modulus;
455 sum = (sum << 8) % modulus;
456
457 // Pack: checksum in upper 15 bits, parity in LSB
458 // Parity covers the same byte stream as the checksum core, i.e. data[0] ^ seed
459 ((sum as u16) << 1) | (parity8(psum) as u16)
460}
461
462/// Compute a 32-bit Koopman checksum with parity (31-bit checksum + 1 parity bit).
463///
464/// Detects all 1-bit, 2-bit, and 3-bit errors for data up to 134,217,720 bytes.
465/// Uses modulus 2147483629 for the 31-bit checksum portion.
466///
467/// # Arguments
468/// * `data` - The data bytes to checksum
469/// * `initial_seed` - Initial seed value
470///
471/// # Returns
472/// 32-bit value: 31-bit checksum in upper bits, parity in LSB, or 0 if data is empty
473///
474/// # Example
475/// ```rust
476/// use koopman_checksum::koopman32p;
477///
478/// let checksum = koopman32p(b"test data", 0xee);
479/// let parity_bit = checksum & 1;
480/// let checksum_bits = checksum >> 1;
481/// ```
482#[inline]
483#[must_use]
484pub fn koopman32p(data: &[u8], initial_seed: u8) -> u32 {
485 koopman32p_with_modulus(data, initial_seed, NONZERO_MODULUS_31P)
486}
487
488/// Compute a 32-bit Koopman checksum with parity using a custom modulus.
489///
490/// # Arguments
491/// * `data` - The data bytes to checksum
492/// * `initial_seed` - Initial seed value
493/// * `modulus` - The modulus for the 31-bit checksum. Must be non-zero and <= 2^31-1.
494///
495/// # Returns
496/// 32-bit value: 31-bit checksum in upper bits, parity in LSB, or 0 if data is empty
497///
498/// # Example
499/// ```rust
500/// use std::num::NonZeroU64;
501/// use koopman_checksum::koopman32p_with_modulus;
502///
503/// let modulus = NonZeroU64::new(2147483629).unwrap();
504/// let checksum = koopman32p_with_modulus(b"test", 0xee, modulus);
505/// ```
506#[inline]
507#[must_use]
508pub fn koopman32p_with_modulus(data: &[u8], initial_seed: u8, modulus: NonZeroU64) -> u32 {
509 if data.is_empty() {
510 return 0;
511 }
512
513 let modulus = modulus.get();
514 let mut sum: u64 = (data[0] ^ initial_seed) as u64;
515 let mut psum: u8 = sum as u8;
516
517 for &byte in &data[1..] {
518 sum = ((sum << 8) + byte as u64) % modulus;
519 psum ^= byte;
520 }
521
522 // Append four implicit zero bytes
523 sum = (sum << 8) % modulus;
524 sum = (sum << 8) % modulus;
525 sum = (sum << 8) % modulus;
526 sum = (sum << 8) % modulus;
527
528 // Pack: checksum in upper 31 bits, parity in LSB
529 // Parity covers the same byte stream as the checksum core, i.e. data[0] ^ seed
530 ((sum as u32) << 1) | (parity8(psum) as u32)
531}
532
533// ============================================================================
534// Streaming/Incremental API
535// ============================================================================
536
537/// Macro to generate streaming checksum structs.
538/// This reduces code duplication across Koopman8, Koopman16, Koopman32.
539macro_rules! impl_streaming_hasher {
540 (
541 $name:ident,
542 $sum_type:ty,
543 $output_type:ty,
544 $default_modulus_raw:expr,
545 $nonzero_type:ty,
546 $finalize_shifts:expr,
547 $fast_mod:expr
548 ) => {
549 impl Default for $name {
550 fn default() -> Self {
551 Self::new()
552 }
553 }
554
555 impl $name {
556 /// Create a new hasher with the default modulus.
557 #[inline]
558 pub fn new() -> Self {
559 Self {
560 sum: 0,
561 modulus: $default_modulus_raw,
562 seed: 0,
563 initialized: false,
564 use_fast_mod: true,
565 }
566 }
567
568 /// Create a new hasher with a custom modulus.
569 ///
570 /// # Arguments
571 /// * `modulus` - The modulus to use. Must be non-zero.
572 ///
573 /// # Example
574 /// ```rust
575 #[doc = concat!("use std::num::", stringify!($nonzero_type), ";")]
576 #[doc = concat!("use koopman_checksum::{", stringify!($name), ", ", stringify!($default_modulus_raw), "};")]
577 ///
578 #[doc = concat!("let modulus = ", stringify!($nonzero_type), "::new(", stringify!($default_modulus_raw), ").unwrap();")]
579 #[doc = concat!("let hasher = ", stringify!($name), "::with_modulus(modulus);")]
580 /// ```
581 #[inline]
582 pub fn with_modulus(modulus: $nonzero_type) -> Self {
583 let modulus_val = modulus.get();
584 Self {
585 sum: 0,
586 modulus: modulus_val,
587 seed: 0,
588 initialized: false,
589 use_fast_mod: modulus_val == $default_modulus_raw,
590 }
591 }
592
593 /// Create a new hasher with an initial seed.
594 ///
595 /// # Example
596 /// ```rust
597 #[doc = concat!("use koopman_checksum::", stringify!($name), ";")]
598 ///
599 #[doc = concat!("let hasher = ", stringify!($name), "::with_seed(0xee);")]
600 /// ```
601 #[inline]
602 pub fn with_seed(seed: u8) -> Self {
603 Self {
604 sum: seed as $sum_type,
605 modulus: $default_modulus_raw,
606 seed: seed as $sum_type,
607 initialized: false,
608 use_fast_mod: true,
609 }
610 }
611
612 /// Update the checksum with more data.
613 #[inline]
614 pub fn update(&mut self, data: &[u8]) {
615 if data.is_empty() {
616 return;
617 }
618
619 let mut iter = data.iter();
620
621 if !self.initialized {
622 if let Some(&first) = iter.next() {
623 self.sum ^= first as $sum_type;
624 self.initialized = true;
625 }
626 }
627
628 if self.use_fast_mod {
629 for &byte in iter {
630 self.sum = $fast_mod((self.sum << 8) + byte as $sum_type);
631 }
632 } else {
633 for &byte in iter {
634 self.sum = ((self.sum << 8) + byte as $sum_type) % self.modulus;
635 }
636 }
637 }
638
639 /// Finalize and return the checksum.
640 ///
641 /// Returns 0 if no data was provided.
642 #[inline]
643 #[must_use]
644 pub fn finalize(self) -> $output_type {
645 if !self.initialized {
646 return 0;
647 }
648 let mut sum = self.sum;
649 if self.use_fast_mod {
650 for _ in 0..$finalize_shifts {
651 sum = $fast_mod(sum << 8);
652 }
653 } else {
654 for _ in 0..$finalize_shifts {
655 sum = (sum << 8) % self.modulus;
656 }
657 }
658 sum as $output_type
659 }
660
661 /// Reset the hasher to initial state.
662 #[inline]
663 pub fn reset(&mut self) {
664 self.sum = self.seed;
665 self.initialized = false;
666 }
667 }
668 };
669}
670
671/// Incremental Koopman8 checksum calculator.
672///
673/// Allows computing checksums over data that arrives in chunks.
674///
675/// # Example
676/// ```rust
677/// use koopman_checksum::Koopman8;
678///
679/// let mut hasher = Koopman8::new();
680/// hasher.update(b"Hello, ");
681/// hasher.update(b"World!");
682/// let checksum = hasher.finalize();
683/// ```
684#[derive(Clone, Debug)]
685pub struct Koopman8 {
686 sum: u32,
687 modulus: u32,
688 seed: u32,
689 initialized: bool,
690 use_fast_mod: bool,
691}
692
693// Koopman8 doesn't have a fast_mod, so we use a passthrough
694#[inline(always)]
695fn identity_mod_8(x: u32) -> u32 { x % MODULUS_8 }
696
697impl_streaming_hasher!(
698 Koopman8, u32, u8,
699 MODULUS_8, NonZeroU32,
700 1, identity_mod_8
701);
702
703/// Incremental Koopman16 checksum calculator.
704///
705/// Allows computing checksums over data that arrives in chunks.
706/// Uses fast modular reduction when using the default modulus.
707///
708/// # Example
709/// ```rust
710/// use koopman_checksum::Koopman16;
711///
712/// let mut hasher = Koopman16::new();
713/// hasher.update(b"Hello, ");
714/// hasher.update(b"World!");
715/// let checksum = hasher.finalize();
716/// ```
717#[derive(Clone, Debug)]
718pub struct Koopman16 {
719 sum: u32,
720 modulus: u32,
721 seed: u32,
722 initialized: bool,
723 use_fast_mod: bool,
724}
725
726impl_streaming_hasher!(
727 Koopman16, u32, u16,
728 MODULUS_16, NonZeroU32,
729 2, fast_mod_65519
730);
731
732/// Incremental Koopman32 checksum calculator.
733///
734/// Allows computing checksums over data that arrives in chunks.
735/// Uses fast modular reduction when using the default modulus.
736///
737/// # Example
738/// ```rust
739/// use koopman_checksum::Koopman32;
740///
741/// let mut hasher = Koopman32::new();
742/// hasher.update(b"Hello, ");
743/// hasher.update(b"World!");
744/// let checksum = hasher.finalize();
745/// ```
746#[derive(Clone, Debug)]
747pub struct Koopman32 {
748 sum: u64,
749 modulus: u64,
750 seed: u64,
751 initialized: bool,
752 use_fast_mod: bool,
753}
754
755impl_streaming_hasher!(
756 Koopman32, u64, u32,
757 MODULUS_32, NonZeroU64,
758 4, fast_mod_4294967291
759);
760
761// ============================================================================
762// Parity Streaming API
763// ============================================================================
764
765/// Macro to generate streaming parity checksum structs.
766macro_rules! impl_streaming_parity_hasher {
767 (
768 $name:ident,
769 $sum_type:ty,
770 $output_type:ty,
771 $default_modulus_raw:expr,
772 $nonzero_type:ty,
773 $finalize_shifts:expr
774 ) => {
775 impl Default for $name {
776 fn default() -> Self {
777 Self::new()
778 }
779 }
780
781 impl $name {
782 /// Create a new hasher with the default modulus.
783 #[inline]
784 pub fn new() -> Self {
785 Self {
786 sum: 0,
787 psum: 0,
788 modulus: $default_modulus_raw,
789 seed: 0,
790 initialized: false,
791 }
792 }
793
794 /// Create a new hasher with a custom modulus.
795 ///
796 /// # Arguments
797 /// * `modulus` - The modulus to use. Must be non-zero.
798 #[inline]
799 pub fn with_modulus(modulus: $nonzero_type) -> Self {
800 Self {
801 sum: 0,
802 psum: 0,
803 modulus: modulus.get(),
804 seed: 0,
805 initialized: false,
806 }
807 }
808
809 /// Create a new hasher with an initial seed.
810 #[inline]
811 pub fn with_seed(seed: u8) -> Self {
812 Self {
813 sum: seed as $sum_type,
814 psum: seed,
815 modulus: $default_modulus_raw,
816 seed: seed as $sum_type,
817 initialized: false,
818 }
819 }
820
821 /// Update the checksum with more data.
822 #[inline]
823 pub fn update(&mut self, data: &[u8]) {
824 if data.is_empty() {
825 return;
826 }
827
828 let mut iter = data.iter();
829
830 if !self.initialized {
831 if let Some(&first) = iter.next() {
832 self.sum ^= first as $sum_type;
833 self.psum ^= first;
834 self.initialized = true;
835 }
836 }
837
838 for &byte in iter {
839 self.sum = ((self.sum << 8) + byte as $sum_type) % self.modulus;
840 self.psum ^= byte;
841 }
842 }
843
844 /// Finalize and return the checksum with parity.
845 ///
846 /// Returns 0 if no data was provided.
847 #[inline]
848 #[must_use]
849 pub fn finalize(self) -> $output_type {
850 if !self.initialized {
851 return 0;
852 }
853 let mut sum = self.sum;
854 for _ in 0..$finalize_shifts {
855 sum = (sum << 8) % self.modulus;
856 }
857 // Pack: checksum in upper bits, parity in LSB
858 ((sum as $output_type) << 1) | (parity8(self.psum) as $output_type)
859 }
860
861 /// Reset the hasher to initial state.
862 #[inline]
863 pub fn reset(&mut self) {
864 self.sum = self.seed;
865 self.psum = self.seed as u8;
866 self.initialized = false;
867 }
868 }
869 };
870}
871
872/// Incremental Koopman8P checksum calculator (7-bit checksum + 1 parity bit).
873///
874/// Allows computing checksums over data that arrives in chunks.
875///
876/// # Example
877/// ```rust
878/// use koopman_checksum::Koopman8P;
879///
880/// let mut hasher = Koopman8P::new();
881/// hasher.update(b"Hello");
882/// let checksum = hasher.finalize();
883/// let parity_bit = checksum & 1;
884/// ```
885#[derive(Clone, Debug)]
886pub struct Koopman8P {
887 sum: u32,
888 psum: u8,
889 modulus: u32,
890 seed: u32,
891 initialized: bool,
892}
893
894impl_streaming_parity_hasher!(
895 Koopman8P, u32, u8,
896 MODULUS_7P, NonZeroU32,
897 1
898);
899
900/// Incremental Koopman16P checksum calculator (15-bit checksum + 1 parity bit).
901///
902/// Allows computing checksums over data that arrives in chunks.
903///
904/// # Example
905/// ```rust
906/// use koopman_checksum::Koopman16P;
907///
908/// let mut hasher = Koopman16P::new();
909/// hasher.update(b"Hello, ");
910/// hasher.update(b"World!");
911/// let checksum = hasher.finalize();
912/// let parity_bit = checksum & 1;
913/// ```
914#[derive(Clone, Debug)]
915pub struct Koopman16P {
916 sum: u32,
917 psum: u8,
918 modulus: u32,
919 seed: u32,
920 initialized: bool,
921}
922
923impl_streaming_parity_hasher!(
924 Koopman16P, u32, u16,
925 MODULUS_15P, NonZeroU32,
926 2
927);
928
929/// Incremental Koopman32P checksum calculator (31-bit checksum + 1 parity bit).
930///
931/// Allows computing checksums over data that arrives in chunks.
932///
933/// # Example
934/// ```rust
935/// use koopman_checksum::Koopman32P;
936///
937/// let mut hasher = Koopman32P::new();
938/// hasher.update(b"Hello, ");
939/// hasher.update(b"World!");
940/// let checksum = hasher.finalize();
941/// let parity_bit = checksum & 1;
942/// ```
943#[derive(Clone, Debug)]
944pub struct Koopman32P {
945 sum: u64,
946 psum: u8,
947 modulus: u64,
948 seed: u64,
949 initialized: bool,
950}
951
952impl_streaming_parity_hasher!(
953 Koopman32P, u64, u32,
954 MODULUS_31P, NonZeroU64,
955 4
956);
957
958// ============================================================================
959// Verification Functions
960// ============================================================================
961
962/// Verify data integrity using Koopman8 checksum.
963///
964/// # Arguments
965/// * `data` - The data bytes (excluding checksum)
966/// * `expected` - The expected checksum value
967/// * `initial_seed` - Initial seed used when computing the checksum
968///
969/// # Returns
970/// `true` if the checksum matches, `false` otherwise
971///
972/// # Example
973/// ```rust
974/// use koopman_checksum::{koopman8, verify8};
975///
976/// let data = b"test data";
977/// let checksum = koopman8(data, 0xee);
978/// assert!(verify8(data, checksum, 0xee));
979/// assert!(!verify8(data, checksum.wrapping_add(1), 0));
980/// ```
981#[inline]
982#[must_use]
983pub fn verify8(data: &[u8], expected: u8, initial_seed: u8) -> bool {
984 koopman8(data, initial_seed) == expected
985}
986
987/// Verify data integrity using Koopman16 checksum.
988///
989/// # Arguments
990/// * `data` - The data bytes (excluding checksum)
991/// * `expected` - The expected checksum value
992/// * `initial_seed` - Initial seed used when computing the checksum
993///
994/// # Returns
995/// `true` if the checksum matches, `false` otherwise
996///
997/// # Example
998/// ```rust
999/// use koopman_checksum::{koopman16, verify16};
1000///
1001/// let data = b"test data";
1002/// let checksum = koopman16(data, 0xee);
1003/// assert!(verify16(data, checksum, 0xee));
1004/// ```
1005#[inline]
1006#[must_use]
1007pub fn verify16(data: &[u8], expected: u16, initial_seed: u8) -> bool {
1008 koopman16(data, initial_seed) == expected
1009}
1010
1011/// Verify data integrity using Koopman32 checksum.
1012///
1013/// # Arguments
1014/// * `data` - The data bytes (excluding checksum)
1015/// * `expected` - The expected checksum value
1016/// * `initial_seed` - Initial seed used when computing the checksum
1017///
1018/// # Returns
1019/// `true` if the checksum matches, `false` otherwise
1020///
1021/// # Example
1022/// ```rust
1023/// use koopman_checksum::{koopman32, verify32};
1024///
1025/// let data = b"test data";
1026/// let checksum = koopman32(data, 0xee);
1027/// assert!(verify32(data, checksum, 0xee));
1028/// ```
1029#[inline]
1030#[must_use]
1031pub fn verify32(data: &[u8], expected: u32, initial_seed: u8) -> bool {
1032 koopman32(data, initial_seed) == expected
1033}
1034
1035/// Verify data integrity using Koopman8P checksum (with parity).
1036///
1037/// # Arguments
1038/// * `data` - The data bytes (excluding checksum)
1039/// * `expected` - The expected checksum value (7-bit checksum + 1 parity bit)
1040/// * `initial_seed` - Initial seed used when computing the checksum
1041///
1042/// # Returns
1043/// `true` if the checksum matches, `false` otherwise
1044///
1045/// # Example
1046/// ```rust
1047/// use koopman_checksum::{koopman8p, verify8p};
1048///
1049/// let data = b"test";
1050/// let checksum = koopman8p(data, 0xee);
1051/// assert!(verify8p(data, checksum, 0xee));
1052/// ```
1053#[inline]
1054#[must_use]
1055pub fn verify8p(data: &[u8], expected: u8, initial_seed: u8) -> bool {
1056 koopman8p(data, initial_seed) == expected
1057}
1058
1059/// Verify data integrity using Koopman16P checksum (with parity).
1060///
1061/// # Arguments
1062/// * `data` - The data bytes (excluding checksum)
1063/// * `expected` - The expected checksum value (15-bit checksum + 1 parity bit)
1064/// * `initial_seed` - Initial seed used when computing the checksum
1065///
1066/// # Returns
1067/// `true` if the checksum matches, `false` otherwise
1068///
1069/// # Example
1070/// ```rust
1071/// use koopman_checksum::{koopman16p, verify16p};
1072///
1073/// let data = b"test data";
1074/// let checksum = koopman16p(data, 0xee);
1075/// assert!(verify16p(data, checksum, 0xee));
1076/// ```
1077#[inline]
1078#[must_use]
1079pub fn verify16p(data: &[u8], expected: u16, initial_seed: u8) -> bool {
1080 koopman16p(data, initial_seed) == expected
1081}
1082
1083/// Verify data integrity using Koopman32P checksum (with parity).
1084///
1085/// # Arguments
1086/// * `data` - The data bytes (excluding checksum)
1087/// * `expected` - The expected checksum value (31-bit checksum + 1 parity bit)
1088/// * `initial_seed` - Initial seed used when computing the checksum
1089///
1090/// # Returns
1091/// `true` if the checksum matches, `false` otherwise
1092///
1093/// # Example
1094/// ```rust
1095/// use koopman_checksum::{koopman32p, verify32p};
1096///
1097/// let data = b"test data";
1098/// let checksum = koopman32p(data, 0xee);
1099/// assert!(verify32p(data, checksum, 0xee));
1100/// ```
1101#[inline]
1102#[must_use]
1103pub fn verify32p(data: &[u8], expected: u32, initial_seed: u8) -> bool {
1104 koopman32p(data, initial_seed) == expected
1105}
1106
1107// ============================================================================
1108// Tests
1109// ============================================================================
1110
1111#[cfg(test)]
1112mod tests {
1113 use super::*;
1114 use core::num::NonZeroU32;
1115 use core::num::NonZeroU64;
1116 const NONZERO_MODULUS_16: NonZeroU32 = NonZeroU32::new(MODULUS_16).unwrap();
1117 const NONZERO_MODULUS_32: NonZeroU64 = NonZeroU64::new(MODULUS_32).unwrap();
1118
1119 // Test vectors based on the C reference implementation
1120 const TEST_DATA: &[u8] = b"123456789";
1121
1122 #[test]
1123 fn test_koopman8_empty() {
1124 assert_eq!(koopman8(&[], 0), 0);
1125 assert_eq!(koopman8(&[], 42), 0); // Empty data returns 0 regardless of initial seed
1126 }
1127
1128 #[test]
1129 fn test_koopman8_single_byte() {
1130 // For single byte 0x12: sum = 0x12, then append zero: (0x12 << 8) % 253 = 4608 % 253 = 54
1131 assert_eq!(koopman8(&[0x12], 0), ((0x12u32 << 8) % MODULUS_8) as u8);
1132 }
1133
1134 #[test]
1135 fn test_koopman16_empty() {
1136 assert_eq!(koopman16(&[], 0), 0);
1137 assert_eq!(koopman16(&[], 42), 0); // Empty data returns 0 regardless of initial seed
1138 }
1139
1140 #[test]
1141 fn test_koopman32_empty() {
1142 assert_eq!(koopman32(&[], 0), 0);
1143 assert_eq!(koopman32(&[], 42), 0); // Empty data returns 0 regardless of initial seed
1144 }
1145
1146 #[test]
1147 fn test_streaming_koopman8() {
1148 let full = koopman8(TEST_DATA, 0);
1149
1150 let mut hasher = Koopman8::new();
1151 hasher.update(&TEST_DATA[..4]);
1152 hasher.update(&TEST_DATA[4..]);
1153 let streaming = hasher.finalize();
1154
1155 assert_eq!(full, streaming);
1156 }
1157
1158 #[test]
1159 fn test_streaming_koopman16() {
1160 let full = koopman16(TEST_DATA, 0);
1161
1162 let mut hasher = Koopman16::new();
1163 hasher.update(&TEST_DATA[..4]);
1164 hasher.update(&TEST_DATA[4..]);
1165 let streaming = hasher.finalize();
1166
1167 assert_eq!(full, streaming);
1168 }
1169
1170 #[test]
1171 fn test_streaming_koopman32() {
1172 let full = koopman32(TEST_DATA, 0);
1173
1174 let mut hasher = Koopman32::new();
1175 hasher.update(&TEST_DATA[..4]);
1176 hasher.update(&TEST_DATA[4..]);
1177 let streaming = hasher.finalize();
1178
1179 assert_eq!(full, streaming);
1180 }
1181
1182 #[test]
1183 fn test_seed_affects_result() {
1184 let result0 = koopman16(TEST_DATA, 0);
1185 let result1 = koopman16(TEST_DATA, 1);
1186 assert_ne!(result0, result1);
1187 }
1188
1189 #[test]
1190 fn test_single_bit_detection() {
1191 let original = koopman16(TEST_DATA, 0);
1192
1193 for i in 0..TEST_DATA.len() {
1194 for bit in 0..8 {
1195 let mut corrupted = TEST_DATA.to_vec();
1196 corrupted[i] ^= 1 << bit;
1197 let corrupted_checksum = koopman16(&corrupted, 0);
1198 assert_ne!(original, corrupted_checksum,
1199 "Failed to detect single bit flip at byte {} bit {}", i, bit);
1200 }
1201 }
1202 }
1203
1204 #[test]
1205 fn test_reference_calculation() {
1206 // Input: [0x12, 0x34, 0x56] with initial seed 0, modulus 253
1207 // Step 1: sum = 0x12 = 18
1208 // Step 2: sum = ((18 << 8) + 0x34) % 253 = 4660 % 253 = 106
1209 // Step 3: sum = ((106 << 8) + 0x56) % 253 = 27222 % 253 = 151
1210 // Final: sum = (151 << 8) % 253 = 38656 % 253 = 200
1211
1212 let data = [0x12u8, 0x34, 0x56];
1213 let result = koopman8(&data, 0);
1214 assert_eq!(result, 200);
1215 }
1216
1217 // ========================================================================
1218 // Additional tests for parity variants
1219 // ========================================================================
1220
1221 #[test]
1222 fn test_koopman8p_parity_correctness() {
1223 // Verify that the parity bit correctly reflects the parity of data bytes only
1224 // (per the reference C implementation, checksum is NOT included in parity)
1225 let data = b"Test";
1226 let result = koopman8p(data, 0);
1227
1228 // The checksum is in upper 7 bits
1229 let _checksum = result >> 1;
1230 let parity_bit = result & 1;
1231
1232 // Compute expected parity: XOR all data bytes (NOT including checksum)
1233 let mut expected_parity: u8 = 0;
1234 for &byte in data {
1235 expected_parity ^= byte;
1236 }
1237 let expected_parity_bit = expected_parity.count_ones() & 1;
1238
1239 assert_eq!(parity_bit as u32, expected_parity_bit);
1240 }
1241
1242 #[test]
1243 fn test_parity_variants_detect_single_bit_errors() {
1244 let data = b"Test";
1245 let original = koopman16p(data, 0);
1246
1247 for i in 0..data.len() {
1248 for bit in 0..8 {
1249 let mut corrupted = data.to_vec();
1250 corrupted[i] ^= 1 << bit;
1251 let corrupted_checksum = koopman16p(&corrupted, 0);
1252 assert_ne!(original, corrupted_checksum,
1253 "Failed to detect single bit flip at byte {} bit {}", i, bit);
1254 }
1255 }
1256 }
1257
1258 // ========================================================================
1259 // Tests for custom moduli
1260 // ========================================================================
1261
1262 #[test]
1263 fn test_custom_modulus_8() {
1264 const MODULUS_8_ALT: u32 = 239;
1265 let data = b"test";
1266 let result1 = koopman8_with_modulus(data, 0, NONZERO_MODULUS_8);
1267 let modulus_alt = NonZeroU32::new(MODULUS_8_ALT).unwrap();
1268 let result2 = koopman8_with_modulus(data, 0, modulus_alt);
1269
1270 // Different moduli should (usually) produce different results
1271 // Note: They could theoretically be equal, but very unlikely
1272 assert_ne!(result1, result2);
1273 }
1274
1275 #[test]
1276 fn test_custom_modulus_matches_default() {
1277 let data = b"test data";
1278
1279 assert_eq!(
1280 koopman8(data, 0),
1281 koopman8_with_modulus(data, 0, NONZERO_MODULUS_8)
1282 );
1283 assert_eq!(
1284 koopman16(data, 0),
1285 koopman16_with_modulus(data, 0, NONZERO_MODULUS_16)
1286 );
1287 assert_eq!(
1288 koopman32(data, 0),
1289 koopman32_with_modulus(data, 0, NONZERO_MODULUS_32)
1290 );
1291 }
1292
1293 #[test]
1294 fn test_parity_custom_modulus_matches_default() {
1295 let data = b"test data";
1296
1297 assert_eq!(
1298 koopman8p(data, 0),
1299 koopman8p_with_modulus(data, 0, NONZERO_MODULUS_7P)
1300 );
1301 assert_eq!(
1302 koopman16p(data, 0),
1303 koopman16p_with_modulus(data, 0, NONZERO_MODULUS_15P)
1304 );
1305 assert_eq!(
1306 koopman32p(data, 0),
1307 koopman32p_with_modulus(data, 0, NONZERO_MODULUS_31P)
1308 );
1309 }
1310
1311 #[test]
1312 fn test_streaming_with_seed() {
1313 let data = b"test data";
1314 let seed = 42u8;
1315
1316 // One-shot with seed
1317 let expected = koopman16(data, seed);
1318
1319 // Streaming with seed
1320 let mut hasher = Koopman16::with_seed(seed);
1321 hasher.update(data);
1322 let streaming = hasher.finalize();
1323
1324 assert_eq!(expected, streaming);
1325 }
1326
1327 #[test]
1328 fn test_streaming_with_seed_chunked() {
1329 let data = b"test data for chunked processing";
1330 let seed = 123u8;
1331
1332 let expected = koopman16(data, seed);
1333
1334 let mut hasher = Koopman16::with_seed(seed);
1335 hasher.update(&data[..10]);
1336 hasher.update(&data[10..20]);
1337 hasher.update(&data[20..]);
1338 let streaming = hasher.finalize();
1339
1340 assert_eq!(expected, streaming);
1341 }
1342
1343 // ========================================================================
1344 // Tests for reset behavior
1345 // ========================================================================
1346
1347 #[test]
1348 fn test_reset_without_seed() {
1349 let data = b"test";
1350
1351 let mut hasher = Koopman16::new();
1352 hasher.update(data);
1353 let first = hasher.finalize();
1354
1355 let mut hasher = Koopman16::new();
1356 hasher.update(b"other data");
1357 hasher.reset();
1358 hasher.update(data);
1359 let after_reset = hasher.finalize();
1360
1361 assert_eq!(first, after_reset);
1362 }
1363
1364 #[test]
1365 fn test_reset_preserves_seed() {
1366 let data = b"test";
1367 let seed = 42u8;
1368
1369 // First computation with seed
1370 let mut hasher = Koopman16::with_seed(seed);
1371 hasher.update(data);
1372 let first = hasher.finalize();
1373
1374 // Computation after reset should produce same result
1375 let mut hasher = Koopman16::with_seed(seed);
1376 hasher.update(b"garbage data");
1377 hasher.reset();
1378 hasher.update(data);
1379 let after_reset = hasher.finalize();
1380
1381 assert_eq!(first, after_reset);
1382 }
1383
1384 #[test]
1385 fn test_reset_all_variants() {
1386 let data = b"test";
1387
1388 // Koopman8
1389 let mut h8 = Koopman8::with_seed(10);
1390 h8.update(b"junk");
1391 h8.reset();
1392 h8.update(data);
1393 assert_eq!(h8.finalize(), koopman8(data, 10));
1394
1395 // Koopman16
1396 let mut h16 = Koopman16::with_seed(20);
1397 h16.update(b"junk");
1398 h16.reset();
1399 h16.update(data);
1400 assert_eq!(h16.finalize(), koopman16(data, 20));
1401
1402 // Koopman32
1403 let mut h32 = Koopman32::with_seed(30);
1404 h32.update(b"junk");
1405 h32.reset();
1406 h32.update(data);
1407 assert_eq!(h32.finalize(), koopman32(data, 30));
1408 }
1409
1410 // ========================================================================
1411 // Tests for two-bit error detection
1412 // ========================================================================
1413
1414 #[test]
1415 fn test_two_bit_error_detection() {
1416 // Test that most two-bit errors are detected
1417 // Note: HD=3 means we detect ALL 1-bit and 2-bit errors
1418 let data = b"Test";
1419 let original = koopman16(data, 0);
1420 let mut detected = 0;
1421 let mut total = 0;
1422
1423 for i in 0..data.len() {
1424 for j in i..data.len() {
1425 for bit_i in 0..8 {
1426 for bit_j in 0..8 {
1427 if i == j && bit_i == bit_j {
1428 continue; // Skip single-bit errors
1429 }
1430 total += 1;
1431 let mut corrupted = data.to_vec();
1432 corrupted[i] ^= 1 << bit_i;
1433 corrupted[j] ^= 1 << bit_j;
1434 if koopman16(&corrupted, 0) != original {
1435 detected += 1;
1436 }
1437 }
1438 }
1439 }
1440 }
1441
1442 // Should detect all two-bit errors for data within HD=3 length
1443 assert_eq!(detected, total, "Should detect all two-bit errors");
1444 }
1445
1446 // ========================================================================
1447 // Tests for streaming API edge cases
1448 // ========================================================================
1449
1450 #[test]
1451 fn test_streaming_empty_updates() {
1452 let data = b"test";
1453
1454 let mut hasher = Koopman16::new();
1455 hasher.update(&[]); // Empty update
1456 hasher.update(data);
1457 hasher.update(&[]); // Another empty update
1458
1459 assert_eq!(hasher.finalize(), koopman16(data, 0));
1460 }
1461
1462 #[test]
1463 fn test_streaming_byte_by_byte() {
1464 let data = b"test data";
1465
1466 let mut hasher = Koopman16::new();
1467 for &byte in data {
1468 hasher.update(&[byte]);
1469 }
1470
1471 assert_eq!(hasher.finalize(), koopman16(data, 0));
1472 }
1473
1474 #[test]
1475 fn test_finalize_without_data() {
1476 let hasher = Koopman16::new();
1477 assert_eq!(hasher.finalize(), 0);
1478
1479 let hasher_with_seed = Koopman16::with_seed(42);
1480 assert_eq!(hasher_with_seed.finalize(), 0);
1481 }
1482
1483 #[test]
1484 fn test_streaming_parity_koopman8p() {
1485 let data = b"test";
1486 let expected = koopman8p(data, 0);
1487
1488 let mut hasher = Koopman8P::new();
1489 hasher.update(&data[..2]);
1490 hasher.update(&data[2..]);
1491 let streaming = hasher.finalize();
1492
1493 assert_eq!(expected, streaming);
1494 }
1495
1496 #[test]
1497 fn test_streaming_parity_koopman16p() {
1498 let data = b"test data";
1499 let expected = koopman16p(data, 0);
1500
1501 let mut hasher = Koopman16P::new();
1502 hasher.update(&data[..4]);
1503 hasher.update(&data[4..]);
1504 let streaming = hasher.finalize();
1505
1506 assert_eq!(expected, streaming);
1507 }
1508
1509 #[test]
1510 fn test_streaming_parity_koopman32p() {
1511 let data = b"test data for streaming";
1512 let expected = koopman32p(data, 0);
1513
1514 let mut hasher = Koopman32P::new();
1515 hasher.update(&data[..10]);
1516 hasher.update(&data[10..]);
1517 let streaming = hasher.finalize();
1518
1519 assert_eq!(expected, streaming);
1520 }
1521
1522 #[test]
1523 fn test_streaming_parity_with_seed() {
1524 let data = b"test";
1525 let seed = 42u8;
1526
1527 let expected = koopman16p(data, seed);
1528
1529 let mut hasher = Koopman16P::with_seed(seed);
1530 hasher.update(data);
1531 let streaming = hasher.finalize();
1532
1533 assert_eq!(expected, streaming);
1534 }
1535
1536 // ========================================================================
1537 // Tests for parity verification
1538 // ========================================================================
1539
1540 #[test]
1541 fn test_verify_parity() {
1542 let data = b"test data";
1543
1544 let cs8p = koopman8p(data, 0);
1545 assert!(verify8p(data, cs8p, 0));
1546 assert!(!verify8p(data, cs8p.wrapping_add(1), 0));
1547
1548 let cs16p = koopman16p(data, 0);
1549 assert!(verify16p(data, cs16p, 0));
1550 assert!(!verify16p(data, cs16p.wrapping_add(1), 0));
1551
1552 let cs32p = koopman32p(data, 0);
1553 assert!(verify32p(data, cs32p, 0));
1554 assert!(!verify32p(data, cs32p.wrapping_add(1), 0));
1555 }
1556
1557 // ========================================================================
1558 // Tests for streaming with custom modulus
1559 // ========================================================================
1560
1561 #[test]
1562 fn test_streaming_with_custom_modulus() {
1563 let data = b"test data";
1564
1565 // Test that streaming with default modulus matches one-shot
1566 let mut hasher = Koopman16::with_modulus(NONZERO_MODULUS_16);
1567 hasher.update(data);
1568 assert_eq!(hasher.finalize(), koopman16(data, 0));
1569
1570 // Test with a different modulus
1571 let alt_modulus = NonZeroU32::new(32749).unwrap();
1572 let mut hasher = Koopman16::with_modulus(alt_modulus);
1573 hasher.update(data);
1574 let streaming = hasher.finalize();
1575
1576 // Should produce a valid result (just verify it's deterministic)
1577 let mut hasher2 = Koopman16::with_modulus(alt_modulus);
1578 hasher2.update(data);
1579 assert_eq!(streaming, hasher2.finalize());
1580 }
1581}