Skip to main content

openipc_core/
fec.rs

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/// Reed-Solomon FEC error.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum FecError {
12    /// FEC parameters are outside `0 < k <= n < 256`.
13    InvalidParameters,
14    /// Fewer than `k` usable fragments were available.
15    NotEnoughFragments,
16    /// A fragment index was outside the configured block.
17    InvalidFragmentIndex(usize),
18    /// The decode matrix could not be inverted.
19    SingularMatrix,
20    /// Recovered output did not match expected primary slots.
21    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/// Reed-Solomon FEC code used by WFB blocks.
39#[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    /// Create an FEC code with `k` primary fragments and `n-k` parity fragments.
49    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    /// Return the number of primary fragments.
91    pub const fn k(&self) -> usize {
92        self.k
93    }
94
95    /// Return the total number of primary plus parity fragments.
96    pub const fn n(&self) -> usize {
97        self.n
98    }
99
100    /// Generate parity fragments for a full primary block.
101    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    /// Recover missing primary fragments in-place.
118    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    /// Recover missing primary fragments into caller-owned reusable buffers.
179    ///
180    /// `present` identifies received fragments. `fragments` contains exactly
181    /// `n * block_size` contiguous bytes in fragment-index order. This avoids
182    /// allocating recovered fragments in the packet-processing hot path.
183    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        // WFB uses k=8. Keep its per-damaged-block index selection on the
197        // stack while preserving support for larger custom FEC codes.
198        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        // Exhaustively caching larger codes can consume substantial memory and
293        // delay session setup. WFB's normal 8/12 code has only 494 recoverable
294        // primary/parity loss patterns and fits comfortably within this bound.
295        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}