1use comnum_core::{Error, Result, ensure};
35use comnum_math::gf2::poly_rem;
36
37#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct Crc {
41 name: &'static str,
42 width: u32,
43 poly: u64,
44}
45
46impl Crc {
47 pub fn new(name: &'static str, width: u32, poly: u64) -> Result<Crc> {
52 ensure((1..=64).contains(&width), || format!("the CRC degree must be 1 to 64, got {width}"))?;
53 ensure(width == 64 || poly < (1u64 << width), || {
54 format!("the generator {poly:#x} exceeds the degree {width}")
55 })?;
56 Ok(Crc { name, width, poly })
57 }
58
59 pub fn name(&self) -> &'static str {
61 self.name
62 }
63
64 pub fn width(&self) -> u32 {
66 self.width
67 }
68
69 pub fn poly(&self) -> u64 {
71 self.poly
72 }
73
74 pub fn crc24a() -> Crc {
76 Crc { name: "CRC24A", width: 24, poly: 0x86_4CFB }
77 }
78
79 pub fn crc24b() -> Crc {
81 Crc { name: "CRC24B", width: 24, poly: 0x80_0063 }
82 }
83
84 pub fn crc24c() -> Crc {
86 Crc { name: "CRC24C", width: 24, poly: 0xB2_B117 }
87 }
88
89 pub fn crc16() -> Crc {
91 Crc { name: "CRC16", width: 16, poly: 0x1021 }
92 }
93
94 pub fn crc11() -> Crc {
96 Crc { name: "CRC11", width: 11, poly: 0x621 }
97 }
98
99 pub fn crc6() -> Crc {
101 Crc { name: "CRC6", width: 6, poly: 0x21 }
102 }
103
104 pub fn from_name(name: &str) -> Result<Crc> {
110 Ok(match name.to_ascii_lowercase().as_str() {
111 "crc24a" => Crc::crc24a(),
112 "crc24b" => Crc::crc24b(),
113 "crc24c" => Crc::crc24c(),
114 "crc16" => Crc::crc16(),
115 "crc11" => Crc::crc11(),
116 "crc6" => Crc::crc6(),
117 other => {
118 return Err(Error::Config(format!(
119 "unknown CRC {other:?}: crc24a, crc24b, crc24c, crc16, crc11 or crc6"
120 )));
121 }
122 })
123 }
124
125 pub fn checksum(&self, bits: &[u8]) -> Vec<u8> {
128 poly_rem(bits, &self.generator())
129 }
130
131 fn generator(&self) -> Vec<u8> {
133 (0..self.width).map(|i| ((self.poly >> i) & 1) as u8).chain([1]).collect()
134 }
135
136 pub fn attach(&self, bits: &[u8]) -> Vec<u8> {
138 let mut out = bits.to_vec();
139 out.extend(self.checksum(bits));
140 out
141 }
142
143 pub fn check(&self, word: &[u8]) -> bool {
145 let w = self.width as usize;
146 word.len() >= w && self.checksum(&word[..word.len() - w]) == word[word.len() - w..]
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 fn former_checksum(crc: &Crc, bits: &[u8]) -> Vec<u8> {
156 let top = 1u64 << (crc.width - 1);
157 let mask = (top << 1).wrapping_sub(1);
158 let mut reg = 0u64;
159 for &b in bits {
160 let fb = ((reg & top) != 0) ^ (b & 1 == 1);
161 reg = (reg << 1) & mask;
162 if fb {
163 reg ^= crc.poly;
164 }
165 }
166 (0..crc.width).rev().map(|i| ((reg >> i) & 1) as u8).collect()
167 }
168
169 #[test]
170 fn the_checksums_are_those_of_the_former_register() {
171 let mut rng = comnum_core::rng::Rng::new(9);
172 let mut crcs = vec![Crc::crc24a(), Crc::crc24b(), Crc::crc24c(), Crc::crc16(), Crc::crc11(), Crc::crc6()];
173 crcs.extend([Crc::new("one", 1, 1).unwrap(), Crc::new("wide", 64, 0x1B).unwrap()]);
174 for crc in &crcs {
175 for len in [0, 1, 12, 300] {
176 let bits: Vec<u8> = (0..len).map(|_| rng.bit()).collect();
177 assert_eq!(crc.checksum(&bits), former_checksum(crc, &bits), "{} on {len} bits", crc.name);
178 }
179 }
180 }
181
182 #[test]
183 fn the_remainder_of_a_codeword_is_zero_and_every_single_error_is_detected() {
184 for crc in [Crc::crc24a(), Crc::crc24b(), Crc::crc24c(), Crc::crc16(), Crc::crc11(), Crc::crc6()] {
185 let word = crc.attach(&[1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0]);
186 assert!(crc.checksum(&word).iter().all(|b| *b == 0), "{}", crc.name);
188 for i in 0..word.len() {
189 let mut e = word.clone();
190 e[i] ^= 1;
191 assert!(!crc.check(&e), "{} misses an error at {i}", crc.name);
192 }
193 }
194 let msg: Vec<u8> = b"123456789".iter().flat_map(|c| (0..8).rev().map(move |i| (c >> i) & 1)).collect();
196 let v = Crc::crc16().checksum(&msg).iter().fold(0u32, |a, b| (a << 1) | u32::from(*b));
197 assert_eq!(v, 0x31C3);
198 for w in [1, 64] {
200 let c = Crc::new("edge", w, 1).unwrap();
201 assert!(c.check(&c.attach(&[1, 0, 1])));
202 }
203 assert!(Crc::new("bad", 65, 1).is_err() && Crc::new("bad", 4, 16).is_err());
204 assert_eq!(Crc::from_name("CRC24A").unwrap(), Crc::crc24a());
205 }
206}
207
208#[cfg(test)]
210mod literature {
211 use super::*;
212
213 #[test]
216 fn ts_38_212_sec_5_1_generator_polynomials() {
217 let exponents = |crc: &Crc| -> Vec<u32> {
218 let mut e: Vec<u32> = (0..crc.width).filter(|i| (crc.poly >> i) & 1 == 1).collect();
219 e.push(crc.width);
220 e.sort_unstable_by(|a, b| b.cmp(a));
221 e
222 };
223 assert_eq!(exponents(&Crc::crc24a()), [24, 23, 18, 17, 14, 11, 10, 7, 6, 5, 4, 3, 1, 0]);
224 assert_eq!(exponents(&Crc::crc24b()), [24, 23, 6, 5, 1, 0]);
225 assert_eq!(exponents(&Crc::crc24c()), [24, 23, 21, 20, 17, 15, 13, 12, 8, 4, 2, 1, 0]);
226 assert_eq!(exponents(&Crc::crc16()), [16, 12, 5, 0]);
227 assert_eq!(exponents(&Crc::crc11()), [11, 10, 9, 5, 0]);
228 assert_eq!(exponents(&Crc::crc6()), [6, 5, 0]);
229 }
230}