1#![allow(clippy::needless_range_loop)]
24
25pub mod gf {
28 const fn build_tables() -> ([u8; 256], [u8; 512]) {
31 let mut log = [0u8; 256];
32 let mut exp = [0u8; 512];
33 let mut x: u16 = 1;
34 let mut i = 0usize;
35 while i < 255 {
36 exp[i] = x as u8;
37 log[x as usize] = i as u8;
38 x <<= 1;
39 if x & 0x100 != 0 {
40 x ^= 0x11D;
41 }
42 i += 1;
43 }
44 let mut j = 255usize;
46 while j < 512 {
47 exp[j] = exp[j - 255];
48 j += 1;
49 }
50 (log, exp)
51 }
52
53 const TABLES: ([u8; 256], [u8; 512]) = build_tables();
54 const LOG: [u8; 256] = TABLES.0;
55 const EXP: [u8; 512] = TABLES.1;
56
57 #[inline(always)]
59 pub const fn add(a: u8, b: u8) -> u8 {
60 a ^ b
61 }
62
63 #[inline(always)]
65 pub fn mul(a: u8, b: u8) -> u8 {
66 if a == 0 || b == 0 {
67 0
68 } else {
69 EXP[LOG[a as usize] as usize + LOG[b as usize] as usize]
70 }
71 }
72
73 #[inline(always)]
75 pub fn inv(a: u8) -> u8 {
76 debug_assert!(a != 0, "GF(256) has no inverse of 0");
77 EXP[255 - LOG[a as usize] as usize]
78 }
79
80 #[inline(always)]
82 pub fn div(a: u8, b: u8) -> u8 {
83 if a == 0 {
84 0
85 } else {
86 EXP[LOG[a as usize] as usize + 255 - LOG[b as usize] as usize]
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[repr(u8)]
98pub enum GfBackend {
99 Scalar = 0,
101 Ssse3 = 1,
103 Avx2 = 2,
105 Avx512Pshufb = 3,
107 Gfni256 = 4,
110 Gfni512 = 5,
113 AffineScalar = 6,
118 Neon = 7,
123}
124
125impl GfBackend {
126 pub fn available(self) -> bool {
128 match self {
129 GfBackend::Scalar | GfBackend::AffineScalar => true,
130 #[cfg(target_arch = "aarch64")]
131 GfBackend::Neon => std::arch::is_aarch64_feature_detected!("neon"),
132 #[cfg(not(target_arch = "aarch64"))]
133 GfBackend::Neon => false,
134 #[cfg(target_arch = "x86_64")]
135 GfBackend::Ssse3 => std::is_x86_feature_detected!("ssse3"),
136 #[cfg(target_arch = "x86_64")]
137 GfBackend::Avx2 => std::is_x86_feature_detected!("avx2"),
138 #[cfg(target_arch = "x86_64")]
139 GfBackend::Avx512Pshufb => {
140 std::is_x86_feature_detected!("avx512f")
141 && std::is_x86_feature_detected!("avx512bw")
142 }
143 #[cfg(target_arch = "x86_64")]
144 GfBackend::Gfni256 => {
145 std::is_x86_feature_detected!("gfni") && std::is_x86_feature_detected!("avx2")
146 }
147 #[cfg(target_arch = "x86_64")]
148 GfBackend::Gfni512 => {
149 std::is_x86_feature_detected!("gfni")
150 && std::is_x86_feature_detected!("avx512f")
151 && std::is_x86_feature_detected!("avx512bw")
152 }
153 #[cfg(not(target_arch = "x86_64"))]
154 _ => false,
155 }
156 }
157
158 pub fn name(self) -> &'static str {
160 match self {
161 GfBackend::Scalar => "scalar",
162 GfBackend::Ssse3 => "ssse3",
163 GfBackend::Avx2 => "avx2",
164 GfBackend::Avx512Pshufb => "avx512-pshufb",
165 GfBackend::Gfni256 => "gfni256",
166 GfBackend::Gfni512 => "gfni512",
167 GfBackend::AffineScalar => "affine-emulated",
168 GfBackend::Neon => "neon",
169 }
170 }
171
172 fn from_u8(v: u8) -> Self {
173 match v {
174 1 => GfBackend::Ssse3,
175 2 => GfBackend::Avx2,
176 3 => GfBackend::Avx512Pshufb,
177 4 => GfBackend::Gfni256,
178 5 => GfBackend::Gfni512,
179 6 => GfBackend::AffineScalar,
180 7 => GfBackend::Neon,
181 _ => GfBackend::Scalar,
182 }
183 }
184}
185
186#[cfg(target_arch = "x86_64")]
190const GF_LADDER: [GfBackend; 6] = [
191 GfBackend::Gfni512,
192 GfBackend::Avx512Pshufb,
193 GfBackend::Gfni256,
194 GfBackend::Avx2,
195 GfBackend::Ssse3,
196 GfBackend::Scalar,
197];
198#[cfg(target_arch = "aarch64")]
202const GF_LADDER: [GfBackend; 2] = [GfBackend::Neon, GfBackend::Scalar];
203#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
205const GF_LADDER: [GfBackend; 1] = [GfBackend::Scalar];
206
207fn detect_best_backend() -> GfBackend {
208 GF_LADDER
209 .into_iter()
210 .find(|b| b.available())
211 .unwrap_or(GfBackend::Scalar)
212}
213
214const BACKEND_UNINIT: u8 = 0xFF;
221static SELECTED_BACKEND: std::sync::atomic::AtomicU8 =
222 std::sync::atomic::AtomicU8::new(BACKEND_UNINIT);
223
224#[inline]
225fn current_backend() -> GfBackend {
226 use std::sync::atomic::Ordering;
227 let v = SELECTED_BACKEND.load(Ordering::Relaxed);
228 if v == BACKEND_UNINIT {
229 let b = detect_best_backend();
230 SELECTED_BACKEND.store(b as u8, Ordering::Relaxed);
231 b
232 } else {
233 GfBackend::from_u8(v)
234 }
235}
236
237#[inline]
242fn gf_mul_add(backend: GfBackend, out: &mut [u8], src: &[u8], coef: u8) {
243 debug_assert_eq!(out.len(), src.len());
244 if coef == 0 {
245 return;
246 }
247 if coef == 1 {
248 for (o, &s) in out.iter_mut().zip(src) {
249 *o ^= s;
250 }
251 return;
252 }
253 gf_mul_add_backend(backend, out, src, coef);
254}
255
256pub fn gf_mul_add_auto(out: &mut [u8], src: &[u8], coef: u8) {
261 gf_mul_add(current_backend(), out, src, coef);
262}
263
264pub fn gf_mul_add_backend(backend: GfBackend, out: &mut [u8], src: &[u8], coef: u8) {
269 debug_assert_eq!(out.len(), src.len());
270 debug_assert!(
271 backend.available(),
272 "GF backend {} is not available on this host",
273 backend.name()
274 );
275 match backend {
276 GfBackend::Scalar => gf_mul_add_scalar(out, src, coef),
277 GfBackend::AffineScalar => gf_mul_add_affine_scalar(out, src, coef),
278 #[cfg(target_arch = "aarch64")]
279 GfBackend::Neon => unsafe { gf_mul_add_neon(out, src, coef) },
282 #[cfg(not(target_arch = "aarch64"))]
283 GfBackend::Neon => gf_mul_add_scalar(out, src, coef),
284 #[cfg(target_arch = "x86_64")]
285 GfBackend::Ssse3 => unsafe { gf_mul_add_ssse3(out, src, coef) },
288 #[cfg(target_arch = "x86_64")]
289 GfBackend::Avx2 => unsafe { gf_mul_add_avx2(out, src, coef) },
290 #[cfg(target_arch = "x86_64")]
291 GfBackend::Avx512Pshufb => unsafe { gf_mul_add_avx512(out, src, coef) },
292 #[cfg(target_arch = "x86_64")]
293 GfBackend::Gfni256 => unsafe { gf_mul_add_gfni256(out, src, coef) },
294 #[cfg(target_arch = "x86_64")]
295 GfBackend::Gfni512 => unsafe { gf_mul_add_gfni512(out, src, coef) },
296 #[cfg(not(target_arch = "x86_64"))]
297 _ => gf_mul_add_scalar(out, src, coef),
298 }
299}
300
301fn gf_mul_add_scalar(out: &mut [u8], src: &[u8], coef: u8) {
302 for (o, &s) in out.iter_mut().zip(src) {
303 *o ^= gf::mul(coef, s);
304 }
305}
306
307#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
313#[inline]
314fn gf_nibble_tables(coef: u8) -> ([u8; 16], [u8; 16]) {
315 let mut lo = [0u8; 16];
316 let mut hi = [0u8; 16];
317 for i in 0..16u8 {
318 lo[i as usize] = gf::mul(coef, i);
319 hi[i as usize] = gf::mul(coef, i << 4);
320 }
321 (lo, hi)
322}
323
324#[cfg(target_arch = "x86_64")]
325#[target_feature(enable = "ssse3")]
326unsafe fn gf_mul_add_ssse3(out: &mut [u8], src: &[u8], coef: u8) {
327 use std::arch::x86_64::*;
328 let (lo, hi) = gf_nibble_tables(coef);
329 let n = out.len();
330 unsafe {
333 let lo_v = _mm_loadu_si128(lo.as_ptr() as *const __m128i);
334 let hi_v = _mm_loadu_si128(hi.as_ptr() as *const __m128i);
335 let mask = _mm_set1_epi8(0x0f);
336 let mut i = 0usize;
337 while i + 16 <= n {
338 let s = _mm_loadu_si128(src.as_ptr().add(i) as *const __m128i);
339 let lo_n = _mm_and_si128(s, mask);
340 let hi_n = _mm_and_si128(_mm_srli_epi16(s, 4), mask);
341 let prod = _mm_xor_si128(_mm_shuffle_epi8(lo_v, lo_n), _mm_shuffle_epi8(hi_v, hi_n));
342 let o = _mm_loadu_si128(out.as_ptr().add(i) as *const __m128i);
343 _mm_storeu_si128(out.as_mut_ptr().add(i) as *mut __m128i, _mm_xor_si128(o, prod));
344 i += 16;
345 }
346 while i < n {
347 out[i] ^= gf::mul(coef, src[i]);
348 i += 1;
349 }
350 }
351}
352
353#[cfg(target_arch = "x86_64")]
354#[target_feature(enable = "avx2")]
355unsafe fn gf_mul_add_avx2(out: &mut [u8], src: &[u8], coef: u8) {
356 use std::arch::x86_64::*;
357 let (lo, hi) = gf_nibble_tables(coef);
358 let n = out.len();
359 unsafe {
362 let lo_v = _mm256_broadcastsi128_si256(_mm_loadu_si128(lo.as_ptr() as *const __m128i));
363 let hi_v = _mm256_broadcastsi128_si256(_mm_loadu_si128(hi.as_ptr() as *const __m128i));
364 let mask = _mm256_set1_epi8(0x0f);
365 let mut i = 0usize;
366 while i + 32 <= n {
367 let s = _mm256_loadu_si256(src.as_ptr().add(i) as *const __m256i);
368 let lo_n = _mm256_and_si256(s, mask);
369 let hi_n = _mm256_and_si256(_mm256_srli_epi16(s, 4), mask);
370 let prod = _mm256_xor_si256(
371 _mm256_shuffle_epi8(lo_v, lo_n),
372 _mm256_shuffle_epi8(hi_v, hi_n),
373 );
374 let o = _mm256_loadu_si256(out.as_ptr().add(i) as *const __m256i);
375 _mm256_storeu_si256(out.as_mut_ptr().add(i) as *mut __m256i, _mm256_xor_si256(o, prod));
376 i += 32;
377 }
378 while i < n {
379 out[i] ^= gf::mul(coef, src[i]);
380 i += 1;
381 }
382 }
383}
384
385fn gf_affine_matrix(coef: u8) -> u64 {
396 let mut m = 0u64;
397 for i in 0..8usize {
398 let mut row = 0u8;
399 for j in 0..8usize {
400 if (gf::mul(coef, 1u8 << j) >> i) & 1 == 1 {
401 row |= 1u8 << j;
402 }
403 }
404 let byte_idx = 7 - i;
405 m |= (row as u64) << (8 * byte_idx);
406 }
407 m
408}
409
410#[inline]
414fn gf2p8affine_byte(x: u8, m: u64) -> u8 {
415 let mut out = 0u8;
416 for i in 0..8usize {
417 let mat_byte = ((m >> (8 * (7 - i))) & 0xff) as u8;
418 if (mat_byte & x).count_ones() & 1 == 1 {
419 out |= 1u8 << i;
420 }
421 }
422 out
423}
424
425fn gf_mul_add_affine_scalar(out: &mut [u8], src: &[u8], coef: u8) {
429 let m = gf_affine_matrix(coef);
430 for (o, &s) in out.iter_mut().zip(src) {
431 *o ^= gf2p8affine_byte(s, m);
432 }
433}
434
435#[cfg(target_arch = "x86_64")]
440#[target_feature(enable = "avx512f,avx512bw")]
441unsafe fn gf_mul_add_avx512(out: &mut [u8], src: &[u8], coef: u8) {
442 use std::arch::x86_64::*;
443 let (lo, hi) = gf_nibble_tables(coef);
444 let n = out.len();
445 unsafe {
448 let lo_v = _mm512_broadcast_i32x4(_mm_loadu_si128(lo.as_ptr() as *const __m128i));
449 let hi_v = _mm512_broadcast_i32x4(_mm_loadu_si128(hi.as_ptr() as *const __m128i));
450 let mask = _mm512_set1_epi8(0x0f);
451 let mut i = 0usize;
452 while i + 64 <= n {
453 let s = _mm512_loadu_si512(src.as_ptr().add(i) as *const __m512i);
454 let lo_n = _mm512_and_si512(s, mask);
455 let hi_n = _mm512_and_si512(_mm512_srli_epi16::<4>(s), mask);
456 let prod = _mm512_xor_si512(
457 _mm512_shuffle_epi8(lo_v, lo_n),
458 _mm512_shuffle_epi8(hi_v, hi_n),
459 );
460 let o = _mm512_loadu_si512(out.as_ptr().add(i) as *const __m512i);
461 _mm512_storeu_si512(
462 out.as_mut_ptr().add(i) as *mut __m512i,
463 _mm512_xor_si512(o, prod),
464 );
465 i += 64;
466 }
467 while i < n {
468 out[i] ^= gf::mul(coef, src[i]);
469 i += 1;
470 }
471 }
472}
473
474#[cfg(target_arch = "x86_64")]
478#[target_feature(enable = "gfni,avx2")]
479unsafe fn gf_mul_add_gfni256(out: &mut [u8], src: &[u8], coef: u8) {
480 use std::arch::x86_64::*;
481 let matrix = _mm256_set1_epi64x(gf_affine_matrix(coef) as i64);
482 let n = out.len();
483 unsafe {
486 let mut i = 0usize;
487 while i + 32 <= n {
488 let s = _mm256_loadu_si256(src.as_ptr().add(i) as *const __m256i);
489 let prod = _mm256_gf2p8affine_epi64_epi8::<0>(s, matrix);
490 let o = _mm256_loadu_si256(out.as_ptr().add(i) as *const __m256i);
491 _mm256_storeu_si256(
492 out.as_mut_ptr().add(i) as *mut __m256i,
493 _mm256_xor_si256(o, prod),
494 );
495 i += 32;
496 }
497 while i < n {
498 out[i] ^= gf::mul(coef, src[i]);
499 i += 1;
500 }
501 }
502}
503
504#[cfg(target_arch = "x86_64")]
508#[target_feature(enable = "gfni,avx512f,avx512bw")]
509unsafe fn gf_mul_add_gfni512(out: &mut [u8], src: &[u8], coef: u8) {
510 use std::arch::x86_64::*;
511 let matrix = _mm512_set1_epi64(gf_affine_matrix(coef) as i64);
512 let n = out.len();
513 unsafe {
516 let mut i = 0usize;
517 while i + 64 <= n {
518 let s = _mm512_loadu_si512(src.as_ptr().add(i) as *const __m512i);
519 let prod = _mm512_gf2p8affine_epi64_epi8::<0>(s, matrix);
520 let o = _mm512_loadu_si512(out.as_ptr().add(i) as *const __m512i);
521 _mm512_storeu_si512(
522 out.as_mut_ptr().add(i) as *mut __m512i,
523 _mm512_xor_si512(o, prod),
524 );
525 i += 64;
526 }
527 while i < n {
528 out[i] ^= gf::mul(coef, src[i]);
529 i += 1;
530 }
531 }
532}
533
534#[cfg(target_arch = "aarch64")]
540#[target_feature(enable = "neon")]
541unsafe fn gf_mul_add_neon(out: &mut [u8], src: &[u8], coef: u8) {
542 use std::arch::aarch64::*;
543 let (lo, hi) = gf_nibble_tables(coef);
544 let n = out.len();
545 unsafe {
548 let lo_v = vld1q_u8(lo.as_ptr());
549 let hi_v = vld1q_u8(hi.as_ptr());
550 let mask = vdupq_n_u8(0x0f);
551 let mut i = 0usize;
552 while i + 16 <= n {
553 let s = vld1q_u8(src.as_ptr().add(i));
554 let lo_n = vandq_u8(s, mask);
555 let hi_n = vshrq_n_u8::<4>(s);
556 let prod = veorq_u8(vqtbl1q_u8(lo_v, lo_n), vqtbl1q_u8(hi_v, hi_n));
557 let o = vld1q_u8(out.as_ptr().add(i));
558 vst1q_u8(out.as_mut_ptr().add(i), veorq_u8(o, prod));
559 i += 16;
560 }
561 while i < n {
562 out[i] ^= gf::mul(coef, src[i]);
563 i += 1;
564 }
565 }
566}
567
568#[derive(Debug, Clone)]
571pub struct RsCode {
572 k: usize,
573 r: usize,
574 cauchy: Vec<u8>,
577 backend: Option<GfBackend>,
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Eq)]
586pub enum FecError {
587 BadParams,
589 TooFewShards,
592 BadShardLen,
594}
595
596impl RsCode {
597 pub fn new(k: usize, r: usize) -> Result<Self, FecError> {
600 if k == 0 || r == 0 || k + r > 256 {
601 return Err(FecError::BadParams);
602 }
603 let mut cauchy = vec![0u8; r * k];
608 for j in 0..r {
609 for c in 0..k {
610 let x = (k + j) as u8 ^ c as u8;
611 cauchy[j * k + c] = gf::inv(x);
612 }
613 }
614 Ok(Self { k, r, cauchy, backend: None })
615 }
616
617 pub fn with_backend(mut self, backend: Option<GfBackend>) -> Self {
621 self.backend = backend;
622 self
623 }
624
625 #[inline]
628 fn effective_backend(&self) -> GfBackend {
629 self.backend.unwrap_or_else(current_backend)
630 }
631
632 pub fn k(&self) -> usize {
634 self.k
635 }
636
637 pub fn r(&self) -> usize {
639 self.r
640 }
641
642 pub fn encode(
645 &self,
646 data: &[&[u8]],
647 parity: &mut [&mut [u8]],
648 ) -> Result<(), FecError> {
649 if data.len() != self.k || parity.len() != self.r {
650 return Err(FecError::BadParams);
651 }
652 let len = data[0].len();
653 if len == 0
654 || data.iter().any(|s| s.len() != len)
655 || parity.iter().any(|s| s.len() != len)
656 {
657 return Err(FecError::BadShardLen);
658 }
659 let backend = self.effective_backend();
660 for j in 0..self.r {
661 let row = &self.cauchy[j * self.k..(j + 1) * self.k];
662 parity[j].fill(0);
663 for c in 0..self.k {
664 let coef = row[c];
665 if coef != 0 {
666 gf_mul_add(backend, parity[j], data[c], coef);
667 }
668 }
669 }
670 Ok(())
671 }
672
673 pub fn decode(&self, shards: &mut [Option<Vec<u8>>]) -> Result<(), FecError> {
678 let n = self.k + self.r;
679 if shards.len() != n {
680 return Err(FecError::BadParams);
681 }
682 if (0..self.k).all(|i| shards[i].is_some()) {
684 return Ok(());
685 }
686 let mut surv: Vec<usize> = Vec::with_capacity(self.k);
688 let mut len = 0usize;
689 for (idx, s) in shards.iter().enumerate() {
690 if let Some(v) = s
691 && surv.len() < self.k
692 {
693 if len == 0 {
694 len = v.len();
695 } else if v.len() != len {
696 return Err(FecError::BadShardLen);
697 }
698 surv.push(idx);
699 }
700 }
701 if surv.len() < self.k || len == 0 {
702 return Err(FecError::TooFewShards);
703 }
704 let mut a = vec![0u8; self.k * self.k];
707 for (row, &pos) in surv.iter().enumerate() {
708 for c in 0..self.k {
709 a[row * self.k + c] = self.enc_entry(pos, c);
710 }
711 }
712 let inv = invert(&a, self.k).ok_or(FecError::TooFewShards)?;
713 let backend = self.effective_backend();
714 for i in 0..self.k {
717 if shards[i].is_some() {
718 continue;
719 }
720 let mut out = vec![0u8; len];
721 for j in 0..self.k {
722 let coef = inv[i * self.k + j];
723 if coef == 0 {
724 continue;
725 }
726 let src = shards[surv[j]].as_ref().unwrap();
727 gf_mul_add(backend, &mut out, src, coef);
728 }
729 shards[i] = Some(out);
730 }
731 Ok(())
732 }
733
734 #[inline]
737 fn enc_entry(&self, row: usize, col: usize) -> u8 {
738 if row < self.k {
739 if row == col {
740 1
741 } else {
742 0
743 }
744 } else {
745 self.cauchy[(row - self.k) * self.k + col]
746 }
747 }
748}
749
750fn invert(m: &[u8], n: usize) -> Option<Vec<u8>> {
753 let mut a = m.to_vec();
754 let mut inv = vec![0u8; n * n];
755 for i in 0..n {
756 inv[i * n + i] = 1;
757 }
758 for col in 0..n {
759 let mut piv = col;
761 while piv < n && a[piv * n + col] == 0 {
762 piv += 1;
763 }
764 if piv == n {
765 return None; }
767 if piv != col {
768 for c in 0..n {
769 a.swap(col * n + c, piv * n + c);
770 inv.swap(col * n + c, piv * n + c);
771 }
772 }
773 let pv = a[col * n + col];
775 let pinv = gf::inv(pv);
776 for c in 0..n {
777 a[col * n + c] = gf::mul(a[col * n + c], pinv);
778 inv[col * n + c] = gf::mul(inv[col * n + c], pinv);
779 }
780 for row in 0..n {
782 if row == col {
783 continue;
784 }
785 let factor = a[row * n + col];
786 if factor == 0 {
787 continue;
788 }
789 for c in 0..n {
790 a[row * n + c] ^= gf::mul(factor, a[col * n + c]);
791 inv[row * n + c] ^= gf::mul(factor, inv[col * n + c]);
792 }
793 }
794 }
795 Some(inv)
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801
802 #[test]
803 fn gf_field_laws() {
804 for a in 0u8..=255 {
806 assert_eq!(gf::add(a, 0), a);
807 assert_eq!(gf::mul(a, 1), a);
808 assert_eq!(gf::mul(a, 0), 0);
809 }
810 for a in 1u8..=255 {
812 assert_eq!(gf::mul(a, gf::inv(a)), 1, "inverse of {a}");
813 }
814 for a in 0u8..=255 {
816 for b in 1u8..=255 {
817 assert_eq!(gf::mul(a, b), gf::mul(b, a));
818 assert_eq!(gf::div(gf::mul(a, b), b), a);
819 }
820 }
821 }
822
823 fn exhaustive_recovery(k: usize, r: usize, len: usize) {
827 exhaustive_recovery_backend(k, r, len, None);
828 }
829
830 fn exhaustive_recovery_backend(k: usize, r: usize, len: usize, backend: Option<GfBackend>) {
834 let code = RsCode::new(k, r).expect("code").with_backend(backend);
835 let data: Vec<Vec<u8>> = (0..k)
837 .map(|i| {
838 (0..len)
839 .map(|b| ((i * 131 + b * 17 + 7) & 0xFF) as u8)
840 .collect()
841 })
842 .collect();
843 let mut parity: Vec<Vec<u8>> = vec![vec![0u8; len]; r];
844 {
845 let data_refs: Vec<&[u8]> = data.iter().map(|s| s.as_slice()).collect();
846 let mut par_refs: Vec<&mut [u8]> =
847 parity.iter_mut().map(|s| s.as_mut_slice()).collect();
848 code.encode(&data_refs, &mut par_refs).expect("encode");
849 }
850 let n = k + r;
851 let all: Vec<Vec<u8>> = data.iter().chain(parity.iter()).cloned().collect();
853 for lost_count in 1..=r {
855 for mask in 0u32..(1 << n) {
858 if (mask.count_ones() as usize) != lost_count {
859 continue;
860 }
861 let mut shards: Vec<Option<Vec<u8>>> = all
862 .iter()
863 .enumerate()
864 .map(|(i, s)| {
865 if mask & (1 << i) != 0 {
866 None
867 } else {
868 Some(s.clone())
869 }
870 })
871 .collect();
872 code.decode(&mut shards).expect("decode");
873 for i in 0..k {
874 assert_eq!(
875 shards[i].as_ref().unwrap(),
876 &data[i],
877 "k={k} r={r} mask={mask:b}: data shard {i} mismatch"
878 );
879 }
880 }
881 }
882 }
883
884 #[test]
885 fn recovery_k4_r2() {
886 exhaustive_recovery(4, 2, 32);
887 }
888
889 #[test]
890 fn recovery_k6_r3() {
891 exhaustive_recovery(6, 3, 16);
892 }
893
894 #[test]
895 fn recovery_k8_r4() {
896 exhaustive_recovery(8, 4, 8);
897 }
898
899 #[test]
900 fn recovery_k1_r1() {
901 exhaustive_recovery(1, 1, 64);
902 }
903
904 #[test]
905 fn recovery_k16_r16_high_parity() {
906 let (k, r, len) = (16usize, 16usize, 64usize);
913 let code = RsCode::new(k, r).expect("code");
914 let data: Vec<Vec<u8>> = (0..k)
915 .map(|i| (0..len).map(|b| ((i * 131 + b * 17 + 7) & 0xFF) as u8).collect())
916 .collect();
917 let mut parity: Vec<Vec<u8>> = vec![vec![0u8; len]; r];
918 {
919 let dr: Vec<&[u8]> = data.iter().map(|s| s.as_slice()).collect();
920 let mut pr: Vec<&mut [u8]> = parity.iter_mut().map(|s| s.as_mut_slice()).collect();
921 code.encode(&dr, &mut pr).expect("encode");
922 }
923 let all: Vec<Vec<u8>> = data.iter().chain(parity.iter()).cloned().collect();
924 let n = (k + r) as u32;
925 let full: u32 = if n >= 32 { u32::MAX } else { (1u32 << n) - 1 };
927 let mut masks: Vec<u32> = vec![
928 (1u32 << k) - 1, full & !((1u32 << k) - 1), 0x5555_5555 & full, ];
932 let mut st: u32 = 0x1234_5678;
933 while masks.len() < 30 {
934 let (mut m, mut cnt) = (0u32, 0);
935 while cnt < 16 {
936 st = st.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
937 let bit = (st >> 16) % n;
938 if m & (1 << bit) == 0 {
939 m |= 1 << bit;
940 cnt += 1;
941 }
942 }
943 masks.push(m);
944 }
945 for mask in masks {
946 assert_eq!(mask.count_ones(), 16, "mask must drop exactly r=16");
947 let mut shards: Vec<Option<Vec<u8>>> = all
948 .iter()
949 .enumerate()
950 .map(|(i, s)| if mask & (1 << i) != 0 { None } else { Some(s.clone()) })
951 .collect();
952 code.decode(&mut shards).expect("decode r=16");
953 for i in 0..k {
954 assert_eq!(shards[i].as_ref().unwrap(), &data[i], "mask={mask:b}: data {i} mismatch");
955 }
956 }
957 }
958
959 #[test]
960 fn too_few_shards_is_reported() {
961 let code = RsCode::new(4, 2).expect("code");
962 let len = 16;
963 let data: Vec<Vec<u8>> = (0..4).map(|_| vec![1u8; len]).collect();
964 let mut parity: Vec<Vec<u8>> = vec![vec![0u8; len]; 2];
965 {
966 let dr: Vec<&[u8]> = data.iter().map(|s| s.as_slice()).collect();
967 let mut pr: Vec<&mut [u8]> = parity.iter_mut().map(|s| s.as_mut_slice()).collect();
968 code.encode(&dr, &mut pr).expect("encode");
969 }
970 let mut shards: Vec<Option<Vec<u8>>> = vec![
972 None,
973 None,
974 None,
975 Some(data[3].clone()),
976 Some(parity[0].clone()),
977 Some(parity[1].clone()),
978 ];
979 assert_eq!(code.decode(&mut shards), Err(FecError::TooFewShards));
980 }
981
982 #[test]
983 fn gfni_affine_matrix_matches_field_multiply() {
984 assert_eq!(
987 gf_affine_matrix(1),
988 0x0102_0408_1020_4080,
989 "multiply-by-1 must be the GF2P8AFFINEQB identity matrix"
990 );
991 for coef in 0u16..=255 {
996 let m = gf_affine_matrix(coef as u8);
997 for x in 0u16..=255 {
998 assert_eq!(
999 gf2p8affine_byte(x as u8, m),
1000 gf::mul(coef as u8, x as u8),
1001 "affine(coef={coef}, x={x}) != gf::mul"
1002 );
1003 }
1004 }
1005 }
1006
1007 #[test]
1008 fn all_available_backends_match_scalar() {
1009 use GfBackend::*;
1014 let candidates = [Scalar, AffineScalar, Ssse3, Avx2, Avx512Pshufb, Gfni256, Gfni512, Neon];
1015 let n = 1000usize;
1016 let src: Vec<u8> = (0..n).map(|i| ((i * 73 + 11) & 0xff) as u8).collect();
1017 let init: Vec<u8> = (0..n).map(|i| ((i * 31 + 7) & 0xff) as u8).collect();
1018 for coef in [2u8, 7, 100, 255] {
1019 let mut want = init.clone();
1020 gf_mul_add_backend(Scalar, &mut want, &src, coef);
1021 for &b in &candidates {
1022 if !b.available() {
1023 continue;
1024 }
1025 let mut got = init.clone();
1026 gf_mul_add_backend(b, &mut got, &src, coef);
1027 assert_eq!(got, want, "backend {} disagrees at coef {coef}", b.name());
1028 }
1029 }
1030 }
1031
1032 #[cfg(target_arch = "aarch64")]
1033 #[test]
1034 fn neon_backend_matches_scalar_and_recovers() {
1035 assert!(GfBackend::Neon.available(), "NEON must be available on aarch64");
1038 assert_eq!(detect_best_backend(), GfBackend::Neon, "aarch64 must pick NEON");
1039 let n = 1000usize;
1042 let src: Vec<u8> = (0..n).map(|i| ((i * 73 + 11) & 0xff) as u8).collect();
1043 let init: Vec<u8> = (0..n).map(|i| ((i * 31 + 7) & 0xff) as u8).collect();
1044 for coef in [2u8, 7, 100, 255] {
1045 let mut want = init.clone();
1046 gf_mul_add_backend(GfBackend::Scalar, &mut want, &src, coef);
1047 let mut got = init.clone();
1048 gf_mul_add_backend(GfBackend::Neon, &mut got, &src, coef);
1049 assert_eq!(got, want, "NEON disagrees with scalar at coef {coef}");
1050 }
1051 exhaustive_recovery_backend(4, 2, 32, Some(GfBackend::Neon));
1053 exhaustive_recovery_backend(6, 3, 16, Some(GfBackend::Neon));
1054 exhaustive_recovery_backend(8, 4, 8, Some(GfBackend::Neon));
1055 }
1056
1057 #[test]
1058 fn exhaustive_recovery_via_gfni_emulation() {
1059 exhaustive_recovery_backend(4, 2, 32, Some(GfBackend::AffineScalar));
1065 exhaustive_recovery_backend(6, 3, 16, Some(GfBackend::AffineScalar));
1066 exhaustive_recovery_backend(8, 4, 8, Some(GfBackend::AffineScalar));
1067 }
1068}