Skip to main content

delsum_lib/polyhash/
rev.rs

1#[cfg(feature = "parallel")]
2use rayon::prelude::*;
3
4use crate::{
5    checksum::{CheckReverserError, Checksum, filter_opt_err},
6    endian::{Signedness, WordSpec, wordspec_combos},
7    utils::unresult_iter,
8};
9
10use super::{PolyHash, PolyHashBuilder};
11
12pub fn reverse_polyhash<'a>(
13    spec: &PolyHashBuilder<u64>,
14    chk_bytes: &[(&'a [u8], Vec<u8>)],
15    _verbosity: u64,
16    extended_search: bool,
17) -> impl Iterator<Item = Result<PolyHash<u64>, CheckReverserError>> + use<'a> {
18    let width = spec.width.unwrap();
19    let chk_bytes = chk_bytes.to_vec();
20    let combos = wordspec_combos(
21        spec.wordsize,
22        spec.input_endian,
23        spec.output_endian,
24        Some(Signedness::Unsigned),
25        width,
26        extended_search,
27    );
28    let factor = spec.factor;
29    let init = spec.init;
30    let sign = spec.signedness;
31    let addout = spec.addout;
32    filter_opt_err(combos.into_iter().flat_map(move |wordspec| {
33        unresult_iter(reverse(
34            width, &chk_bytes, factor, init, addout, sign, wordspec,
35        ))
36    }))
37}
38
39#[cfg(feature = "parallel")]
40pub fn reverse_polyhash_para<'a>(
41    spec: &PolyHashBuilder<u64>,
42    chk_bytes: &[(&'a [u8], Vec<u8>)],
43    _verbosity: u64,
44    extended_search: bool,
45) -> impl ParallelIterator<Item = Result<PolyHash<u64>, CheckReverserError>> + use<'a> {
46    let width = spec.width.unwrap();
47    let chk_bytes = chk_bytes.to_vec();
48    let combos = wordspec_combos(
49        spec.wordsize,
50        spec.input_endian,
51        spec.output_endian,
52        Some(Signedness::Unsigned),
53        width,
54        extended_search,
55    );
56    let factor = spec.factor;
57    let init = spec.init;
58    let sign = spec.signedness;
59    let addout = spec.addout;
60    combos.into_par_iter().flat_map(move |wordspec| {
61        filter_opt_err(unresult_iter(reverse(
62            width, &chk_bytes, factor, init, addout, sign, wordspec,
63        )))
64        .par_bridge()
65    })
66}
67
68// The way the init/addout parameters are cancelled out in the case of polyhash
69// is similar to how they are cancelled out in the other modules.
70// However, here we simply lift the solution to higher moduli 2^i iteratively
71// since we do not have to find the modulus itself (it is already given as 2^width).
72//
73// This also allows us to calculate our polynomials modulo so-called zero-polynomials
74// that are zero on every point. This makes our polynomials radically shorter (think degree
75// 32 or 64), without changing the result.
76fn reverse<'a>(
77    width: usize,
78    chk_bytes: &[(&'a [u8], Vec<u8>)],
79    factor: Option<u64>,
80    init: Option<u64>,
81    addout: Option<u64>,
82    sign: Option<Signedness>,
83    wordspec: WordSpec,
84) -> Result<impl Iterator<Item = PolyHash<u64>> + use<'a>, Option<CheckReverserError>> {
85    let Some(mut polys): Option<Vec<_>> = chk_bytes
86        .iter()
87        .map(|x| poly_from_data(width, &wordspec, x))
88        .collect()
89    else {
90        return Err(None);
91    };
92
93    polys.sort();
94    polys.dedup();
95
96    if let Some(value) = check_params(chk_bytes, factor, init, addout) {
97        return Err(Some(value));
98    }
99
100    let (filtered, addout_info) = filter_addouts(polys, addout);
101    let (filtered, init_info) = filter_inits(filtered, init, width);
102
103    let revspec = RevSpec {
104        polys: filtered,
105        width,
106        init: init_info,
107        addout: addout_info,
108        factor,
109        sign,
110        wordsize: wordspec.wordsize,
111    };
112
113    Ok(find_solutions(revspec, wordspec))
114}
115
116fn complete_solution(
117    revspec: &RevSpec,
118    solution: PartialSolution,
119    wordspec: WordSpec,
120) -> Option<PolyHash<u64>> {
121    let addout = match &revspec.addout {
122        ParamSource::Given(addout) => *addout,
123        ParamSource::Files(files) => find_addout(
124            files,
125            solution.factor,
126            solution.init,
127            solution.sign.unwrap(),
128        )?,
129    };
130    Some(
131        PolyHash::with_options()
132            .factor(solution.factor)
133            .init(solution.init)
134            .addout(addout)
135            .width(solution.width)
136            .inendian(wordspec.input_endian)
137            .outendian(wordspec.output_endian)
138            .wordsize(wordspec.wordsize)
139            .signedness(solution.sign.unwrap())
140            .build()
141            .unwrap(),
142    )
143}
144
145fn find_addout(file_polys: &[FilePoly], factor: u64, init: u64, sign: Signedness) -> Option<u64> {
146    let (first, rest) = file_polys.split_first()?;
147    let addout = find_single_addout(first, factor, init, sign);
148    rest.iter()
149        .all(|p| find_single_addout(p, factor, init, sign) == addout)
150        .then_some(addout)
151}
152
153fn find_single_addout(p: &FilePoly, factor: u64, init: u64, sign: Signedness) -> u64 {
154    let mask = mask_val(p.width as u8);
155    p.eval_with_init(factor, init, sign).wrapping_neg() & mask
156}
157
158fn check_params(
159    chk_bytes: &[(&[u8], Vec<u8>)],
160    factor: Option<u64>,
161    init: Option<u64>,
162    addout: Option<u64>,
163) -> Option<CheckReverserError> {
164    if 3 > chk_bytes.len()
165        + init.is_some() as usize
166        + factor.is_some() as usize
167        + addout.is_some() as usize
168    {
169        return Some(CheckReverserError::MissingParameter(
170            "need at least 3 files + parameters (init, factor, addout)",
171        ));
172    }
173
174    if init.is_none()
175        && chk_bytes.iter().map(|x| x.0.len()).max() == chk_bytes.iter().map(|x| x.0.len()).min()
176    {
177        return Some(CheckReverserError::UnsuitableFiles(
178            "need at least one file with different length (or set init to 0)",
179        ));
180    }
181    None
182}
183
184fn poly_from_data(
185    width: usize,
186    wordspec: &WordSpec,
187    chk_bytes: &(&[u8], Vec<u8>),
188) -> Option<FilePoly> {
189    if chk_bytes.0.len() % wordspec.word_bytes() != 0 {
190        return None;
191    }
192    let chk = Checksum::from_bytes(&chk_bytes.1, wordspec.output_endian, width)?;
193    let poly = [Signedness::Unsigned, Signedness::Signed].map(|signedness| WordPolynomial {
194        coefficients: WordSpec {
195            signedness,
196            ..*wordspec
197        }
198        .iter_words(chk_bytes.0)
199        .rev()
200        .map(|x| {
201            if x.negative {
202                x.value.wrapping_neg()
203            } else {
204                x.value
205            }
206        })
207        .collect(),
208    });
209    let size = poly[0].coefficients.len();
210    let sum = WordPolynomial {
211        coefficients: vec![chk],
212    };
213    let poly = poly.map(|mut x| {
214        x = x - sum.clone();
215        x.shorten(width);
216        x
217    });
218
219    Some(FilePoly {
220        poly,
221        init: InitPlace::Single(size),
222        width,
223    })
224}
225
226#[derive(Clone)]
227struct RevSpec {
228    polys: Vec<FilePoly>,
229    width: usize,
230    factor: Option<u64>,
231    init: ParamSource,
232    addout: ParamSource,
233    sign: Option<Signedness>,
234    wordsize: usize,
235}
236
237fn filter_addouts(original: Vec<FilePoly>, addout: Option<u64>) -> (Vec<FilePoly>, ParamSource) {
238    if let Some(addout) = addout {
239        let clean = original
240            .into_iter()
241            .map(|x| x.remove_addout(addout))
242            .collect();
243        return (clean, ParamSource::Given(addout));
244    }
245
246    let mut result = vec![];
247    for x in original.windows(2) {
248        let [lhs, rhs] = x else { unreachable!() };
249        let (InitPlace::Single(lhs_len), InitPlace::Single(rhs_len)) = (lhs.init, rhs.init) else {
250            unreachable!();
251        };
252
253        let init = if lhs_len == rhs_len {
254            InitPlace::None
255        } else {
256            InitPlace::Duo(lhs_len, rhs_len)
257        };
258        let poly0 = rhs.poly[0].clone() - lhs.poly[0].clone();
259        let poly1 = rhs.poly[1].clone() - lhs.poly[1].clone();
260
261        result.push(FilePoly {
262            poly: [poly0, poly1],
263            init,
264            width: lhs.width,
265        })
266    }
267    (result, ParamSource::Files(original))
268}
269
270fn cancel_dual_inits(
271    lhs: &WordPolynomial,
272    rhs: &WordPolynomial,
273    ll: usize,
274    lr: usize,
275    rl: usize,
276    rr: usize,
277) -> WordPolynomial {
278    let ldiff = lr - ll;
279    let rdiff = rr - rl;
280    let lhs = (lhs.clone() << rdiff) - lhs.clone();
281    let rhs = (rhs.clone() << ldiff) - rhs.clone();
282    let diff = rl - ll;
283    let cancelled = rhs - (lhs << diff);
284    cancelled
285}
286
287fn cancel_single_inits(
288    width: usize,
289    lhs: &WordPolynomial,
290    rhs: &WordPolynomial,
291    diff: usize,
292) -> WordPolynomial {
293    let mut cancelled = rhs.clone() - (lhs.clone() << diff);
294    cancelled.shorten(width);
295    cancelled
296}
297
298fn filter_inits(
299    original: Vec<FilePoly>,
300    init: Option<u64>,
301    width: usize,
302) -> (Vec<FilePoly>, ParamSource) {
303    if let Some(init) = init {
304        let clean = original.into_iter().map(|x| x.remove_init(init)).collect();
305        return (clean, ParamSource::Given(init));
306    }
307
308    let mut init_free = vec![];
309    let mut initiferous = vec![];
310    for x in original {
311        match x.init {
312            InitPlace::None => init_free.push(x),
313            _ => initiferous.push(x),
314        }
315    }
316    for x in initiferous.windows(2) {
317        let [lhs, rhs] = x else { unreachable!() };
318
319        match (lhs.init, rhs.init) {
320            (InitPlace::Single(l), InitPlace::Single(r)) => {
321                let diff = r - l;
322                let mut cancelled = [WordPolynomial::default(), WordPolynomial::default()];
323                for i in 0..2 {
324                    let lhs_poly = &lhs.poly[i];
325                    let rhs_poly = &rhs.poly[i];
326                    cancelled[i] = cancel_single_inits(width, lhs_poly, rhs_poly, diff);
327                }
328                init_free.push(FilePoly {
329                    init: InitPlace::None,
330                    poly: cancelled,
331                    width,
332                });
333            }
334            (InitPlace::Duo(ll, lr), InitPlace::Duo(rl, rr)) => {
335                let mut cancelled = [WordPolynomial::default(), WordPolynomial::default()];
336                for i in 0..2 {
337                    let mut cancel = cancel_dual_inits(&lhs.poly[i], &rhs.poly[i], ll, lr, rl, rr);
338                    cancel.shorten(width);
339                    cancelled[i] = cancel;
340                }
341                init_free.push(FilePoly {
342                    init: InitPlace::None,
343                    poly: cancelled,
344                    width,
345                });
346            }
347            (InitPlace::None | InitPlace::Duo(..), _)
348            | (_, InitPlace::None | InitPlace::Duo(..)) => unreachable!(),
349        }
350    }
351    (init_free, ParamSource::Files(initiferous))
352}
353
354#[derive(Debug, Clone)]
355enum ParamSource {
356    Given(u64),
357    Files(Vec<FilePoly>),
358}
359
360impl ParamSource {
361    fn matches_init(&self, factor: u64, init: u64, mask: u64, sign: Signedness) -> bool {
362        match self {
363            ParamSource::Given(given_init) => init & mask == given_init & mask,
364            ParamSource::Files(items) => items.iter().all(|p| {
365                p.poly[sign as usize]
366                    .eval(factor)
367                    .wrapping_add(p.init.init_value(factor, init))
368                    & mask
369                    == 0
370            }),
371        }
372    }
373}
374
375#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
376struct FilePoly {
377    init: InitPlace,
378    poly: [WordPolynomial; 2],
379    width: usize,
380}
381
382impl FilePoly {
383    fn remove_init(self, init: u64) -> FilePoly {
384        match self.init {
385            InitPlace::None => self,
386            InitPlace::Single(length) => {
387                let result = self.poly.map(|p| {
388                    let mut res = p + (WordPolynomial::constant(init) << length);
389                    res.shorten(self.width);
390                    res
391                });
392                FilePoly {
393                    poly: result,
394                    init: InitPlace::None,
395                    width: self.width,
396                }
397            }
398            InitPlace::Duo(llength, rlength) => {
399                let result = self.poly.map(|p| {
400                    let mut result = p + (WordPolynomial::constant(init) << rlength)
401                        - (WordPolynomial::constant(init) << llength);
402                    result.shorten(self.width);
403                    result
404                });
405                FilePoly {
406                    poly: result,
407                    init: InitPlace::None,
408                    width: self.width,
409                }
410            }
411        }
412    }
413
414    fn remove_addout(self, addout: u64) -> FilePoly {
415        let poly = self.poly.map(|p| p + WordPolynomial::constant(addout));
416        FilePoly { poly, ..self }
417    }
418
419    fn eval_with_init(&self, factor: u64, init: u64, sign: Signedness) -> u64 {
420        self.poly[sign as usize]
421            .eval(factor)
422            .wrapping_add(self.init.init_value(factor, init))
423    }
424}
425
426#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
427enum InitPlace {
428    None,
429    Single(usize),
430    Duo(usize, usize),
431}
432
433impl InitPlace {
434    fn init_value(self, factor: u64, init: u64) -> u64 {
435        match self {
436            InitPlace::None => 0,
437            InitPlace::Single(pos) => pow(factor, pos).wrapping_mul(init),
438            InitPlace::Duo(posl, posr) => pow(factor, posr)
439                .wrapping_sub(pow(factor, posl))
440                .wrapping_mul(init),
441        }
442    }
443}
444
445#[derive(Debug)]
446struct PartialSolution {
447    width: usize,
448    factor: u64,
449    init: u64,
450    sign: Option<Signedness>,
451}
452
453impl PartialSolution {
454    fn matches_spec(&self, spec: &RevSpec) -> bool {
455        let mask = mask_val(self.width as u8);
456        if let Some(factor) = spec.factor {
457            if factor & mask != self.factor & mask {
458                return false;
459            }
460        }
461        let sign = self.sign.unwrap_or_default();
462        if is_poly_solution(
463            spec.polys.iter().map(|x| &x.poly[sign as usize]),
464            self.factor,
465            mask,
466        ) && spec.init.matches_init(self.factor, self.init, mask, sign)
467        {
468            true
469        } else {
470            false
471        }
472    }
473
474    fn split_sign(&self) -> Option<[Self; 2]> {
475        if self.sign.is_none() {
476            return Some([
477                PartialSolution {
478                    width: self.width,
479                    factor: self.factor,
480                    init: self.init,
481                    sign: Some(Signedness::Unsigned),
482                },
483                PartialSolution {
484                    width: self.width,
485                    factor: self.factor,
486                    init: self.init,
487                    sign: Some(Signedness::Signed),
488                },
489            ]);
490        } else {
491            None
492        }
493    }
494}
495
496fn find_solutions(spec: RevSpec, wordspec: WordSpec) -> impl Iterator<Item = PolyHash<u64>> {
497    let mut partial_solutions = vec![];
498    partial_solutions.extend(
499        INITIAL_SOLUTIONS
500            .into_iter()
501            .map(|x| PartialSolution {
502                sign: spec.sign,
503                ..x
504            })
505            .filter(|x| x.matches_spec(&spec)),
506    );
507
508    std::iter::from_fn(move || {
509        while let Some(partial_solution) = partial_solutions.pop() {
510            if partial_solution.width == spec.width {
511                if partial_solution.factor != 1 {
512                    if let Some(concrete_solutions) = partial_solution.split_sign() {
513                        partial_solutions.extend(concrete_solutions);
514                    } else if let Some(solution) =
515                        complete_solution(&spec, partial_solution, wordspec)
516                    {
517                        return Some(solution);
518                    }
519                }
520            } else {
521                partial_solutions.extend(lift_solution(&spec, partial_solution));
522            }
523        }
524        None
525    })
526}
527
528const INITIAL_SOLUTIONS: [PartialSolution; 2] = [
529    PartialSolution {
530        width: 1,
531        factor: 1,
532        init: 0,
533        sign: None,
534    },
535    PartialSolution {
536        width: 1,
537        factor: 1,
538        init: 1,
539        sign: None,
540    },
541];
542
543fn lifted_signs(
544    wordsize: usize,
545    prev_sign: Option<Signedness>,
546    width: usize,
547) -> &'static [Option<Signedness>] {
548    match (width + 1 < wordsize, prev_sign) {
549        (_, Some(Signedness::Signed)) => &[Some(Signedness::Signed)],
550        (_, Some(Signedness::Unsigned)) => &[Some(Signedness::Unsigned)],
551        (true, None) => &[None],
552        (false, None) => &[Some(Signedness::Signed), Some(Signedness::Unsigned)],
553    }
554}
555
556fn lift_solution(
557    spec: &RevSpec,
558    subsolution: PartialSolution,
559) -> impl Iterator<Item = PartialSolution> {
560    let PartialSolution {
561        width,
562        factor,
563        init,
564        sign,
565    } = subsolution;
566    let step = 1u64 << subsolution.width;
567    let signs = lifted_signs(spec.wordsize, sign, width);
568    let with_sign = move |sign| {
569        [
570            (factor, init),
571            (factor + step, init),
572            (factor, init + step),
573            (factor + step, init + step),
574        ]
575        .into_iter()
576        .map(move |(factor, init)| PartialSolution {
577            width: width + 1,
578            factor,
579            init,
580            sign,
581        })
582        .filter(|subsolution| subsolution.matches_spec(spec))
583    };
584    signs.iter().copied().flat_map(with_sign)
585}
586
587fn is_poly_solution<'a>(
588    mut polys: impl Iterator<Item = &'a WordPolynomial>,
589    factor: u64,
590    mask: u64,
591) -> bool {
592    polys.all(|p| p.eval(factor) & mask == 0)
593}
594
595fn pow(mut base: u64, mut exp: usize) -> u64 {
596    if exp == 0 {
597        return 1;
598    }
599    let mut acc: u64 = 1;
600    loop {
601        if (exp & 1) == 1 {
602            acc = acc.wrapping_mul(base);
603            if exp == 1 {
604                return acc;
605            }
606        }
607        exp /= 2;
608        base = base.wrapping_mul(base);
609    }
610}
611
612#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
613struct WordPolynomial {
614    coefficients: Vec<u64>,
615}
616
617fn add_swap(x: &mut u64, a: u64) -> u64 {
618    let old = *x;
619    *x = x.wrapping_add(a);
620    old
621}
622
623impl WordPolynomial {
624    // multiplies with (x + factor)
625    fn linear_mul(&mut self, factor: u64) {
626        self.coefficients.insert(0, 0);
627        let Some((&mut mut cur, others)) = self.coefficients.split_last_mut() else {
628            return;
629        };
630        for coeff in others.iter_mut().rev() {
631            cur = add_swap(coeff, factor.wrapping_mul(cur));
632        }
633    }
634
635    fn constant(word: u64) -> WordPolynomial {
636        Self {
637            coefficients: vec![word],
638        }
639    }
640
641    fn one() -> Self {
642        Self::constant(1)
643    }
644
645    // the zero polynomial of width `width` is just the
646    // rising factorial of the width.
647    fn zero_poly(width: usize) -> Self {
648        let mut cur = Self::one();
649        let mut i = 1;
650        let mut zero_bits = 0;
651        while zero_bits < width {
652            cur.linear_mul(i);
653            zero_bits += i.trailing_zeros() as usize;
654            i += 1;
655        }
656        cur
657    }
658
659    fn eval(&self, x: u64) -> u64 {
660        let mut result = 0u64;
661        for coeff in self.coefficients.iter().rev() {
662            result = result.wrapping_mul(x).wrapping_add(*coeff);
663        }
664        result
665    }
666
667    // returns the degree of self, or None if self is zero
668    fn deg(&self) -> Option<usize> {
669        self.coefficients.len().checked_sub(
670            1 + self
671                .coefficients
672                .iter()
673                .copied()
674                .rev()
675                .take_while(|x| *x == 0)
676                .count(),
677        )
678    }
679
680    // naive polynomial remainder
681    fn reduce(&mut self, other: &Self) {
682        let Some(selfdeg) = self.deg() else {
683            return;
684        };
685        let Some(otherdeg) = other.deg() else {
686            return;
687        };
688        if selfdeg < otherdeg {
689            return;
690        }
691
692        let diff = selfdeg - otherdeg;
693
694        let leading_other = other.coefficients[otherdeg];
695        debug_assert_eq!(leading_other, 1, "`other` must be monic");
696        for i in (0..=diff).rev() {
697            let leading_self = self.coefficients[otherdeg + i];
698
699            for (self_coeff, other_coeff) in self.coefficients[i..]
700                .iter_mut()
701                .zip(other.coefficients.iter())
702            {
703                *self_coeff = self_coeff.wrapping_sub(other_coeff.wrapping_mul(leading_self));
704            }
705        }
706        self.coefficients.truncate(otherdeg);
707    }
708
709    // calculates self %= zero_poly to make it shorter
710    // without changing self as a mapping
711    fn shorten(&mut self, width: usize) {
712        let zero = Self::zero_poly(width);
713        self.reduce(&zero);
714        self.coefficients.shrink_to_fit();
715    }
716}
717
718impl std::ops::Add for WordPolynomial {
719    type Output = Self;
720
721    fn add(mut self, mut rhs: Self) -> Self::Output {
722        if self.coefficients.len() < rhs.coefficients.len() {
723            std::mem::swap(&mut self, &mut rhs);
724        }
725
726        for (a, b) in self.coefficients.iter_mut().zip(rhs.coefficients.iter()) {
727            *a = a.wrapping_add(*b);
728        }
729        self
730    }
731}
732
733impl std::ops::Sub for WordPolynomial {
734    type Output = Self;
735
736    fn sub(mut self, mut rhs: Self) -> Self::Output {
737        let swapped = if self.coefficients.len() < rhs.coefficients.len() {
738            std::mem::swap(&mut self, &mut rhs);
739            true
740        } else {
741            false
742        };
743
744        for (a, b) in self.coefficients.iter_mut().zip(rhs.coefficients.iter()) {
745            *a = a.wrapping_sub(*b);
746        }
747        if swapped {
748            self.coefficients
749                .iter_mut()
750                .for_each(|x| *x = x.wrapping_neg());
751        }
752        self
753    }
754}
755
756impl std::ops::Shl<usize> for WordPolynomial {
757    type Output = Self;
758
759    fn shl(mut self, rhs: usize) -> Self::Output {
760        if rhs == 0 {
761            return self;
762        }
763
764        let mut coeffs = vec![0; rhs];
765        coeffs.append(&mut self.coefficients);
766        self.coefficients = coeffs;
767        self
768    }
769}
770
771fn mask_val(width: u8) -> u64 {
772    if width >= 64 {
773        u64::MAX
774    } else {
775        (1u64 << width) - 1
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use quickcheck::{Arbitrary, Gen, TestResult};
782
783    use crate::{
784        checksum::tests::ReverseFileSet,
785        endian::{Endian, Signedness},
786    };
787
788    use super::*;
789
790    impl Arbitrary for WordPolynomial {
791        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
792            let coeffs = Vec::<u64>::arbitrary(g);
793            Self {
794                coefficients: coeffs,
795            }
796        }
797    }
798
799    fn mask(x: u64, width: u8) -> u64 {
800        x & mask_val(width)
801    }
802
803    impl Arbitrary for PolyHashBuilder<u64> {
804        fn arbitrary(g: &mut Gen) -> Self {
805            let mut new_polyhash = PolyHash::with_options();
806            let width = u8::arbitrary(g) % 63 + 2;
807            new_polyhash.width(width as usize);
808            let mut factor = mask(u64::arbitrary(g) << 1 | 1, width);
809            if factor == 1 {
810                factor = 3;
811            }
812            new_polyhash.factor(factor);
813            let init = mask(u64::arbitrary(g), width);
814            new_polyhash.init(init);
815            let addout = mask(u64::arbitrary(g), width);
816            new_polyhash.addout(addout);
817            let wordspec = WordSpec::arbitrary(g);
818            let max_word_width = ((width as usize).div_ceil(8)).next_power_of_two() * 8;
819            let actual_wordsize = max_word_width.min(wordspec.wordsize);
820            new_polyhash.wordsize(actual_wordsize);
821            new_polyhash.inendian(if actual_wordsize == 8 {
822                Endian::Big
823            } else {
824                wordspec.input_endian
825            });
826            new_polyhash.outendian(wordspec.output_endian);
827            new_polyhash.signedness(wordspec.signedness);
828            new_polyhash
829        }
830    }
831
832    fn run_polyhash_rev(
833        files: ReverseFileSet,
834        polyhash_build: PolyHashBuilder<u64>,
835        known: (bool, bool, bool),
836        wordspec_known: (bool, bool, bool, bool),
837    ) -> TestResult {
838        let polyhash = polyhash_build.build().unwrap();
839        let mut naive = PolyHash::<u64>::with_options();
840        naive.width(polyhash_build.width.unwrap());
841        if known.0 {
842            naive.factor(polyhash_build.factor.unwrap());
843        }
844        if known.1 {
845            naive.init(polyhash_build.init.unwrap());
846        }
847        if known.2 {
848            naive.addout(polyhash_build.addout.unwrap());
849        }
850        if wordspec_known.0 {
851            naive.wordsize(polyhash_build.wordsize.unwrap());
852        }
853        if wordspec_known.1 {
854            naive.inendian(polyhash_build.input_endian.unwrap());
855        }
856        if wordspec_known.2 {
857            naive.outendian(polyhash_build.output_endian.unwrap());
858        }
859        if wordspec_known.3 {
860            naive.signedness(polyhash_build.signedness.unwrap());
861        }
862        let chk_files: Vec<_> = files.with_checksums(&polyhash);
863        let reverser = reverse_polyhash(&naive, &chk_files, 0, false);
864        files.check_matching(&polyhash, reverser)
865    }
866
867    #[quickcheck]
868    fn qc_polyhash_rev(
869        files: ReverseFileSet,
870        polyhash_build: PolyHashBuilder<u64>,
871        known: (bool, bool, bool),
872        wordspec_known: (bool, bool, bool, bool),
873    ) -> TestResult {
874        run_polyhash_rev(files, polyhash_build, known, wordspec_known)
875    }
876
877    #[test]
878    fn error1() {
879        let files = ReverseFileSet(vec![vec![0, 18, 232, 236, 87, 255, 203, 100], vec![]]);
880        let mut polyhash_build = PolyHash::with_options();
881        polyhash_build
882            .width(7)
883            .factor(47)
884            .addout(0)
885            .inendian(Endian::Big)
886            .outendian(Endian::Big)
887            .signedness(Signedness::Unsigned)
888            .wordsize(8);
889        let res = run_polyhash_rev(
890            files,
891            polyhash_build,
892            (false, false, true),
893            (false, false, false, false),
894        );
895        assert!(!res.is_failure());
896    }
897
898    #[test]
899    fn error2() {
900        let files = ReverseFileSet(vec![
901            vec![
902                11, 64, 11, 236, 49, 191, 139, 194, 99, 52, 58, 24, 92, 159, 147, 137, 143, 208,
903                88, 235, 210, 48, 91, 21, 245, 211, 0, 255, 29, 255, 156, 185, 137, 81, 79, 166,
904                174, 248, 0, 16, 18, 137, 123, 92, 244, 197, 194, 101, 85, 255, 92, 113, 6, 133,
905                145, 204, 220, 117, 8, 255, 86, 113, 251, 255, 229, 0, 128, 161, 152, 198, 51, 0,
906                83, 232, 192, 117, 136, 24, 107, 214, 160, 1, 155, 255, 125, 5, 112, 35,
907            ],
908            vec![
909                171, 11, 130, 90, 14, 244, 199, 255, 1, 117, 108, 139, 151, 228, 94, 125, 40, 104,
910                134, 120, 155, 251, 94, 152, 156, 55, 133, 162, 110, 12, 26, 34, 250, 33, 107, 165,
911                121, 249, 183, 48,
912            ],
913            vec![
914                139, 155, 73, 251, 44, 63, 174, 79, 2, 12, 255, 63, 34, 165, 126, 37, 102, 227, 71,
915                164, 182, 0, 130, 165,
916            ],
917        ]);
918
919        let mut polyhash_build = PolyHash::with_options();
920        polyhash_build
921            .width(60)
922            .factor(177519018992307695)
923            .inendian(Endian::Little)
924            .outendian(Endian::Little)
925            .signedness(Signedness::Unsigned)
926            .wordsize(16);
927        assert!(
928            !run_polyhash_rev(
929                files,
930                polyhash_build,
931                (false, false, false),
932                (false, false, false, false)
933            )
934            .is_failure()
935        );
936    }
937
938    #[test]
939    fn error3() {
940        let files = ReverseFileSet(vec![
941            vec![
942                87, 65, 72, 201, 246, 255, 75, 207, 1, 15, 110, 87, 135, 244, 208, 46, 77, 222,
943                112, 151, 158, 26, 209, 154, 137, 3, 210, 234, 124, 187, 113, 2, 103, 48, 237, 66,
944                97, 20, 189, 182,
945            ],
946            vec![
947                140, 255, 249, 103, 77, 57, 255, 193, 218, 115, 17, 99, 89, 1, 166, 43, 10, 151,
948                56, 72, 149, 255, 142, 86, 254, 132, 168, 162, 2, 255, 10, 127,
949            ],
950            vec![],
951        ]);
952
953        let mut polyhash_build = PolyHash::with_options();
954        polyhash_build
955            .width(42)
956            .factor(0x3fa691cbcb1)
957            .init(0x2b3ac03d788)
958            .inendian(Endian::Little)
959            .outendian(Endian::Big)
960            .signedness(Signedness::Unsigned)
961            .wordsize(64);
962        assert!(
963            !run_polyhash_rev(
964                files,
965                polyhash_build,
966                (true, false, false),
967                (true, true, true, true)
968            )
969            .is_failure()
970        );
971    }
972
973    #[test]
974    fn error4() {
975        let files = ReverseFileSet(vec![vec![49, 102, 242, 157, 81, 134, 181, 69], vec![]]);
976
977        let mut polyhash_build = PolyHash::with_options();
978        polyhash_build
979            .width(41)
980            .factor(0xb614134799)
981            .init(0x14f8d8416e7)
982            .inendian(Endian::Little)
983            .outendian(Endian::Little)
984            .signedness(Signedness::Unsigned)
985            .wordsize(64);
986        assert!(
987            !run_polyhash_rev(
988                files,
989                polyhash_build,
990                (false, true, false),
991                (true, true, true, true)
992            )
993            .is_failure()
994        );
995    }
996
997    #[test]
998    fn error5() {
999        let files = ReverseFileSet(vec![vec![1, 0], vec![]]);
1000
1001        let mut polyhash_build = PolyHash::with_options();
1002        polyhash_build
1003            .width(16)
1004            .factor(0x103)
1005            .addout(0)
1006            .init(1)
1007            .inendian(Endian::Big)
1008            .outendian(Endian::Little)
1009            .signedness(Signedness::Unsigned)
1010            .wordsize(16);
1011        assert!(
1012            !run_polyhash_rev(
1013                files,
1014                polyhash_build,
1015                (true, true, true),
1016                (true, false, true, true)
1017            )
1018            .is_failure()
1019        );
1020    }
1021
1022    #[test]
1023    fn error6() {
1024        let files = ReverseFileSet(vec![
1025            vec![
1026                254, 52, 100, 107, 238, 255, 142, 161, 244, 42, 126, 169, 0, 1, 39, 8, 254, 86, 42,
1027                68, 149, 37, 64, 55, 87, 5, 130, 227, 49, 0, 234, 213, 42, 1, 181, 202, 10, 60, 60,
1028                229, 106, 223, 0, 161, 1, 121, 100, 11, 191, 1, 255, 196, 60, 185, 113, 255, 60,
1029                126, 200, 255, 98, 241, 129, 201, 255, 27, 233, 255, 81, 123, 84, 197, 23, 192, 88,
1030                170, 63, 169, 237, 183, 190, 197, 1, 207, 37, 235, 236, 34,
1031            ],
1032            vec![
1033                31, 25, 248, 236, 32, 63, 177, 0, 97, 174, 163, 251, 52, 184, 30, 57, 102, 75, 112,
1034                184, 225, 13, 41, 123, 19, 198, 181, 119, 71, 3, 64, 132, 53, 252, 166, 134, 252,
1035                195, 162, 175, 67, 1, 47, 93, 98, 236, 93, 47, 12, 226, 136, 15, 162, 10, 139, 80,
1036                167, 114, 0, 161, 33, 32, 106, 188, 200, 229, 127, 193, 7, 255, 1, 53, 1, 209, 150,
1037                33, 86, 9, 253, 255, 160, 9, 198, 38, 109, 54, 202, 204,
1038            ],
1039            vec![
1040                194, 101, 169, 88, 156, 175, 230, 147, 114, 1, 90, 145, 245, 213, 165, 164, 13, 12,
1041                238, 98, 238, 155, 91, 118, 115, 39, 0, 0, 164, 45, 212, 255, 79, 45, 253, 156, 1,
1042                97, 126, 19, 192, 41, 210, 195, 22, 89, 142, 45, 111, 211, 159, 133, 0, 74, 119,
1043                154, 229, 138, 185, 106, 1, 192, 131, 131, 1, 124, 214, 231, 131, 78, 183, 0, 171,
1044                3, 132, 127, 177, 61, 49, 71,
1045            ],
1046        ]);
1047        let mut polyhash_build = PolyHash::with_options();
1048        polyhash_build
1049            .width(43)
1050            .factor(0x7ffffffffffu64)
1051            .addout(0x2738294205e)
1052            .init(0x604e9d7a5d7)
1053            .inendian(Endian::Little)
1054            .outendian(Endian::Little)
1055            .signedness(Signedness::Signed)
1056            .wordsize(32);
1057        assert!(
1058            !run_polyhash_rev(
1059                files,
1060                polyhash_build,
1061                (false, false, false),
1062                (true, true, true, false)
1063            )
1064            .is_failure()
1065        );
1066    }
1067
1068    #[test]
1069    fn eval() {
1070        let poly = WordPolynomial {
1071            coefficients: vec![
1072                0x538107cc2cc3d3cd,
1073                0x53ad12105a37ea91,
1074                0xf834621210ff5545,
1075                0x4c0300d048fedcef,
1076                0x86f111dc9eb71158,
1077            ],
1078        };
1079        let res = poly.eval(0xa0f4946c408448b2);
1080        assert_eq!(res, 0x5d4cc07b361e7f2b);
1081    }
1082
1083    #[test]
1084    fn zero_polynomials() {
1085        for width in 0..=16 {
1086            let zero = WordPolynomial::zero_poly(width);
1087            let mask = (1 << width) - 1;
1088            for i in 0..=mask {
1089                let res = zero.eval(i);
1090                assert_eq!(res & mask, 0);
1091            }
1092        }
1093    }
1094
1095    #[test]
1096    fn rem() {
1097        let mut p = WordPolynomial {
1098            coefficients: vec![
1099                0x2af98107c994f2dc,
1100                0x130fc109ec31962,
1101                0x6acc95f45db55397,
1102                0xb928859e5971a22,
1103                0xd26e5b00b83f82ce,
1104            ],
1105        };
1106        let q = WordPolynomial {
1107            coefficients: vec![0xd1b714812043a8af, 0x7c52dfef8e18dbd9, 0x1],
1108        };
1109        p.reduce(&q);
1110        assert_eq!(p.coefficients, [0x759503c87242760d, 0x22f475b3698da76d]);
1111    }
1112
1113    #[quickcheck]
1114    fn shorten_preserves_map(p: WordPolynomial, point: u16) {
1115        let mut shortened = p.clone();
1116        shortened.shorten(16);
1117        assert!(shortened.deg() <= Some(17));
1118        let a = p.eval(point as u64);
1119        let b = shortened.eval(point as u64);
1120        assert_eq!(a & 0xffff, b & 0xffff);
1121    }
1122}