aede_core/audit/
crc.rs

1//! The three cyclic redundancy checks the audio containers carry.
2//!
3//! Each is defined by its polynomial and by whether the bits are reflected, and
4//! the three used here disagree on both counts — so they are written out rather
5//! than derived from one another.
6//!
7//! | Where | Width | Polynomial | Covers |
8//! |---|---|---|---|
9//! | FLAC frame header | 8 | `0x07` | the header, up to the byte before the CRC |
10//! | FLAC frame | 16 | `0x8005` | the whole frame, up to the two bytes before the CRC |
11//! | Ogg page | 32 | `0x04C11DB7` | the page, its own CRC field read as zero |
12//!
13//! Tables are built at first use rather than written out: 256 entries computed
14//! in a few microseconds are easier to trust than 256 constants copied by hand.
15
16use std::sync::OnceLock;
17
18/// CRC-8 as FLAC uses it: polynomial `0x07`, no reflection, zero seed.
19pub fn crc8(data: &[u8]) -> u8 {
20    static TABLE: OnceLock<[u8; 256]> = OnceLock::new();
21    let table = TABLE.get_or_init(|| {
22        let mut table = [0u8; 256];
23        for (index, entry) in table.iter_mut().enumerate() {
24            let mut value = index as u8;
25            for _ in 0..8 {
26                value = if value & 0x80 != 0 {
27                    (value << 1) ^ 0x07
28                } else {
29                    value << 1
30                };
31            }
32            *entry = value;
33        }
34        table
35    });
36
37    data.iter()
38        .fold(0u8, |crc, &byte| table[(crc ^ byte) as usize])
39}
40
41/// CRC-16 as FLAC uses it: polynomial `0x8005`, no reflection, zero seed.
42pub fn crc16(data: &[u8]) -> u16 {
43    static TABLE: OnceLock<[u16; 256]> = OnceLock::new();
44    let table = TABLE.get_or_init(|| {
45        let mut table = [0u16; 256];
46        for (index, entry) in table.iter_mut().enumerate() {
47            let mut value = (index as u16) << 8;
48            for _ in 0..8 {
49                value = if value & 0x8000 != 0 {
50                    (value << 1) ^ 0x8005
51                } else {
52                    value << 1
53                };
54            }
55            *entry = value;
56        }
57        table
58    });
59
60    data.iter().fold(0u16, |crc, &byte| {
61        (crc << 8) ^ table[((crc >> 8) as u8 ^ byte) as usize]
62    })
63}
64
65/// CRC-32 as Ogg uses it: polynomial `0x04C11DB7`, **no** reflection and no
66/// final inversion — which is what sets it apart from the CRC-32 of zip and
67/// PNG, and why a general-purpose implementation cannot be reused here.
68pub fn crc32_ogg(data: &[u8]) -> u32 {
69    crc32_ogg_bytes(data.iter().copied())
70}
71
72/// Computes a page checksum without copying or changing its checksum field.
73pub(crate) fn crc32_ogg_page(page: &[u8]) -> u32 {
74    crc32_ogg_bytes(page.iter().enumerate().map(
75        |(index, &byte)| {
76            if (22..26).contains(&index) { 0 } else { byte }
77        },
78    ))
79}
80
81fn crc32_ogg_bytes(data: impl Iterator<Item = u8>) -> u32 {
82    static TABLE: OnceLock<[u32; 256]> = OnceLock::new();
83    let table = TABLE.get_or_init(|| {
84        let mut table = [0u32; 256];
85        for (index, entry) in table.iter_mut().enumerate() {
86            let mut value = (index as u32) << 24;
87            for _ in 0..8 {
88                value = if value & 0x8000_0000 != 0 {
89                    (value << 1) ^ 0x04C1_1DB7
90                } else {
91                    value << 1
92                };
93            }
94            *entry = value;
95        }
96        table
97    });
98
99    data.fold(0u32, |crc, byte| {
100        (crc << 8) ^ table[((crc >> 24) as u8 ^ byte) as usize]
101    })
102}
103
104#[cfg(test)]
105#[path = "crc_tests.rs"]
106mod tests;