1use std::{borrow::Cow, collections::HashMap, sync::Arc, sync::OnceLock};
2
3const GF_SIZE: usize = 255;
4const GF_BITS: usize = 8;
5const PRIMITIVE_POLY: &[u8; 9] = b"101110001";
6
7static GF_TABLES: OnceLock<GfTables> = OnceLock::new();
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum FecError {
12 InvalidParameters,
14 NotEnoughFragments,
16 InvalidFragmentIndex(usize),
18 SingularMatrix,
20 OutputSlotMismatch,
22}
23
24impl std::fmt::Display for FecError {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 Self::InvalidParameters => write!(f, "invalid FEC parameters"),
28 Self::NotEnoughFragments => write!(f, "not enough fragments to recover block"),
29 Self::InvalidFragmentIndex(idx) => write!(f, "invalid FEC fragment index {idx}"),
30 Self::SingularMatrix => write!(f, "FEC decode matrix is singular"),
31 Self::OutputSlotMismatch => write!(f, "FEC output slot mismatch"),
32 }
33 }
34}
35
36impl std::error::Error for FecError {}
37
38#[derive(Debug, Clone)]
40pub struct FecCode {
41 k: usize,
42 n: usize,
43 enc_matrix: Vec<u8>,
44 decode_cache: Arc<HashMap<u128, Box<[u8]>>>,
45}
46
47impl FecCode {
48 pub fn new(k: usize, n: usize) -> Result<Self, FecError> {
50 if k == 0 || n == 0 || k > n || n >= 256 {
51 return Err(FecError::InvalidParameters);
52 }
53
54 let tables = tables();
55 let mut tmp = vec![0; n * k];
56 tmp[0] = 1;
57 for row in 0..(n - 1) {
58 for col in 0..k {
59 tmp[(row + 1) * k + col] = tables.gf_exp[modnn((row * col) as i32) as usize];
60 }
61 }
62
63 invert_vdm(&mut tmp[..k * k], k)?;
64
65 let mut enc_matrix = vec![0; n * k];
66 if n > k {
67 matmul(
68 &tmp[k * k..],
69 &tmp[..k * k],
70 &mut enc_matrix[k * k..],
71 n - k,
72 k,
73 k,
74 );
75 }
76 for col in 0..k {
77 enc_matrix[col * k + col] = 1;
78 }
79
80 let mut code = Self {
81 k,
82 n,
83 enc_matrix,
84 decode_cache: Arc::new(HashMap::new()),
85 };
86 code.decode_cache = Arc::new(code.precompute_decode_matrices()?);
87 Ok(code)
88 }
89
90 pub const fn k(&self) -> usize {
92 self.k
93 }
94
95 pub const fn n(&self) -> usize {
97 self.n
98 }
99
100 pub fn encode(&self, primary: &[Vec<u8>], block_size: usize) -> Result<Vec<Vec<u8>>, FecError> {
102 if primary.len() != self.k || primary.iter().any(|fragment| fragment.len() < block_size) {
103 return Err(FecError::InvalidParameters);
104 }
105
106 let mut fecs = vec![vec![0; block_size]; self.n - self.k];
107 for (fec_offset, fec) in fecs.iter_mut().enumerate() {
108 let fecnum = self.k + fec_offset;
109 let matrix_row = &self.enc_matrix[fecnum * self.k..(fecnum + 1) * self.k];
110 for (src_idx, src) in primary.iter().enumerate() {
111 addmul(fec, src, matrix_row[src_idx], block_size);
112 }
113 }
114 Ok(fecs)
115 }
116
117 pub fn recover_primary(
119 &self,
120 fragments: &mut [Option<Vec<u8>>],
121 block_size: usize,
122 ) -> Result<usize, FecError> {
123 if fragments.len() != self.n {
124 return Err(FecError::InvalidParameters);
125 }
126 if (0..self.k).all(|idx| fragments[idx].is_some()) {
127 return Ok(0);
128 }
129
130 let mut indexes = Vec::with_capacity(self.k);
131 let mut parity_cursor = self.k;
132
133 for primary_idx in 0..self.k {
134 if let Some(fragment) = fragments[primary_idx].as_ref() {
135 if fragment.len() < block_size {
136 return Err(FecError::InvalidParameters);
137 }
138 indexes.push(primary_idx);
139 } else {
140 while parity_cursor < self.n && fragments[parity_cursor].is_none() {
141 parity_cursor += 1;
142 }
143 if parity_cursor >= self.n {
144 return Err(FecError::NotEnoughFragments);
145 }
146 let fragment = fragments[parity_cursor]
147 .as_ref()
148 .ok_or(FecError::NotEnoughFragments)?;
149 if fragment.len() < block_size {
150 return Err(FecError::InvalidParameters);
151 }
152 indexes.push(parity_cursor);
153 parity_cursor += 1;
154 }
155 }
156
157 self.validate_indexes(&indexes)?;
158 let dec_matrix = self.decode_matrix(&indexes)?;
159 let mut recovered = 0usize;
160
161 for row in 0..self.k {
162 if indexes[row] >= self.k {
163 let mut out = vec![0; block_size];
164 for col in 0..self.k {
165 let input = fragments[indexes[col]]
166 .as_deref()
167 .expect("selected fragment exists");
168 addmul(&mut out, input, dec_matrix[row * self.k + col], block_size);
169 }
170 fragments[row] = Some(out);
171 recovered += 1;
172 }
173 }
174
175 Ok(recovered)
176 }
177
178 pub fn recover_primary_into(
184 &self,
185 fragments: &mut [u8],
186 present: &mut [bool],
187 block_size: usize,
188 ) -> Result<usize, FecError> {
189 if fragments.len() != self.n * block_size || present.len() != self.n {
190 return Err(FecError::InvalidParameters);
191 }
192 if present[..self.k].iter().all(|is_present| *is_present) {
193 return Ok(0);
194 }
195
196 let mut stack_indexes = [0usize; 16];
199 let mut heap_indexes = if self.k > stack_indexes.len() {
200 vec![0; self.k]
201 } else {
202 Vec::new()
203 };
204 let indexes = if self.k <= stack_indexes.len() {
205 &mut stack_indexes[..self.k]
206 } else {
207 heap_indexes.as_mut_slice()
208 };
209 let mut parity_cursor = self.k;
210 for primary_idx in 0..self.k {
211 if present[primary_idx] {
212 indexes[primary_idx] = primary_idx;
213 } else {
214 while parity_cursor < self.n && !present[parity_cursor] {
215 parity_cursor += 1;
216 }
217 if parity_cursor >= self.n {
218 return Err(FecError::NotEnoughFragments);
219 }
220 indexes[primary_idx] = parity_cursor;
221 parity_cursor += 1;
222 }
223 }
224
225 self.validate_indexes(indexes)?;
226 let dec_matrix = self.decode_matrix(indexes)?;
227 let mut recovered = 0;
228 for row in 0..self.k {
229 if indexes[row] < self.k {
230 continue;
231 }
232
233 fragment_mut(fragments, row, block_size).fill(0);
234 for col in 0..self.k {
235 addmul_distinct_contiguous(
236 fragments,
237 row,
238 indexes[col],
239 dec_matrix[row * self.k + col],
240 block_size,
241 );
242 }
243 present[row] = true;
244 recovered += 1;
245 }
246
247 Ok(recovered)
248 }
249
250 fn validate_indexes(&self, indexes: &[usize]) -> Result<(), FecError> {
251 if indexes.len() != self.k {
252 return Err(FecError::NotEnoughFragments);
253 }
254 for (row, &idx) in indexes.iter().enumerate() {
255 if idx >= self.n {
256 return Err(FecError::InvalidFragmentIndex(idx));
257 }
258 if idx < self.k && idx != row {
259 return Err(FecError::OutputSlotMismatch);
260 }
261 }
262 Ok(())
263 }
264
265 fn decode_matrix(&self, indexes: &[usize]) -> Result<Cow<'_, [u8]>, FecError> {
266 if let Some(key) = index_key(indexes) {
267 if let Some(matrix) = self.decode_cache.get(&key) {
268 return Ok(Cow::Borrowed(matrix));
269 }
270 }
271 Ok(Cow::Owned(self.decode_matrix_uncached(indexes)?))
272 }
273
274 fn decode_matrix_uncached(&self, indexes: &[usize]) -> Result<Vec<u8>, FecError> {
275 let mut matrix = vec![0; self.k * self.k];
276 for (row, &idx) in indexes.iter().enumerate() {
277 let row_start = row * self.k;
278 if idx < self.k {
279 matrix[row_start + row] = 1;
280 } else {
281 matrix[row_start..row_start + self.k]
282 .copy_from_slice(&self.enc_matrix[idx * self.k..(idx + 1) * self.k]);
283 }
284 }
285 invert_mat(&mut matrix, self.k)?;
286 Ok(matrix)
287 }
288
289 fn precompute_decode_matrices(&self) -> Result<HashMap<u128, Box<[u8]>>, FecError> {
290 const MAX_CACHED_MATRICES: usize = 4_096;
291
292 let parity_count = self.n - self.k;
296 if self.k > 12
297 || parity_count > 12
298 || self.k >= usize::BITS as usize
299 || parity_count >= usize::BITS as usize
300 {
301 return Ok(HashMap::new());
302 }
303
304 let pattern_count = (1..=self.k.min(parity_count)).fold(0usize, |total, missing| {
305 total.saturating_add(
306 binomial(self.k, missing).saturating_mul(binomial(parity_count, missing)),
307 )
308 });
309 if pattern_count > MAX_CACHED_MATRICES {
310 return Ok(HashMap::new());
311 }
312
313 let mut cache = HashMap::with_capacity(pattern_count);
314 for primary_mask in 1usize..(1usize << self.k) {
315 let missing = primary_mask.count_ones() as usize;
316 if missing > parity_count {
317 continue;
318 }
319
320 for parity_mask in 1usize..(1usize << parity_count) {
321 if parity_mask.count_ones() as usize != missing {
322 continue;
323 }
324
325 let mut selected_parity = (0..parity_count)
326 .filter(|parity| parity_mask & (1usize << parity) != 0)
327 .map(|parity| self.k + parity);
328 let indexes: Vec<usize> = (0..self.k)
329 .map(|primary| {
330 if primary_mask & (1usize << primary) == 0 {
331 primary
332 } else {
333 selected_parity.next().expect("matching parity count")
334 }
335 })
336 .collect();
337 let matrix = self.decode_matrix_uncached(&indexes)?.into_boxed_slice();
338 cache.insert(
339 index_key(&indexes).expect("cached FEC indexes fit u128"),
340 matrix,
341 );
342 }
343 }
344 Ok(cache)
345 }
346}
347
348fn binomial(n: usize, k: usize) -> usize {
349 let k = k.min(n - k);
350 (0..k).fold(1usize, |result, index| result * (n - index) / (index + 1))
351}
352
353fn index_key(indexes: &[usize]) -> Option<u128> {
354 if indexes.len() > 16 || indexes.iter().any(|&index| index > u8::MAX as usize) {
355 return None;
356 }
357
358 Some(
359 indexes
360 .iter()
361 .enumerate()
362 .fold(0u128, |key, (offset, &index)| {
363 key | (index as u128) << (offset * 8)
364 }),
365 )
366}
367
368#[derive(Clone)]
369struct GfTables {
370 gf_exp: [u8; 510],
371 inverse: [u8; 256],
372 gf_mul: Box<[[u8; 256]; 256]>,
373 gf_mul_low: Box<[[u8; 16]; 256]>,
374 gf_mul_high: Box<[[u8; 16]; 256]>,
375}
376
377fn tables() -> &'static GfTables {
378 GF_TABLES.get_or_init(GfTables::new)
379}
380
381impl GfTables {
382 fn new() -> Self {
383 let mut gf_exp = [0; 510];
384 let mut gf_log = [0; 256];
385 let mut inverse = [0; 256];
386
387 let mut mask = 1u8;
388 gf_exp[GF_BITS] = 0;
389 for i in 0..GF_BITS {
390 gf_exp[i] = mask;
391 gf_log[mask as usize] = i as u16;
392 if PRIMITIVE_POLY[i] == b'1' {
393 gf_exp[GF_BITS] ^= mask;
394 }
395 mask <<= 1;
396 }
397 gf_log[gf_exp[GF_BITS] as usize] = GF_BITS as u16;
398
399 mask = 1 << (GF_BITS - 1);
400 for i in (GF_BITS + 1)..GF_SIZE {
401 gf_exp[i] = if gf_exp[i - 1] >= mask {
402 gf_exp[GF_BITS] ^ ((gf_exp[i - 1] ^ mask) << 1)
403 } else {
404 gf_exp[i - 1] << 1
405 };
406 gf_log[gf_exp[i] as usize] = i as u16;
407 }
408 gf_log[0] = GF_SIZE as u16;
409 for i in 0..GF_SIZE {
410 gf_exp[i + GF_SIZE] = gf_exp[i];
411 }
412
413 inverse[1] = 1;
414 for i in 2..=GF_SIZE {
415 inverse[i] = gf_exp[GF_SIZE - gf_log[i] as usize];
416 }
417
418 let mut gf_mul = Box::new([[0; 256]; 256]);
419 for i in 1..256 {
420 for j in 1..256 {
421 gf_mul[i][j] = gf_exp[modnn(gf_log[i] as i32 + gf_log[j] as i32) as usize];
422 }
423 }
424
425 let mut gf_mul_low = Box::new([[0; 16]; 256]);
426 let mut gf_mul_high = Box::new([[0; 16]; 256]);
427 for coefficient in 0..256 {
428 for nibble in 0..16 {
429 gf_mul_low[coefficient][nibble] = gf_mul[coefficient][nibble];
430 gf_mul_high[coefficient][nibble] = gf_mul[coefficient][nibble << 4];
431 }
432 }
433
434 Self {
435 gf_exp,
436 inverse,
437 gf_mul,
438 gf_mul_low,
439 gf_mul_high,
440 }
441 }
442}
443
444fn modnn(mut x: i32) -> u8 {
445 while x >= GF_SIZE as i32 {
446 x -= GF_SIZE as i32;
447 x = (x >> GF_BITS) + (x & GF_SIZE as i32);
448 }
449 x as u8
450}
451
452fn gf_mul(x: u8, y: u8) -> u8 {
453 tables().gf_mul[x as usize][y as usize]
454}
455
456#[inline(always)]
457fn addmul(dst: &mut [u8], src: &[u8], coefficient: u8, len: usize) {
458 if coefficient == 0 {
459 return;
460 }
461 if coefficient == 1 {
462 for (output, input) in dst[..len].iter_mut().zip(&src[..len]) {
463 *output ^= *input;
464 }
465 return;
466 }
467 let tables = tables();
468 let coefficient = coefficient as usize;
469 let vector_len = crate::fec_simd::addmul(
470 &mut dst[..len],
471 &src[..len],
472 &tables.gf_mul_low[coefficient],
473 &tables.gf_mul_high[coefficient],
474 );
475 let mul = &tables.gf_mul[coefficient];
476 for idx in vector_len..len {
477 dst[idx] ^= mul[src[idx] as usize];
478 }
479}
480
481fn fragment_mut(data: &mut [u8], index: usize, block_size: usize) -> &mut [u8] {
482 &mut data[index * block_size..(index + 1) * block_size]
483}
484
485fn addmul_distinct_contiguous(
486 fragments: &mut [u8],
487 dst_idx: usize,
488 src_idx: usize,
489 coefficient: u8,
490 len: usize,
491) {
492 debug_assert_ne!(dst_idx, src_idx);
493 if dst_idx < src_idx {
494 let (before_src, from_src) = fragments.split_at_mut(src_idx * len);
495 let dst = &mut before_src[dst_idx * len..(dst_idx + 1) * len];
496 addmul(dst, &from_src[..len], coefficient, len);
497 } else {
498 let (before_dst, from_dst) = fragments.split_at_mut(dst_idx * len);
499 let src = &before_dst[src_idx * len..(src_idx + 1) * len];
500 addmul(&mut from_dst[..len], src, coefficient, len);
501 }
502}
503
504fn matmul(a: &[u8], b: &[u8], c: &mut [u8], n: usize, k: usize, m: usize) {
505 for row in 0..n {
506 for col in 0..m {
507 let mut acc = 0;
508 for i in 0..k {
509 acc ^= gf_mul(a[row * k + i], b[i * m + col]);
510 }
511 c[row * m + col] = acc;
512 }
513 }
514}
515
516fn invert_mat(src: &mut [u8], k: usize) -> Result<(), FecError> {
517 let mut indxc = vec![0; k];
518 let mut indxr = vec![0; k];
519 let mut ipiv = vec![0; k];
520 let mut id_row = vec![0; k];
521
522 for col in 0..k {
523 let mut irow = None;
524 let mut icol = None;
525
526 if ipiv[col] != 1 && src[col * k + col] != 0 {
527 irow = Some(col);
528 icol = Some(col);
529 } else {
530 'search: for row in 0..k {
531 if ipiv[row] != 1 {
532 for ix in 0..k {
533 if ipiv[ix] == 0 && src[row * k + ix] != 0 {
534 irow = Some(row);
535 icol = Some(ix);
536 break 'search;
537 }
538 }
539 }
540 }
541 }
542
543 let irow = irow.ok_or(FecError::SingularMatrix)?;
544 let icol = icol.ok_or(FecError::SingularMatrix)?;
545 ipiv[icol] += 1;
546
547 if irow != icol {
548 for ix in 0..k {
549 src.swap(irow * k + ix, icol * k + ix);
550 }
551 }
552 indxr[col] = irow;
553 indxc[col] = icol;
554
555 let pivot = src[icol * k + icol];
556 if pivot == 0 {
557 return Err(FecError::SingularMatrix);
558 }
559 if pivot != 1 {
560 let inv = tables().inverse[pivot as usize];
561 src[icol * k + icol] = 1;
562 for ix in 0..k {
563 src[icol * k + ix] = gf_mul(inv, src[icol * k + ix]);
564 }
565 }
566
567 id_row[icol] = 1;
568 if src[icol * k..(icol + 1) * k] != id_row[..] {
569 let pivot_row = src[icol * k..(icol + 1) * k].to_vec();
570 for ix in 0..k {
571 if ix != icol {
572 let coefficient = src[ix * k + icol];
573 src[ix * k + icol] = 0;
574 addmul(&mut src[ix * k..(ix + 1) * k], &pivot_row, coefficient, k);
575 }
576 }
577 }
578 id_row[icol] = 0;
579 }
580
581 for col in (0..k).rev() {
582 if indxr[col] != indxc[col] {
583 for row in 0..k {
584 src.swap(row * k + indxr[col], row * k + indxc[col]);
585 }
586 }
587 }
588 Ok(())
589}
590
591fn invert_vdm(src: &mut [u8], k: usize) -> Result<(), FecError> {
592 if k == 1 {
593 return Ok(());
594 }
595
596 let mut c = vec![0; k];
597 let mut b = vec![0; k];
598 let mut p = vec![0; k];
599
600 for i in 0..k {
601 p[i] = src[i * k + 1];
602 }
603
604 c[k - 1] = p[0];
605 for (i, p_i) in p.iter().copied().enumerate().take(k).skip(1) {
606 let start = k - 1 - (i - 1);
607 for j in start..(k - 1) {
608 c[j] ^= gf_mul(p_i, c[j + 1]);
609 }
610 c[k - 1] ^= p_i;
611 }
612
613 for row in 0..k {
614 let xx = p[row];
615 let mut t = 1;
616 b[k - 1] = 1;
617 for i in (1..k).rev() {
618 b[i - 1] = c[i] ^ gf_mul(xx, b[i]);
619 t = gf_mul(xx, t) ^ b[i - 1];
620 }
621 if t == 0 {
622 return Err(FecError::SingularMatrix);
623 }
624 let inv = tables().inverse[t as usize];
625 for col in 0..k {
626 src[col * k + row] = gf_mul(inv, b[col]);
627 }
628 }
629
630 Ok(())
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn recovers_missing_primary_fragment_from_parity() {
639 let fec = FecCode::new(3, 5).unwrap();
640 let primary = vec![b"aaaa".to_vec(), b"bbbb".to_vec(), b"cccc".to_vec()];
641 let parity = fec.encode(&primary, 4).unwrap();
642 let mut fragments = vec![
643 Some(primary[0].clone()),
644 None,
645 Some(primary[2].clone()),
646 Some(parity[0].clone()),
647 None,
648 ];
649
650 let recovered = fec.recover_primary(&mut fragments, 4).unwrap();
651 assert_eq!(recovered, 1);
652 assert_eq!(fragments[1].as_deref(), Some(&primary[1][..]));
653 }
654
655 #[test]
656 fn optimized_addmul_matches_scalar_galois_field_math() {
657 let src: Vec<u8> = (0..79).map(|idx| (idx * 37 + 11) as u8).collect();
658 let initial: Vec<u8> = (0..79).map(|idx| (idx * 19 + 7) as u8).collect();
659
660 for coefficient in 0..=u8::MAX {
661 let mut actual = initial.clone();
662 addmul(&mut actual, &src, coefficient, src.len());
663
664 let mut expected = initial.clone();
665 for (output, input) in expected.iter_mut().zip(&src) {
666 *output ^= gf_mul(coefficient, *input);
667 }
668 assert_eq!(actual, expected, "coefficient {coefficient}");
669 }
670 }
671
672 #[test]
673 fn recovers_every_supported_wfb_eight_twelve_loss_pattern() {
674 let fec = FecCode::new(8, 12).unwrap();
675 assert_eq!(fec.decode_cache.len(), 494);
676
677 let primary: Vec<Vec<u8>> = (0..fec.k())
678 .map(|fragment| {
679 (0..257)
680 .map(|offset| (fragment * 31 + offset * 17) as u8)
681 .collect()
682 })
683 .collect();
684 let parity = fec.encode(&primary, 257).unwrap();
685 let complete: Vec<Vec<u8>> = primary.iter().chain(&parity).cloned().collect();
686
687 for mask in 1u16..(1 << fec.k()) {
688 let missing = mask.count_ones() as usize;
689 if missing > fec.n() - fec.k() {
690 continue;
691 }
692
693 let mut fragments: Vec<Option<Vec<u8>>> = complete.iter().cloned().map(Some).collect();
694 for (primary_idx, fragment) in fragments.iter_mut().enumerate().take(fec.k()) {
695 if mask & (1 << primary_idx) != 0 {
696 *fragment = None;
697 }
698 }
699
700 assert_eq!(fec.recover_primary(&mut fragments, 257), Ok(missing));
701 for (fragment, expected) in fragments.iter().zip(&primary) {
702 assert_eq!(fragment.as_deref(), Some(&expected[..]));
703 }
704
705 let mut contiguous: Vec<u8> = complete.iter().flatten().copied().collect();
706 let mut present = vec![true; fec.n()];
707 for (primary_idx, is_present) in present.iter_mut().enumerate().take(fec.k()) {
708 if mask & (1 << primary_idx) != 0 {
709 *is_present = false;
710 }
711 }
712 assert_eq!(
713 fec.recover_primary_into(&mut contiguous, &mut present, 257),
714 Ok(missing)
715 );
716 for (primary_idx, expected) in primary.iter().enumerate() {
717 let start = primary_idx * 257;
718 assert_eq!(&contiguous[start..start + 257], expected);
719 }
720 }
721 }
722
723 #[test]
724 fn cached_recovery_handles_every_primary_and_parity_loss_pattern() {
725 let fec = FecCode::new(8, 12).unwrap();
726 let primary: Vec<Vec<u8>> = (0..fec.k())
727 .map(|fragment| {
728 (0..257)
729 .map(|offset| (fragment * 43 + offset * 29) as u8)
730 .collect()
731 })
732 .collect();
733 let parity = fec.encode(&primary, 257).unwrap();
734 let complete: Vec<Vec<u8>> = primary.iter().chain(&parity).cloned().collect();
735 let parity_count = fec.n() - fec.k();
736 for primary_mask in 1u16..(1 << fec.k()) {
737 let missing = primary_mask.count_ones() as usize;
738 if missing > parity_count {
739 continue;
740 }
741 for parity_mask in 1u16..(1 << parity_count) {
742 if parity_mask.count_ones() as usize != missing {
743 continue;
744 }
745
746 let mut fragments: Vec<Option<Vec<u8>>> =
747 complete.iter().cloned().map(Some).collect();
748 for (primary_idx, fragment) in fragments.iter_mut().enumerate().take(fec.k()) {
749 if primary_mask & (1 << primary_idx) != 0 {
750 *fragment = None;
751 }
752 }
753 for parity_idx in 0..parity_count {
754 if parity_mask & (1 << parity_idx) == 0 {
755 fragments[fec.k() + parity_idx] = None;
756 }
757 }
758
759 assert_eq!(fec.recover_primary(&mut fragments, 257), Ok(missing));
760 for (fragment, expected) in fragments.iter().zip(&primary) {
761 assert_eq!(fragment.as_deref(), Some(&expected[..]));
762 }
763 }
764 }
765 }
766}