Skip to main content

nord_format/
crc.rs

1//! The container's two checksums, as slices and as streams.
2//!
3//! One per header generation: a type-1 file stores a CRC-32 (ISO-HDLC) of the body
4//! (which starts at `0x2c`) in the word at `0x18`; a type-0 file ends with a CRC-16
5//! (IBM-3740, a.k.a. CCITT-FALSE) over every byte before it, stored little-endian.
6
7use crcxx::crc16;
8use crcxx::crc32;
9
10const SLICES: usize = 16;
11
12const CRC_32: crc32::Crc<crc32::LookupTable256xN<SLICES>> =
13    crc32::Crc::<crc32::LookupTable256xN<SLICES>>::new(&crc32::catalog::CRC_32_ISO_HDLC);
14
15const CRC_16: crc16::Crc<crc16::LookupTable256xN<SLICES>> =
16    crc16::Crc::<crc16::LookupTable256xN<SLICES>>::new(&crc16::catalog::CRC_16_IBM_3740);
17
18/// CRC-32 (ISO-HDLC) of a contiguous slice — the type-1 body checksum.
19pub fn crc32(bytes: &[u8]) -> u32 {
20    CRC_32.compute(bytes)
21}
22
23/// CRC-16 (IBM-3740) of a contiguous slice — the type-0 whole-file checksum.
24///
25/// Inferred from specimens; not confirmed on hardware. Identified by matching
26/// the trailing two bytes of specimens from four families (`nspg`, `ne5p`,
27/// `nsmp` v2, `nsmp3`).
28pub fn crc16(bytes: &[u8]) -> u16 {
29    CRC_16.compute(bytes)
30}
31
32/// Streaming CRC-32, for bytes that arrive in pieces.
33pub struct Crc32Stream<'a>(crc32::ComputeMultipart<'a, crc32::LookupTable256xN<SLICES>>);
34
35impl Crc32Stream<'_> {
36    pub fn new() -> Crc32Stream<'static> {
37        Crc32Stream(CRC_32.compute_multipart())
38    }
39
40    pub fn update(&mut self, bytes: &[u8]) {
41        self.0.update(bytes);
42    }
43
44    pub fn value(&self) -> u32 {
45        self.0.value()
46    }
47}
48
49/// Streaming CRC-16, for bytes that arrive in pieces.
50pub struct Crc16Stream<'a>(crc16::ComputeMultipart<'a, crc16::LookupTable256xN<SLICES>>);
51
52impl Crc16Stream<'_> {
53    pub fn new() -> Crc16Stream<'static> {
54        Crc16Stream(CRC_16.compute_multipart())
55    }
56
57    pub fn update(&mut self, bytes: &[u8]) {
58        self.0.update(bytes);
59    }
60
61    pub fn value(&self) -> u16 {
62        self.0.value()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    /// The catalog check values: what each algorithm returns for `"123456789"`.
71    /// Pins the parameters (poly/init/reflect/xorout) against a swap to a
72    /// neighboring variant, which the container tests could miss.
73    #[test]
74    fn the_algorithms_are_the_cataloged_ones() {
75        assert_eq!(crc32(b"123456789"), 0xCBF4_3926, "not CRC-32/ISO-HDLC");
76        assert_eq!(crc16(b"123456789"), 0x29B1, "not CRC-16/IBM-3740");
77    }
78
79    /// A stream fed in pieces equals the slice computed whole.
80    #[test]
81    fn streams_match_slices() {
82        let data: Vec<u8> = (0u8..=255).cycle().take(1000).collect();
83
84        let mut s32 = Crc32Stream::new();
85        let mut s16 = Crc16Stream::new();
86        for chunk in data.chunks(7) {
87            s32.update(chunk);
88            s16.update(chunk);
89        }
90        assert_eq!(s32.value(), crc32(&data));
91        assert_eq!(s16.value(), crc16(&data));
92    }
93}