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
68fn 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 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 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 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 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 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}