Skip to main content

delsum_lib/crc/
mod.rs

1mod rev;
2use super::{
3    CheckBuilderErr, Digest, LinearCheck,
4    endian::{Endian, WordSpec},
5};
6use crate::{bitnum::BitNum, checksum::parse_hex, endian::Signedness};
7use crate::{checksum::Checksum, endian::SignedInt, keyval::KeyValIter};
8pub use rev::reverse_crc;
9#[cfg(feature = "parallel")]
10pub use rev::reverse_crc_para;
11use std::fmt::Display;
12use std::str::FromStr;
13/// A builder for a CRC algorithm.
14///
15/// The Sum type is one of u8, u16, u32, u64 or u128 and must be able to hold `width` bits.
16///
17/// The parameters are the same as of the Rocksoft^TM Model CRC Algorithm:
18/// * `width`: the width in bits of the sum values
19/// * `poly`: the generator polynomial (without highest bit)
20/// * `init`: the initial value for the checksum
21/// * `refin`: whether to reflect the input bytes
22/// * `refout`: whether to reflect the sum
23/// * `xorout`: what to XOR the output with
24/// * `check`: the checksum of the ASCII string "123456789" (is checked on `build()`, optional)
25/// * `name`: an optional name for the algorithm
26///
27/// For more information on the parameters (and CRCs in general), see "A PAINLESS GUIDE CRC ERROR DETECTION ALGORITHMS"
28/// or https://reveng.sourceforge.io/crc-catalogue/legend.htm (which is also a source of parameters for various common algorithms)
29#[derive(Clone, Debug)]
30pub struct CrcBuilder<Sum: BitNum> {
31    width: Option<usize>,
32    poly: Option<Sum>,
33    init: Option<Sum>,
34    xorout: Option<Sum>,
35    refin: Option<bool>,
36    refout: Option<bool>,
37    input_endian: Option<Endian>,
38    output_endian: Option<Endian>,
39    wordsize: Option<usize>,
40    check: Option<Sum>,
41    name: Option<String>,
42}
43
44impl<Sum: BitNum> CrcBuilder<Sum> {
45    /// Sets the poly, mandatory
46    pub fn poly(&mut self, p: Sum) -> &mut Self {
47        self.poly = Some(p);
48        self
49    }
50    /// Sets the width, mandatory
51    pub fn width(&mut self, w: usize) -> &mut Self {
52        self.width = Some(w);
53        self
54    }
55    /// Sets the `init` parameter, default is 0.
56    pub fn init(&mut self, s: Sum) -> &mut Self {
57        self.init = Some(s);
58        self
59    }
60    /// Sets the `xorout` parameter, default is 0.
61    pub fn xorout(&mut self, s: Sum) -> &mut Self {
62        self.xorout = Some(s);
63        self
64    }
65    /// Sets the `refin` parameter, default is false.
66    pub fn refin(&mut self, i: bool) -> &mut Self {
67        self.refin = Some(i);
68        self
69    }
70    /// Sets the `refout` parameter, default is false.
71    pub fn refout(&mut self, o: bool) -> &mut Self {
72        self.refout = Some(o);
73        self
74    }
75    /// The endian of the words of the input file
76    pub fn inendian(&mut self, e: Endian) -> &mut Self {
77        self.input_endian = Some(e);
78        self
79    }
80    /// The number of bits in a word of the input file
81    pub fn wordsize(&mut self, n: usize) -> &mut Self {
82        self.wordsize = Some(n);
83        self
84    }
85    /// The endian of the checksum
86    pub fn outendian(&mut self, e: Endian) -> &mut Self {
87        self.output_endian = Some(e);
88        self
89    }
90    /// Sets the `check` parameter, no check is done if this is left out.
91    pub fn check(&mut self, c: Sum) -> &mut Self {
92        self.check = Some(c);
93        self
94    }
95    /// Sets the name, no name is set otherwise.
96    pub fn name(&mut self, s: &str) -> &mut Self {
97        self.name = Some(s.to_owned());
98        self
99    }
100    /// Build the object for the algorithm, generating the lookup table and verifying that
101    /// the parameters are valid.
102    pub fn build(&self) -> Result<CRC<Sum>, CheckBuilderErr> {
103        let width = match self.width {
104            None => return Err(CheckBuilderErr::MissingParameter("width")),
105            Some(w) => w,
106        };
107        if width > 128 {
108            return Err(CheckBuilderErr::ValueOutOfRange("width"));
109        }
110        let mask = Sum::one() << (width - 1);
111        let mask = mask ^ (mask - Sum::one());
112        let poly = match self.poly {
113            None => return Err(CheckBuilderErr::MissingParameter("poly")),
114            Some(p) => p,
115        };
116        let init = self.init.unwrap_or_else(Sum::zero);
117        let xorout = self.xorout.unwrap_or_else(Sum::zero);
118        let refout = self.refout.unwrap_or(false);
119        // the type needs at least 8 bit so that we can comfortably add bytes to it
120        // (i guess it is kind of already impliead by the from<u8> trait)
121        if poly.bits() < width || poly.bits() < 8 {
122            return Err(CheckBuilderErr::ValueOutOfRange("width"));
123        }
124        // the generator polynomial needs to have the lowest bit set in order to be useful
125        if poly & Sum::one() != Sum::one() {
126            return Err(CheckBuilderErr::ValueOutOfRange("poly"));
127        }
128        if poly & !mask != Sum::zero() {
129            return Err(CheckBuilderErr::ValueOutOfRange("poly"));
130        }
131        if init & !mask != Sum::zero() {
132            return Err(CheckBuilderErr::ValueOutOfRange("init"));
133        }
134        if xorout & !mask != Sum::zero() {
135            return Err(CheckBuilderErr::ValueOutOfRange("xorout"));
136        }
137        let wordsize = self.wordsize.unwrap_or(8);
138        if wordsize == 0 || wordsize % 8 != 0 || wordsize > 64 {
139            return Err(CheckBuilderErr::ValueOutOfRange("wordsize"));
140        }
141        let refin = self.refin.unwrap_or(false);
142        let input_endian = self.input_endian.unwrap_or(Endian::Big);
143        let wordspec = WordSpec {
144            input_endian,
145            wordsize,
146            output_endian: self.output_endian.unwrap_or(Endian::Big),
147            signedness: Signedness::Unsigned,
148        };
149        let crc = CRC {
150            width,
151            poly,
152            init,
153            xorout,
154            refin,
155            refout,
156            wordspec,
157            mask,
158            name: self.name.clone(),
159            table: CRC::<Sum>::generate_crc_table(poly, width),
160        };
161        match self.check {
162            Some(chk) => {
163                if chk & !mask != Sum::zero() {
164                    Err(CheckBuilderErr::ValueOutOfRange("check"))
165                } else if crc.digest(&b"123456789"[..]).unwrap() != chk {
166                    Err(CheckBuilderErr::CheckFail)
167                } else {
168                    Ok(crc)
169                }
170            }
171            None => Ok(crc),
172        }
173    }
174}
175
176#[derive(PartialEq, Eq)]
177pub struct CRC<Sum: BitNum> {
178    init: Sum,
179    xorout: Sum,
180    refin: bool,
181    refout: bool,
182    poly: Sum,
183    wordspec: WordSpec,
184    mask: Sum,
185    width: usize,
186    name: Option<String>,
187    table: Box<[Sum; 256]>,
188}
189
190impl<Sum: BitNum> Display for CRC<Sum> {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        match &self.name {
193            Some(n) => write!(f, "{}", n),
194            None => {
195                write!(
196                    f,
197                    "crc width={} poly={:#x} init={:#x} xorout={:#x} refin={} refout={}",
198                    self.width, self.poly, self.init, self.xorout, self.refin, self.refout
199                )?;
200                if self.wordspec.word_bytes() != 1 || self.wordspec.input_endian != Endian::Big {
201                    write!(
202                        f,
203                        " wordsize={} in_endian={}",
204                        self.wordspec.wordsize, self.wordspec.input_endian
205                    )?;
206                };
207                if self.width > 8 {
208                    write!(f, " out_endian={}", self.wordspec.output_endian)?;
209                };
210                Ok(())
211            }
212        }
213    }
214}
215
216impl<Sum: BitNum> CRC<Sum> {
217    /// Construct a new CRC from parameters, see also the documentation for CRCBuilder.
218    pub fn with_options() -> CrcBuilder<Sum> {
219        CrcBuilder {
220            poly: None,
221            init: None,
222            xorout: None,
223            width: None,
224            refin: None,
225            refout: None,
226            input_endian: None,
227            output_endian: None,
228            wordsize: None,
229            check: None,
230            name: None,
231        }
232    }
233    /// Construct the CRC table, given the bitwidth and the generator.
234    /// Just your typical crc table generator implementation, nothing to see here.
235    fn generate_crc_table(generator: Sum, width: usize) -> Box<[Sum; 256]> {
236        let mut table = Box::new([Sum::zero(); 256]);
237        let mut crc = Sum::one() << (width - 1);
238        for i in 0..8 {
239            let addition = if Sum::zero() != crc & Sum::one() << (width - 1) {
240                // prevent overflow
241                crc = crc ^ Sum::one() << (width - 1);
242                generator
243            } else {
244                Sum::zero()
245            };
246            crc = (crc << 1usize) ^ addition;
247            let validrange = 1usize << i;
248            for x in 0..validrange {
249                table[validrange + x] = table[x] ^ crc;
250            }
251        }
252        table
253    }
254    /// Get the CRC lookup table entry indexed by x.
255    fn get_table_entry(&self, x: Sum) -> Sum {
256        let index: u8 = match x.try_into() {
257            Ok(byte) => byte,
258            Err(_) => panic!("Internal error: non-byte index into CRC lookup table"),
259        };
260        self.table[index as usize]
261    }
262    /// Reflects `sum` if `refout` is set.
263    fn regularize(&self, sum: Sum) -> Sum {
264        if self.refout {
265            sum.revbits() >> (sum.bits() - self.width)
266        } else {
267            sum
268        }
269    }
270}
271impl<Sum: BitNum> FromStr for CrcBuilder<Sum> {
272    /// See documentation of Fromstr on CRC<Sum>
273    fn from_str(s: &str) -> Result<CrcBuilder<Sum>, CheckBuilderErr> {
274        let mut crc = CRC::<Sum>::with_options();
275        for x in KeyValIter::new(s) {
276            let (current_key, current_val) = match x {
277                Err(key) => return Err(CheckBuilderErr::MalformedString(key)),
278                Ok(s) => s,
279            };
280            let crc_op = match current_key.as_str() {
281                // I would love to return a ValueOutOfRange error here, but I don't know how
282                // I would go about it
283                "width" => usize::from_str(&current_val).ok().map(|x| crc.width(x)),
284                "poly" => Some(crc.poly(parse_hex::<Sum>(&current_val, "poly")?)),
285                "init" => Some(crc.init(parse_hex::<Sum>(&current_val, "init")?)),
286                "xorout" => Some(crc.xorout(parse_hex::<Sum>(&current_val, "xorout")?)),
287                "refin" => bool::from_str(&current_val).ok().map(|x| crc.refin(x)),
288                "refout" => bool::from_str(&current_val).ok().map(|x| crc.refout(x)),
289                "in_endian" => Endian::from_str(&current_val).ok().map(|x| crc.inendian(x)),
290                "wordsize" => usize::from_str(&current_val).ok().map(|x| crc.wordsize(x)),
291                "out_endian" => Endian::from_str(&current_val)
292                    .ok()
293                    .map(|x| crc.outendian(x)),
294                "residue" => Some(&mut crc),
295                "check" => Some(crc.check(parse_hex::<Sum>(&current_val, "check")?)),
296                "name" => Some(crc.name(&current_val)),
297                _ => return Err(CheckBuilderErr::UnknownKey(current_key)),
298            };
299            match crc_op {
300                Some(c) => crc = c.clone(),
301                None => return Err(CheckBuilderErr::MalformedString(current_key)),
302            }
303        }
304        Ok(crc)
305    }
306    type Err = CheckBuilderErr;
307}
308
309impl<Sum: BitNum> FromStr for CRC<Sum> {
310    /// Construct a new CRC from a string specification.
311    ///
312    /// Example (courtesy of the crc reveng catalogue):
313    ///
314    /// width=16 poly=0x8005 init=0x0000 refin=true refout=true xorout=0x0000 check=0xbb3d residue=0x0000 name="CRC-16/ARC"
315    ///
316    /// Note: the `residue` parameter is currently ignored.
317    fn from_str(s: &str) -> Result<CRC<Sum>, CheckBuilderErr> {
318        CrcBuilder::<Sum>::from_str(s)?.build()
319    }
320    type Err = CheckBuilderErr;
321}
322
323impl<S: BitNum> Digest for CRC<S> {
324    type Sum = S;
325    fn init(&self) -> Self::Sum {
326        // note: if refout is set, the sum value is always left in reflected form,
327        // because it is needed for the linearity conditions of LinearCheck.
328        self.regularize(self.init)
329    }
330    fn dig_word(&self, sum: Self::Sum, word: SignedInt<u64>) -> Self::Sum {
331        // sum is reflected both at beginning and end to do operations on it in unreflected state
332        // (this could be prevented by implementing a proper implementation for the reflected case)
333        let mut refsum = self.regularize(sum);
334        let inword = if self.refin {
335            word.value.reverse_bits() >> (64 - self.wordspec.wordsize)
336        } else {
337            word.value
338        };
339        for x in (0..self.wordspec.word_bytes()).rev() {
340            let inbyte = (inword >> (x * 8)) as u8;
341            refsum = if self.width <= 8 {
342                // if the width is less than 8, we have to be careful not to do negative shift values
343                let overhang = (refsum << (8 - self.width)) ^ S::from(inbyte);
344                self.get_table_entry(overhang)
345            } else {
346                // your typical CRC reduction implemented through CRC lookup table
347                let overhang = refsum >> (self.width - 8) ^ S::from(inbyte);
348                let l_remain = (refsum << 8) & self.mask;
349                self.get_table_entry(overhang) ^ l_remain
350            }
351        }
352        self.regularize(refsum)
353    }
354    fn finalize(&self, sum: Self::Sum) -> Self::Sum {
355        sum ^ self.xorout
356    }
357
358    fn to_bytes(&self, s: Self::Sum) -> Vec<u8> {
359        self.wordspec.output_to_bytes(s, self.width)
360    }
361
362    fn checksum_from_bytes(&self, bytes: &[u8]) -> Option<Self::Sum> {
363        Checksum::from_bytes(bytes, self.wordspec.output_endian, self.width)
364    }
365
366    fn wordspec(&self) -> WordSpec {
367        self.wordspec
368    }
369}
370
371impl<S: BitNum> LinearCheck for CRC<S> {
372    type Shift = S;
373    fn shift(&self, sum: Self::Sum, shift: &Self::Shift) -> Self::Sum {
374        let sum = self.regularize(sum);
375        // note: this is just a carry-less multiply modulo the generator polynomial
376        // we keep a lo_part and a hi_part because we don't know the type double the size
377        let mut lo_part = if *shift & Self::Shift::one() != Self::Shift::zero() {
378            sum
379        } else {
380            Self::Sum::zero()
381        };
382        let mut hi_part = Self::Sum::zero();
383        for i in 1..self.width {
384            if *shift >> i & Self::Shift::one() != Self::Shift::zero() {
385                lo_part = lo_part ^ sum << i;
386                hi_part = hi_part ^ sum >> (self.width - i);
387            }
388        }
389        lo_part = lo_part & self.mask;
390
391        // we reduce by generator polynomial bytewise through lookup table
392        let mut bits_left = self.width;
393        while bits_left > 0 {
394            let shift_amount = bits_left.min(8);
395            bits_left -= shift_amount;
396            let new_part = self.get_table_entry(hi_part >> (self.width - shift_amount));
397            // be careful not to overshoot
398            if shift_amount >= hi_part.bits() {
399                hi_part = new_part
400            } else {
401                hi_part = (hi_part << shift_amount) & self.mask ^ new_part;
402            }
403        }
404        self.regularize(hi_part ^ lo_part)
405    }
406    fn add(&self, sum_a: Self::Sum, sum_b: &Self::Sum) -> Self::Sum {
407        sum_a ^ *sum_b
408    }
409    fn negate(&self, sum: Self::Sum) -> Self::Sum {
410        // laughs in characteristic 2
411        sum
412    }
413    fn init_shift(&self) -> Self::Shift {
414        Self::Shift::one()
415    }
416    fn inc_shift(&self, mut shift: Self::Shift) -> Self::Shift {
417        // note: shifts are always unreflected
418        for _ in 0..self.wordspec.word_bytes() {
419            shift = if self.width <= 8 {
420                let overhang = shift << (8 - self.width);
421                self.get_table_entry(overhang)
422            } else {
423                let overhang = shift >> (self.width - 8);
424                let l_remain = (shift << 8) & self.mask;
425                self.get_table_entry(overhang) ^ l_remain
426            }
427        }
428        shift
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435    use crate::checksum::tests::{check_example, test_find, test_prop, test_shifts};
436    #[test]
437    fn cms_16() {
438        assert!(
439            CRC::<u32>::with_options()
440                .poly(0x8005)
441                .width(16)
442                .init(0xffff)
443                .check(0xaee7)
444                .build()
445                .is_ok()
446        );
447        let crc = CRC::<u16>::with_options()
448            .poly(0x8005)
449            .width(16)
450            .init(0xffff)
451            .check(0xaee7)
452            .build()
453            .unwrap();
454        test_shifts(&crc);
455        test_find(&crc);
456        test_prop(&crc);
457        check_example(&crc, 0x6bd6);
458    }
459    #[test]
460    fn gsm_3() {
461        let crc = CRC::<u8>::with_options()
462            .poly(0x3)
463            .width(3)
464            .xorout(0x7)
465            .check(0x4)
466            .build()
467            .unwrap();
468        test_shifts(&crc);
469        test_prop(&crc);
470        check_example(&crc, 7);
471        let crc = CRC::<u128>::with_options()
472            .poly(0x3)
473            .width(3)
474            .xorout(0x7)
475            .check(0x4)
476            .build()
477            .unwrap();
478        test_shifts(&crc);
479        test_prop(&crc);
480        check_example(&crc, 7);
481    }
482    #[test]
483    fn rohc_7() {
484        let crc = CRC::<u8>::with_options()
485            .poly(0x4f)
486            .width(7)
487            .init(0x7f)
488            .refin(true)
489            .refout(true)
490            .check(0x53)
491            .build()
492            .unwrap();
493        test_shifts(&crc);
494        test_prop(&crc);
495        check_example(&crc, 0x25);
496        let crc = CRC::<u32>::with_options()
497            .poly(0x4f)
498            .width(7)
499            .init(0x7f)
500            .refin(true)
501            .refout(true)
502            .check(0x53)
503            .build()
504            .unwrap();
505        test_shifts(&crc);
506        test_prop(&crc);
507        check_example(&crc, 0x25);
508    }
509    #[test]
510    fn usb_5() {
511        let crc = CRC::<u8>::with_options()
512            .poly(0x05)
513            .width(5)
514            .init(0x1f)
515            .refin(true)
516            .refout(true)
517            .xorout(0x1f)
518            .check(0x19)
519            .build()
520            .unwrap();
521        test_shifts(&crc);
522        test_prop(&crc);
523        check_example(&crc, 0x17);
524    }
525    #[test]
526    fn umts_12() {
527        let crc = CRC::<u16>::with_options()
528            .poly(0x80f)
529            .width(12)
530            .refout(true)
531            .check(0xdaf)
532            .build()
533            .unwrap();
534        test_shifts(&crc);
535        test_find(&crc);
536        test_prop(&crc);
537        check_example(&crc, 0x35a);
538    }
539    #[test]
540    fn en13757_16() {
541        let crc = CRC::<u16>::with_options()
542            .poly(0x3d65)
543            .width(16)
544            .xorout(0xffff)
545            .check(0xc2b7)
546            .build()
547            .unwrap();
548        test_shifts(&crc);
549        test_find(&crc);
550        test_prop(&crc);
551        check_example(&crc, 0x69e2);
552    }
553    #[test]
554    fn mpt1327_15() {
555        let crc = CRC::<u16>::with_options()
556            .poly(0x6815)
557            .width(15)
558            .xorout(0x0001)
559            .check(0x2566)
560            .build()
561            .unwrap();
562        test_shifts(&crc);
563        test_find(&crc);
564        test_prop(&crc);
565        check_example(&crc, 0x1993);
566    }
567    #[test]
568    fn canfd_17() {
569        let crc = CRC::<u32>::with_options()
570            .poly(0x1685b)
571            .width(17)
572            .check(0x04f03)
573            .build()
574            .unwrap();
575        test_shifts(&crc);
576        test_find(&crc);
577        test_prop(&crc);
578        check_example(&crc, 0x00f396);
579    }
580    #[test]
581    fn bzip2_32() {
582        let crc = CRC::<u32>::with_options()
583            .poly(0x04c11db7)
584            .width(32)
585            .init(0xffffffff)
586            .xorout(0xffffffff)
587            .check(0xfc891918)
588            .build()
589            .unwrap();
590        test_shifts(&crc);
591        test_find(&crc);
592        test_prop(&crc);
593        check_example(&crc, 0xe8c5033d);
594    }
595    #[test]
596    fn iscsi_32() {
597        let crc = CRC::<u32>::with_options()
598            .poly(0x1edc6f41)
599            .width(32)
600            .init(0xffffffff)
601            .xorout(0xffffffff)
602            .refin(true)
603            .refout(true)
604            .check(0xe3069283)
605            .build()
606            .unwrap();
607        test_shifts(&crc);
608        test_find(&crc);
609        test_prop(&crc);
610        check_example(&crc, 0x5a513507);
611    }
612    #[test]
613    fn gsm_40() {
614        let crc = CRC::<u64>::with_options()
615            .poly(0x0004820009)
616            .width(40)
617            .xorout(0xffffffffff)
618            .check(0xd4164fc646)
619            .build()
620            .unwrap();
621        test_shifts(&crc);
622        test_find(&crc);
623        test_prop(&crc);
624        check_example(&crc, 0x4165335176)
625    }
626    #[test]
627    fn xz_64() {
628        let crc = CRC::<u64>::with_options()
629            .poly(0x42f0e1eba9ea3693)
630            .width(64)
631            .init(0xffffffffffffffff)
632            .refin(true)
633            .refout(true)
634            .xorout(0xffffffffffffffff)
635            .check(0x995dc9bbdf1939fa)
636            .build()
637            .unwrap();
638        test_shifts(&crc);
639        test_find(&crc);
640        test_prop(&crc);
641        check_example(&crc, 0xb03d0f148fcab729);
642    }
643    #[test]
644    fn darc_82() {
645        let crc = CRC::<u128>::with_options()
646            .poly(0x0308c0111011401440411)
647            .width(82)
648            .refin(true)
649            .refout(true)
650            .check(0x09ea83f625023801fd612)
651            .build()
652            .unwrap();
653        test_shifts(&crc);
654        test_find(&crc);
655        test_prop(&crc);
656        check_example(&crc, 0x030c57c0142280dfd62847)
657    }
658    #[test]
659    fn parity_1() {
660        let crc = CRC::<u8>::with_options()
661            .poly(1)
662            .width(1)
663            .check(1)
664            .build()
665            .unwrap();
666        test_shifts(&crc);
667        test_prop(&crc);
668        check_example(&crc, 0);
669    }
670    #[test]
671    fn i4321_8() {
672        let crc = CRC::<u8>::with_options()
673            .poly(0x7)
674            .width(8)
675            .xorout(0x55)
676            .check(0xa1)
677            .build()
678            .unwrap();
679        test_shifts(&crc);
680        test_prop(&crc);
681        check_example(&crc, 0x96);
682    }
683    #[test]
684    fn isoiec144433a_16() {
685        let crc = CRC::<u16>::with_options()
686            .poly(0x1021)
687            .width(16)
688            .init(0xc6c6u16)
689            .refin(true)
690            .refout(true)
691            .check(0xbf05)
692            .build()
693            .unwrap();
694        test_shifts(&crc);
695        test_find(&crc);
696        test_prop(&crc);
697        check_example(&crc, 0x6b68);
698    }
699    #[test]
700    fn arc_16() {
701        let crc = CRC::<u16>::from_str("width=16 poly=0x8005 init=0x0000 refin=true refout=true xorout=0x0000 check=0xbb3d residue=0x0000 name=\"CRC-16/ARC\"")
702            .unwrap();
703        test_shifts(&crc);
704        test_find(&crc);
705        test_prop(&crc);
706        check_example(&crc, 0xf15e);
707    }
708    #[test]
709    fn something_16() {
710        let crc = CRC::<u16>::from_str("init=0x5ff\npoly=0x4465     width=15").unwrap();
711        test_shifts(&crc);
712        test_find(&crc);
713        test_prop(&crc);
714        check_example(&crc, 0x2cfa);
715        assert!(CRC::<u16>::from_str("init=0x5ff\npoly=0x4465     width=\"15").is_err());
716        CRC::<u16>::from_str("  init=0x533\n\t\npoly=0x4465     width=\"15\"   ").unwrap();
717    }
718}