Skip to main content

fec_rs/
reed_solomon.rs

1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use crate::errors::{Error, SBSError};
5use crate::galois::{self, EXP_TABLE, LOG_TABLE};
6use crate::matrix::Matrix;
7
8const DATA_DECODE_MATRIX_CACHE_CAPACITY: usize = 254;
9
10/// Reed-Solomon erasure code encoder/decoder.
11///
12/// Operates over GF(2^8) with generating polynomial 29, compatible
13/// with the Moonlight streaming protocol.
14///
15/// # Example
16///
17/// ```
18/// use fec_rs::ReedSolomon;
19///
20/// let rs = ReedSolomon::new(4, 2).unwrap();
21///
22/// let mut shards: Vec<Vec<u8>> = vec![
23///     vec![0, 1, 2, 3],
24///     vec![4, 5, 6, 7],
25///     vec![8, 9, 10, 11],
26///     vec![12, 13, 14, 15],
27///     vec![0, 0, 0, 0],
28///     vec![0, 0, 0, 0],
29/// ];
30///
31/// rs.encode(&mut shards).unwrap();
32/// assert!(rs.verify(&shards).unwrap());
33/// ```
34#[derive(Debug)]
35pub struct ReedSolomon {
36    data_shard_count: usize,
37    parity_shard_count: usize,
38    total_shard_count: usize,
39    matrix: Matrix,
40    mul_slice_fn: galois::MulSliceFn,
41    mul_slice_xor_fn: galois::MulSliceFn,
42    data_decode_matrix_cache: Mutex<HashMap<Vec<usize>, Matrix>>,
43}
44
45impl Clone for ReedSolomon {
46    fn clone(&self) -> Self {
47        ReedSolomon {
48            data_shard_count: self.data_shard_count,
49            parity_shard_count: self.parity_shard_count,
50            total_shard_count: self.total_shard_count,
51            matrix: self.matrix.clone(),
52            mul_slice_fn: self.mul_slice_fn,
53            mul_slice_xor_fn: self.mul_slice_xor_fn,
54            data_decode_matrix_cache: Mutex::new(HashMap::new()),
55        }
56    }
57}
58
59impl PartialEq for ReedSolomon {
60    fn eq(&self, rhs: &ReedSolomon) -> bool {
61        self.data_shard_count == rhs.data_shard_count
62            && self.parity_shard_count == rhs.parity_shard_count
63            && self.matrix == rhs.matrix
64    }
65}
66
67impl ReedSolomon {
68    fn build_matrix(data_shards: usize, total_shards: usize) -> Matrix {
69        let vandermonde = Matrix::vandermonde(total_shards, data_shards);
70        let top = vandermonde.sub_matrix(0, 0, data_shards, data_shards);
71        let mut result = vandermonde.multiply(&top.invert().unwrap());
72
73        let parity_shards = total_shards - data_shards;
74        let mut inverse = vec![0u8; 256];
75        inverse[0] = 0;
76        inverse[1] = 1;
77        for i in 2..256 {
78            inverse[i] = EXP_TABLE[(255 - LOG_TABLE[i]) as usize];
79        }
80
81        for j in 0..parity_shards {
82            for i in 0..data_shards {
83                result.data[(data_shards + j) * data_shards + i] = inverse[(parity_shards + i) ^ j];
84            }
85        }
86
87        result
88    }
89
90    /// Creates a new Reed-Solomon encoder/decoder.
91    ///
92    /// # Errors
93    ///
94    /// Returns an error if `data_shards` or `parity_shards` is 0,
95    /// or if `data_shards + parity_shards > 256`.
96    pub fn new(data_shards: usize, parity_shards: usize) -> Result<Self, Error> {
97        if data_shards == 0 {
98            return Err(Error::TooFewDataShards);
99        }
100        if parity_shards == 0 {
101            return Err(Error::TooFewParityShards);
102        }
103        if data_shards + parity_shards > 256 {
104            return Err(Error::TooManyShards);
105        }
106
107        let total_shards = data_shards + parity_shards;
108        let matrix = Self::build_matrix(data_shards, total_shards);
109        let (mul_slice_fn, mul_slice_xor_fn) = galois::detect_mul_slice();
110
111        Ok(ReedSolomon {
112            data_shard_count: data_shards,
113            parity_shard_count: parity_shards,
114            total_shard_count: total_shards,
115            matrix,
116            mul_slice_fn,
117            mul_slice_xor_fn,
118            data_decode_matrix_cache: Mutex::new(HashMap::new()),
119        })
120    }
121
122    /// Directly set the parity rows of the encoding matrix.
123    ///
124    /// `parity` must have exactly `parity_shard_count * data_shard_count` elements.
125    /// This is used for compatibility with the Moonlight audio protocol,
126    /// where the parity matrix values are hardcoded from OpenFEC.
127    pub fn set_parity_matrix(&mut self, parity: &[u8]) -> Result<(), Error> {
128        let expected = self.parity_shard_count * self.data_shard_count;
129        if parity.len() != expected {
130            return Err(Error::InvalidParityMatrix);
131        }
132        let offset = self.data_shard_count * self.data_shard_count;
133        self.matrix.data[offset..offset + expected].copy_from_slice(parity);
134        self.data_decode_matrix_cache
135            .lock()
136            .unwrap_or_else(|poisoned| poisoned.into_inner())
137            .clear();
138        Ok(())
139    }
140
141    pub fn data_shard_count(&self) -> usize {
142        self.data_shard_count
143    }
144
145    pub fn parity_shard_count(&self) -> usize {
146        self.parity_shard_count
147    }
148
149    pub fn total_shard_count(&self) -> usize {
150        self.total_shard_count
151    }
152
153    #[inline]
154    fn get_parity_rows(&self) -> Vec<&[u8]> {
155        (self.data_shard_count..self.total_shard_count)
156            .map(|i| self.matrix.get_row(i))
157            .collect()
158    }
159
160    /// Multiply `input` by `c` in GF(2^8), write to `out`. Uses pre-detected SIMD path.
161    #[inline(always)]
162    fn mul_slice(&self, c: u8, input: &[u8], out: &mut [u8]) {
163        assert_eq!(input.len(), out.len());
164        if c == 0 {
165            out.iter_mut().for_each(|o| *o = 0);
166            return;
167        }
168        if c == 1 {
169            out.copy_from_slice(input);
170            return;
171        }
172        (self.mul_slice_fn)(c, input, out);
173    }
174
175    /// Multiply `input` by `c` in GF(2^8), XOR into `out`. Uses pre-detected SIMD path.
176    #[inline(always)]
177    fn mul_slice_xor(&self, c: u8, input: &[u8], out: &mut [u8]) {
178        assert_eq!(input.len(), out.len());
179        if c == 0 {
180            return;
181        }
182        if c == 1 {
183            for (o, i) in out.iter_mut().zip(input.iter()) {
184                *o ^= *i;
185            }
186            return;
187        }
188        (self.mul_slice_xor_fn)(c, input, out);
189    }
190
191    fn code_some_slices(&self, matrix_rows: &[&[u8]], inputs: &[&[u8]], outputs: &mut [&mut [u8]]) {
192        for (i_input, input) in inputs.iter().enumerate() {
193            self.code_single_slice(matrix_rows, i_input, input, outputs);
194        }
195    }
196
197    #[inline]
198    fn code_single_slice(
199        &self,
200        matrix_rows: &[&[u8]],
201        i_input: usize,
202        input: &[u8],
203        outputs: &mut [&mut [u8]],
204    ) {
205        for (i_row, output) in outputs.iter_mut().enumerate() {
206            let c = matrix_rows[i_row][i_input];
207            if i_input == 0 {
208                self.mul_slice(c, input, output);
209            } else {
210                self.mul_slice_xor(c, input, output);
211            }
212        }
213    }
214
215    /// Constructs the parity shards.
216    ///
217    /// The parity shard slots will be overwritten.
218    ///
219    /// With the `parallel` feature enabled, parity shards are computed in parallel
220    /// using rayon when the workload is large enough.
221    #[cfg(feature = "parallel")]
222    pub fn encode<T: AsRef<[u8]> + AsMut<[u8]> + Send>(
223        &self,
224        shards: &mut [T],
225    ) -> Result<(), Error> {
226        if shards.len() < self.total_shard_count {
227            return Err(Error::TooFewShards);
228        }
229        if shards.len() > self.total_shard_count {
230            return Err(Error::TooManyShards);
231        }
232        Self::check_slices_uniform(shards)?;
233
234        let (data, parity) = shards.split_at_mut(self.data_shard_count);
235        let parity_rows = self.get_parity_rows();
236
237        // Use rayon for large workloads where parallelization overhead is worthwhile.
238        // Threshold: parity_count * data_count * shard_size > ~1MB of work.
239        let shard_size = data[0].as_ref().len();
240        let work = self.parity_shard_count * self.data_shard_count * shard_size;
241        if work > 1_000_000 {
242            use rayon::prelude::*;
243
244            // Collect data shard references that we can share across threads.
245            let data_refs: Vec<&[u8]> = data.iter().map(|d| d.as_ref()).collect();
246            let mul_fn = self.mul_slice_fn;
247            let mul_xor_fn = self.mul_slice_xor_fn;
248
249            parity
250                .par_iter_mut()
251                .enumerate()
252                .for_each(|(i_row, p): (usize, &mut T)| {
253                    let output = p.as_mut();
254                    let row = parity_rows[i_row];
255                    for (i_input, &input) in data_refs.iter().enumerate() {
256                        let c = row[i_input];
257                        if c == 0 {
258                            if i_input == 0 {
259                                output.iter_mut().for_each(|o| *o = 0);
260                            }
261                        } else if c == 1 {
262                            if i_input == 0 {
263                                output.copy_from_slice(input);
264                            } else {
265                                for (o, i) in output.iter_mut().zip(input.iter()) {
266                                    *o ^= *i;
267                                }
268                            }
269                        } else if i_input == 0 {
270                            mul_fn(c, input, output);
271                        } else {
272                            mul_xor_fn(c, input, output);
273                        }
274                    }
275                });
276
277            return Ok(());
278        }
279
280        Self::encode_sequential(
281            data,
282            parity,
283            &parity_rows,
284            self.mul_slice_fn,
285            self.mul_slice_xor_fn,
286        );
287        Ok(())
288    }
289
290    #[cfg(not(feature = "parallel"))]
291    pub fn encode<T: AsRef<[u8]> + AsMut<[u8]>>(&self, shards: &mut [T]) -> Result<(), Error> {
292        if shards.len() < self.total_shard_count {
293            return Err(Error::TooFewShards);
294        }
295        if shards.len() > self.total_shard_count {
296            return Err(Error::TooManyShards);
297        }
298        Self::check_slices_uniform(shards)?;
299
300        let (data, parity) = shards.split_at_mut(self.data_shard_count);
301        let parity_rows = self.get_parity_rows();
302
303        Self::encode_sequential(
304            data,
305            parity,
306            &parity_rows,
307            self.mul_slice_fn,
308            self.mul_slice_xor_fn,
309        );
310        Ok(())
311    }
312
313    fn encode_sequential<T: AsRef<[u8]> + AsMut<[u8]>>(
314        data: &[T],
315        parity: &mut [T],
316        parity_rows: &[&[u8]],
317        mul_slice_fn: galois::MulSliceFn,
318        mul_slice_xor_fn: galois::MulSliceFn,
319    ) {
320        for i_input in 0..data.len() {
321            let input = data[i_input].as_ref();
322            for (i_row, p) in parity.iter_mut().enumerate() {
323                let c = parity_rows[i_row][i_input];
324                let output = p.as_mut();
325                if c == 0 {
326                    if i_input == 0 {
327                        output.iter_mut().for_each(|o| *o = 0);
328                    }
329                } else if c == 1 {
330                    if i_input == 0 {
331                        output.copy_from_slice(input);
332                    } else {
333                        for (o, i) in output.iter_mut().zip(input.iter()) {
334                            *o ^= *i;
335                        }
336                    }
337                } else if i_input == 0 {
338                    mul_slice_fn(c, input, output);
339                } else {
340                    mul_slice_xor_fn(c, input, output);
341                }
342            }
343        }
344    }
345
346    /// Constructs the parity shards using separate data and parity references.
347    pub fn encode_sep<T: AsRef<[u8]>, U: AsRef<[u8]> + AsMut<[u8]>>(
348        &self,
349        data: &[T],
350        parity: &mut [U],
351    ) -> Result<(), Error> {
352        if data.len() != self.data_shard_count {
353            return Err(if data.len() < self.data_shard_count {
354                Error::TooFewDataShards
355            } else {
356                Error::TooManyDataShards
357            });
358        }
359        if parity.len() != self.parity_shard_count {
360            return Err(if parity.len() < self.parity_shard_count {
361                Error::TooFewParityShards
362            } else {
363                Error::TooManyParityShards
364            });
365        }
366
367        let data_refs: Vec<&[u8]> = data.iter().map(|s| s.as_ref()).collect();
368        let mut parity_refs: Vec<&mut [u8]> = parity.iter_mut().map(|s| s.as_mut()).collect();
369
370        // Ensure all slices (data + parity) have the same non-zero length.
371        let shard_len = data_refs[0].len();
372        if shard_len == 0 {
373            return Err(Error::EmptyShard);
374        }
375        for d in &data_refs[1..] {
376            if d.len() != shard_len {
377                return Err(Error::IncorrectShardSize);
378            }
379        }
380        for p in parity_refs.iter() {
381            if p.len() != shard_len {
382                return Err(Error::IncorrectShardSize);
383            }
384        }
385
386        let parity_rows = self.get_parity_rows();
387        self.code_some_slices(&parity_rows, &data_refs, &mut parity_refs);
388
389        Ok(())
390    }
391
392    /// Constructs the parity shards incrementally using a single data shard.
393    ///
394    /// Must be called in order from `i_data = 0` to `data_shard_count - 1`.
395    /// When `i_data == 0`, parity shards are overwritten. Otherwise, results
396    /// are XOR-accumulated.
397    pub fn encode_single<T: AsRef<[u8]> + AsMut<[u8]>>(
398        &self,
399        i_data: usize,
400        shards: &mut [T],
401    ) -> Result<(), Error> {
402        if i_data >= self.data_shard_count {
403            return Err(Error::InvalidIndex);
404        }
405        if shards.len() != self.total_shard_count {
406            return Err(if shards.len() < self.total_shard_count {
407                Error::TooFewShards
408            } else {
409                Error::TooManyShards
410            });
411        }
412        Self::check_slices_uniform(shards)?;
413
414        let (data_part, parity_part) = shards.split_at_mut(self.data_shard_count);
415        let input = data_part[i_data].as_ref();
416        let mut parity_refs: Vec<&mut [u8]> = parity_part.iter_mut().map(|s| s.as_mut()).collect();
417
418        let parity_rows = self.get_parity_rows();
419        self.code_single_slice(&parity_rows, i_data, input, &mut parity_refs);
420
421        Ok(())
422    }
423
424    /// Constructs the parity shards incrementally using a single data shard (separated).
425    pub fn encode_single_sep<U: AsRef<[u8]> + AsMut<[u8]>>(
426        &self,
427        i_data: usize,
428        single_data: &[u8],
429        parity: &mut [U],
430    ) -> Result<(), Error> {
431        if i_data >= self.data_shard_count {
432            return Err(Error::InvalidIndex);
433        }
434        if parity.len() != self.parity_shard_count {
435            return Err(if parity.len() < self.parity_shard_count {
436                Error::TooFewParityShards
437            } else {
438                Error::TooManyParityShards
439            });
440        }
441        if single_data.is_empty() {
442            return Err(Error::EmptyShard);
443        }
444        for p in parity.iter() {
445            if p.as_ref().len() != single_data.len() {
446                return Err(Error::IncorrectShardSize);
447            }
448        }
449
450        let mut parity_refs: Vec<&mut [u8]> = parity.iter_mut().map(|s| s.as_mut()).collect();
451        let parity_rows = self.get_parity_rows();
452        self.code_single_slice(&parity_rows, i_data, single_data, &mut parity_refs);
453
454        Ok(())
455    }
456
457    /// Checks if the parity shards are correct.
458    pub fn verify<T: AsRef<[u8]>>(&self, shards: &[T]) -> Result<bool, Error> {
459        if shards.len() != self.total_shard_count {
460            return Err(if shards.len() < self.total_shard_count {
461                Error::TooFewShards
462            } else {
463                Error::TooManyShards
464            });
465        }
466        Self::check_slices_uniform(shards)?;
467
468        let slice_len = shards[0].as_ref().len();
469        let mut buffer: Vec<Vec<u8>> = (0..self.parity_shard_count)
470            .map(|_| vec![0u8; slice_len])
471            .collect();
472
473        let data = &shards[0..self.data_shard_count];
474        let to_check = &shards[self.data_shard_count..];
475
476        let data_refs: Vec<&[u8]> = data.iter().map(|s| s.as_ref()).collect();
477        let mut buf_refs: Vec<&mut [u8]> = buffer.iter_mut().map(|s| s.as_mut_slice()).collect();
478
479        let parity_rows = self.get_parity_rows();
480        self.code_some_slices(&parity_rows, &data_refs, &mut buf_refs);
481
482        for (computed, expected) in buffer.iter().zip(to_check.iter()) {
483            if computed.as_slice() != expected.as_ref() {
484                return Ok(false);
485            }
486        }
487
488        Ok(true)
489    }
490
491    /// Reconstructs all missing shards.
492    ///
493    /// Shards that are `None` will be reconstructed. The number of present
494    /// shards must be >= `data_shard_count`.
495    pub fn reconstruct<T: ReconstructShard>(&self, shards: &mut [T]) -> Result<(), Error> {
496        self.reconstruct_internal(shards, false)
497    }
498
499    /// Reconstructs only missing data shards.
500    pub fn reconstruct_data<T: ReconstructShard>(&self, shards: &mut [T]) -> Result<(), Error> {
501        self.reconstruct_internal(shards, true)
502    }
503
504    fn get_data_decode_matrix(
505        &self,
506        valid_indices: &[usize],
507        invalid_indices: &[usize],
508    ) -> Result<Matrix, Error> {
509        {
510            let cache = self
511                .data_decode_matrix_cache
512                .lock()
513                .unwrap_or_else(|poisoned| poisoned.into_inner());
514            if let Some(m) = cache.get(invalid_indices) {
515                return Ok(m.clone());
516            }
517        }
518
519        let mut sub_matrix = Matrix::new(self.data_shard_count, self.data_shard_count);
520        for (sub_row, &valid_index) in valid_indices.iter().enumerate() {
521            for c in 0..self.data_shard_count {
522                sub_matrix.set(sub_row, c, self.matrix.get(valid_index, c));
523            }
524        }
525
526        let data_decode_matrix = sub_matrix.invert().map_err(|_| Error::SingularMatrix)?;
527
528        {
529            let mut cache = self
530                .data_decode_matrix_cache
531                .lock()
532                .unwrap_or_else(|poisoned| poisoned.into_inner());
533            if cache.len() >= DATA_DECODE_MATRIX_CACHE_CAPACITY {
534                // Simple eviction: clear the cache when full
535                cache.clear();
536            }
537            cache.insert(invalid_indices.to_vec(), data_decode_matrix.clone());
538        }
539
540        Ok(data_decode_matrix)
541    }
542
543    fn reconstruct_internal<T: ReconstructShard>(
544        &self,
545        shards: &mut [T],
546        data_only: bool,
547    ) -> Result<(), Error> {
548        if shards.len() != self.total_shard_count {
549            return Err(if shards.len() < self.total_shard_count {
550                Error::TooFewShards
551            } else {
552                Error::TooManyShards
553            });
554        }
555
556        // Count present shards and find shard length
557        let mut number_present = 0usize;
558        let mut shard_len: Option<usize> = None;
559
560        for shard in shards.iter() {
561            if let Some(len) = shard.len() {
562                if len == 0 {
563                    return Err(Error::EmptyShard);
564                }
565                number_present += 1;
566                if let Some(old_len) = shard_len {
567                    if len != old_len {
568                        return Err(Error::IncorrectShardSize);
569                    }
570                }
571                shard_len = Some(len);
572            }
573        }
574
575        if number_present == self.total_shard_count {
576            return Ok(());
577        }
578
579        if number_present < self.data_shard_count {
580            return Err(Error::TooFewShardsPresent);
581        }
582
583        let shard_len = shard_len.expect("at least one shard present");
584
585        // Categorize shards
586        let mut valid_indices: Vec<usize> = Vec::with_capacity(self.data_shard_count);
587        let mut invalid_indices: Vec<usize> = Vec::with_capacity(self.parity_shard_count);
588
589        for (i, shard) in shards.iter().enumerate() {
590            if shard.len().is_some() {
591                if valid_indices.len() < self.data_shard_count {
592                    valid_indices.push(i);
593                }
594            } else {
595                invalid_indices.push(i);
596            }
597        }
598
599        // Initialize missing shards
600        for &i in &invalid_indices {
601            if i < self.data_shard_count || !data_only {
602                shards[i].initialize(shard_len);
603            }
604        }
605
606        let data_decode_matrix = self.get_data_decode_matrix(&valid_indices, &invalid_indices)?;
607
608        // Reconstruct missing data shards.
609        // We need to read from valid shards and write to invalid shards simultaneously.
610        // Since the index sets are disjoint, we use raw pointers to avoid borrow conflicts.
611        let missing_data_indices: Vec<usize> = invalid_indices
612            .iter()
613            .copied()
614            .filter(|&i| i < self.data_shard_count)
615            .collect();
616
617        if !missing_data_indices.is_empty() {
618            let matrix_rows: Vec<&[u8]> = missing_data_indices
619                .iter()
620                .map(|&i| data_decode_matrix.get_row(i))
621                .collect();
622
623            // For each data shard input, encode into each missing data output
624            for (i_input, &valid_idx) in valid_indices.iter().enumerate() {
625                // SAFETY: valid_idx and missing indices are disjoint sets,
626                // so we can safely read from valid_idx while writing to missing indices.
627                let input_ptr = shards[valid_idx].get().unwrap().as_ptr();
628                let input_slice = unsafe { std::slice::from_raw_parts(input_ptr, shard_len) };
629
630                for (i_out, &missing_idx) in missing_data_indices.iter().enumerate() {
631                    let c = matrix_rows[i_out][i_input];
632                    let output = shards[missing_idx].get_mut().unwrap();
633                    if i_input == 0 {
634                        self.mul_slice(c, input_slice, output);
635                    } else {
636                        self.mul_slice_xor(c, input_slice, output);
637                    }
638                }
639            }
640        }
641
642        if data_only {
643            return Ok(());
644        }
645
646        // Reconstruct missing parity shards from all data shards
647        let missing_parity_indices: Vec<usize> = invalid_indices
648            .iter()
649            .copied()
650            .filter(|&i| i >= self.data_shard_count)
651            .collect();
652
653        if !missing_parity_indices.is_empty() {
654            let parity_rows = self.get_parity_rows();
655            let matrix_rows: Vec<&[u8]> = missing_parity_indices
656                .iter()
657                .map(|&i| parity_rows[i - self.data_shard_count])
658                .collect();
659
660            for i_input in 0..self.data_shard_count {
661                // SAFETY: data shards (0..data_shard_count) are disjoint from parity shards.
662                let input_ptr = shards[i_input].get().unwrap().as_ptr();
663                let input_slice = unsafe { std::slice::from_raw_parts(input_ptr, shard_len) };
664
665                for (i_out, &missing_idx) in missing_parity_indices.iter().enumerate() {
666                    let c = matrix_rows[i_out][i_input];
667                    let output = shards[missing_idx].get_mut().unwrap();
668                    if i_input == 0 {
669                        self.mul_slice(c, input_slice, output);
670                    } else {
671                        self.mul_slice_xor(c, input_slice, output);
672                    }
673                }
674            }
675        }
676
677        Ok(())
678    }
679
680    fn check_slices_uniform<T: AsRef<[u8]>>(slices: &[T]) -> Result<(), Error> {
681        if slices.is_empty() {
682            return Ok(());
683        }
684        let size = slices[0].as_ref().len();
685        if size == 0 {
686            return Err(Error::EmptyShard);
687        }
688        for slice in slices.iter().skip(1) {
689            if slice.as_ref().len() != size {
690                return Err(Error::IncorrectShardSize);
691            }
692        }
693        Ok(())
694    }
695}
696
697/// A trait for types that can hold optional shard data for reconstruction.
698///
699/// # Safety
700///
701/// Implementations must guarantee that distinct indices in a `&mut [T]` slice
702/// yield non-overlapping memory regions from `get()`/`get_mut()`. Specifically:
703/// - `get()` on element `i` must not alias `get_mut()` on element `j` when `i != j`.
704/// - The returned slices must remain valid and not be moved/reallocated while borrows are active.
705///
706/// This is required because `reconstruct_internal` uses raw pointers to read from
707/// some shard indices while writing to others simultaneously.
708pub unsafe trait ReconstructShard {
709    /// Returns the length of the shard data, or `None` if absent.
710    fn len(&self) -> Option<usize>;
711    /// Returns `true` if the shard data is absent.
712    fn is_empty(&self) -> bool {
713        self.len().is_none()
714    }
715    /// Get an immutable reference to the shard data.
716    fn get(&self) -> Option<&[u8]>;
717    /// Get a mutable reference to the shard data.
718    fn get_mut(&mut self) -> Option<&mut [u8]>;
719    /// Initialize the shard data to the given length (zeroed).
720    fn initialize(&mut self, len: usize);
721}
722
723// SAFETY: `Option<Vec<u8>>` stores each shard in its own heap allocation,
724// so distinct indices in a slice always yield non-overlapping memory.
725unsafe impl ReconstructShard for Option<Vec<u8>> {
726    fn len(&self) -> Option<usize> {
727        self.as_ref().map(|v| v.len())
728    }
729
730    fn get(&self) -> Option<&[u8]> {
731        self.as_ref().map(|v| v.as_slice())
732    }
733
734    fn get_mut(&mut self) -> Option<&mut [u8]> {
735        self.as_mut().map(|v| v.as_mut_slice())
736    }
737
738    fn initialize(&mut self, len: usize) {
739        if self.is_none() {
740            *self = Some(vec![0u8; len]);
741        }
742    }
743}
744
745/// Bookkeeper for shard-by-shard encoding.
746///
747/// Useful for streaming use cases where data shards arrive one at a time.
748///
749/// # Example
750///
751/// ```
752/// use fec_rs::{ReedSolomon, ShardByShard};
753///
754/// let rs = ReedSolomon::new(3, 2).unwrap();
755/// let mut sbs = ShardByShard::new(&rs);
756///
757/// let mut shards: Vec<Vec<u8>> = vec![
758///     vec![0, 1, 2, 3, 4],
759///     vec![5, 6, 7, 8, 9],
760///     vec![0, 0, 0, 0, 0], // placeholder
761///     vec![0, 0, 0, 0, 0], // parity 1
762///     vec![0, 0, 0, 0, 0], // parity 2
763/// ];
764///
765/// // Encode first two data shards
766/// sbs.encode(&mut shards).unwrap();
767/// sbs.encode(&mut shards).unwrap();
768///
769/// // Fill in third data shard
770/// shards[2] = vec![10, 11, 12, 13, 14];
771///
772/// // Encode third data shard
773/// sbs.encode(&mut shards).unwrap();
774///
775/// assert!(rs.verify(&shards).unwrap());
776/// ```
777pub struct ShardByShard<'a> {
778    codec: &'a ReedSolomon,
779    cur_input: usize,
780}
781
782impl<'a> ShardByShard<'a> {
783    pub fn new(codec: &'a ReedSolomon) -> Self {
784        ShardByShard {
785            codec,
786            cur_input: 0,
787        }
788    }
789
790    /// Checks if all data shards have been encoded.
791    pub fn parity_ready(&self) -> bool {
792        self.cur_input == self.codec.data_shard_count
793    }
794
795    /// Resets the bookkeeping state.
796    ///
797    /// Returns `SBSError::LeftoverShards` if shards were encoded but parity is not ready.
798    pub fn reset(&mut self) -> Result<(), SBSError> {
799        if self.cur_input > 0 && !self.parity_ready() {
800            return Err(SBSError::LeftoverShards);
801        }
802        self.cur_input = 0;
803        Ok(())
804    }
805
806    /// Resets unconditionally.
807    pub fn reset_force(&mut self) {
808        self.cur_input = 0;
809    }
810
811    /// Returns the current data shard index to be encoded.
812    pub fn cur_input_index(&self) -> usize {
813        self.cur_input
814    }
815
816    /// Encodes the current data shard into the parity shards.
817    pub fn encode<T: AsRef<[u8]> + AsMut<[u8]>>(
818        &mut self,
819        shards: &mut [T],
820    ) -> Result<(), SBSError> {
821        if self.parity_ready() {
822            return Err(SBSError::TooManyCalls);
823        }
824        self.codec
825            .encode_single(self.cur_input, shards)
826            .map_err(SBSError::RSError)?;
827        self.cur_input += 1;
828        Ok(())
829    }
830
831    /// Encodes the current data shard using separate data and parity references.
832    pub fn encode_sep<U: AsRef<[u8]> + AsMut<[u8]>>(
833        &mut self,
834        data: &[&[u8]],
835        parity: &mut [U],
836    ) -> Result<(), SBSError> {
837        if self.parity_ready() {
838            return Err(SBSError::TooManyCalls);
839        }
840        if data.len() != self.codec.data_shard_count {
841            return Err(SBSError::RSError(
842                if data.len() < self.codec.data_shard_count {
843                    Error::TooFewDataShards
844                } else {
845                    Error::TooManyDataShards
846                },
847            ));
848        }
849        self.codec
850            .encode_single_sep(self.cur_input, data[self.cur_input], parity)
851            .map_err(SBSError::RSError)?;
852        self.cur_input += 1;
853        Ok(())
854    }
855}
856
857#[cfg(test)]
858mod tests {
859    use super::*;
860
861    #[test]
862    fn test_new_basic() {
863        let rs = ReedSolomon::new(4, 2).unwrap();
864        assert_eq!(rs.data_shard_count(), 4);
865        assert_eq!(rs.parity_shard_count(), 2);
866        assert_eq!(rs.total_shard_count(), 6);
867    }
868
869    #[test]
870    fn test_new_errors() {
871        assert_eq!(ReedSolomon::new(0, 1), Err(Error::TooFewDataShards));
872        assert_eq!(ReedSolomon::new(1, 0), Err(Error::TooFewParityShards));
873        assert_eq!(ReedSolomon::new(128, 129), Err(Error::TooManyShards));
874    }
875
876    #[test]
877    fn test_set_parity_matrix_rejects_wrong_length() {
878        let mut rs = ReedSolomon::new(4, 2).unwrap();
879        assert_eq!(
880            rs.set_parity_matrix(&[1, 2, 3]),
881            Err(Error::InvalidParityMatrix)
882        );
883    }
884
885    #[test]
886    fn test_partial_eq_reflects_parity_matrix_changes() {
887        let mut lhs = ReedSolomon::new(4, 2).unwrap();
888        let rhs = ReedSolomon::new(4, 2).unwrap();
889
890        assert_eq!(lhs, rhs);
891
892        lhs.set_parity_matrix(&[0x77, 0x40, 0x38, 0x0e, 0xc7, 0xa7, 0x0d, 0x6c])
893            .unwrap();
894
895        assert_ne!(lhs, rhs);
896    }
897
898    #[test]
899    fn test_encode_verify() {
900        let rs = ReedSolomon::new(4, 2).unwrap();
901        let mut shards: Vec<Vec<u8>> = vec![
902            vec![0, 1, 2, 3],
903            vec![4, 5, 6, 7],
904            vec![8, 9, 10, 11],
905            vec![12, 13, 14, 15],
906            vec![0, 0, 0, 0],
907            vec![0, 0, 0, 0],
908        ];
909
910        rs.encode(&mut shards).unwrap();
911        assert!(rs.verify(&shards).unwrap());
912
913        // Corrupt parity
914        shards[4][0] ^= 0xFF;
915        assert!(!rs.verify(&shards).unwrap());
916    }
917
918    #[test]
919    fn test_reconstruct_missing_data() {
920        let rs = ReedSolomon::new(4, 2).unwrap();
921        let mut shards: Vec<Vec<u8>> = vec![
922            vec![0, 1, 2, 3],
923            vec![4, 5, 6, 7],
924            vec![8, 9, 10, 11],
925            vec![12, 13, 14, 15],
926            vec![0, 0, 0, 0],
927            vec![0, 0, 0, 0],
928        ];
929        rs.encode(&mut shards).unwrap();
930
931        let original = shards.clone();
932
933        // Lose shard 0
934        let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
935        recovery[0] = None;
936
937        rs.reconstruct(&mut recovery).unwrap();
938
939        for (i, shard) in recovery.iter().enumerate() {
940            assert_eq!(shard.as_ref().unwrap(), &original[i]);
941        }
942    }
943
944    #[test]
945    fn test_reconstruct_missing_parity() {
946        let rs = ReedSolomon::new(4, 2).unwrap();
947        let mut shards: Vec<Vec<u8>> = vec![
948            vec![0, 1, 2, 3],
949            vec![4, 5, 6, 7],
950            vec![8, 9, 10, 11],
951            vec![12, 13, 14, 15],
952            vec![0, 0, 0, 0],
953            vec![0, 0, 0, 0],
954        ];
955        rs.encode(&mut shards).unwrap();
956
957        let original = shards.clone();
958
959        // Lose both parity shards
960        let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
961        recovery[4] = None;
962        recovery[5] = None;
963
964        rs.reconstruct(&mut recovery).unwrap();
965
966        for (i, shard) in recovery.iter().enumerate() {
967            assert_eq!(shard.as_ref().unwrap(), &original[i]);
968        }
969    }
970
971    #[test]
972    fn test_reconstruct_mixed() {
973        let rs = ReedSolomon::new(4, 2).unwrap();
974        let mut shards: Vec<Vec<u8>> = vec![
975            vec![0, 1, 2, 3],
976            vec![4, 5, 6, 7],
977            vec![8, 9, 10, 11],
978            vec![12, 13, 14, 15],
979            vec![0, 0, 0, 0],
980            vec![0, 0, 0, 0],
981        ];
982        rs.encode(&mut shards).unwrap();
983
984        let original = shards.clone();
985
986        // Lose one data + one parity
987        let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
988        recovery[1] = None;
989        recovery[4] = None;
990
991        rs.reconstruct(&mut recovery).unwrap();
992
993        for (i, shard) in recovery.iter().enumerate() {
994            assert_eq!(shard.as_ref().unwrap(), &original[i]);
995        }
996    }
997
998    #[test]
999    fn test_reconstruct_too_few_shards() {
1000        let rs = ReedSolomon::new(4, 2).unwrap();
1001        let mut shards: Vec<Vec<u8>> = vec![
1002            vec![0, 1, 2, 3],
1003            vec![4, 5, 6, 7],
1004            vec![8, 9, 10, 11],
1005            vec![12, 13, 14, 15],
1006            vec![0, 0, 0, 0],
1007            vec![0, 0, 0, 0],
1008        ];
1009        rs.encode(&mut shards).unwrap();
1010
1011        let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
1012        recovery[0] = None;
1013        recovery[1] = None;
1014        recovery[2] = None;
1015
1016        assert_eq!(
1017            rs.reconstruct(&mut recovery),
1018            Err(Error::TooFewShardsPresent)
1019        );
1020    }
1021
1022    #[test]
1023    fn test_shard_by_shard() {
1024        let rs = ReedSolomon::new(3, 2).unwrap();
1025        let mut sbs = ShardByShard::new(&rs);
1026
1027        let mut shards: Vec<Vec<u8>> = vec![
1028            vec![0, 1, 2, 3, 4],
1029            vec![5, 6, 7, 8, 9],
1030            vec![10, 11, 12, 13, 14],
1031            vec![0, 0, 0, 0, 0],
1032            vec![0, 0, 0, 0, 0],
1033        ];
1034
1035        let mut shards_batch = shards.clone();
1036        rs.encode(&mut shards_batch).unwrap();
1037
1038        sbs.encode(&mut shards).unwrap();
1039        sbs.encode(&mut shards).unwrap();
1040        sbs.encode(&mut shards).unwrap();
1041
1042        assert!(sbs.parity_ready());
1043        assert_eq!(shards, shards_batch);
1044    }
1045
1046    #[test]
1047    fn test_encode_sep() {
1048        let rs = ReedSolomon::new(4, 2).unwrap();
1049        let data: Vec<Vec<u8>> = vec![
1050            vec![0, 1, 2, 3],
1051            vec![4, 5, 6, 7],
1052            vec![8, 9, 10, 11],
1053            vec![12, 13, 14, 15],
1054        ];
1055        let mut parity = vec![vec![0u8; 4]; 2];
1056        rs.encode_sep(&data, &mut parity).unwrap();
1057
1058        // Verify against encode
1059        let shards: Vec<Vec<u8>> = data.iter().cloned().chain(parity.iter().cloned()).collect();
1060        assert!(rs.verify(&shards).unwrap());
1061
1062        // Also verify with direct encode
1063        let mut shards2: Vec<Vec<u8>> = data.iter().cloned().chain(vec![vec![0u8; 4]; 2]).collect();
1064        rs.encode(&mut shards2).unwrap();
1065        assert_eq!(shards, shards2);
1066    }
1067
1068    #[test]
1069    fn test_encode_single_sep_matches_batch_encode() {
1070        let rs = ReedSolomon::new(4, 2).unwrap();
1071        let data: Vec<Vec<u8>> = vec![
1072            vec![0, 1, 2, 3],
1073            vec![4, 5, 6, 7],
1074            vec![8, 9, 10, 11],
1075            vec![12, 13, 14, 15],
1076        ];
1077        let mut parity = vec![vec![0u8; 4]; 2];
1078
1079        for (i, shard) in data.iter().enumerate() {
1080            rs.encode_single_sep(i, shard, &mut parity).unwrap();
1081        }
1082
1083        let mut expected: Vec<Vec<u8>> =
1084            data.iter().cloned().chain(vec![vec![0u8; 4]; 2]).collect();
1085        rs.encode(&mut expected).unwrap();
1086
1087        assert_eq!(parity[0], expected[4]);
1088        assert_eq!(parity[1], expected[5]);
1089    }
1090
1091    #[test]
1092    fn test_shard_by_shard_encode_sep_matches_batch_encode() {
1093        let rs = ReedSolomon::new(3, 2).unwrap();
1094        let data: Vec<Vec<u8>> = vec![
1095            vec![0, 1, 2, 3, 4],
1096            vec![5, 6, 7, 8, 9],
1097            vec![10, 11, 12, 13, 14],
1098        ];
1099        let data_refs: Vec<&[u8]> = data.iter().map(Vec::as_slice).collect();
1100        let mut parity = vec![vec![0u8; 5]; 2];
1101        let mut sbs = ShardByShard::new(&rs);
1102
1103        while !sbs.parity_ready() {
1104            sbs.encode_sep(&data_refs, &mut parity).unwrap();
1105        }
1106
1107        let mut expected: Vec<Vec<u8>> =
1108            data.iter().cloned().chain(vec![vec![0u8; 5]; 2]).collect();
1109        rs.encode(&mut expected).unwrap();
1110
1111        assert_eq!(parity[0], expected[3]);
1112        assert_eq!(parity[1], expected[4]);
1113    }
1114
1115    #[test]
1116    fn test_reconstruct_returns_singular_matrix_for_invalid_parity_matrix() {
1117        let mut rs = ReedSolomon::new(4, 2).unwrap();
1118        rs.set_parity_matrix(&[1, 0, 0, 0, 1, 0, 0, 0]).unwrap();
1119
1120        let mut shards: Vec<Vec<u8>> = vec![
1121            vec![0, 1, 2, 3],
1122            vec![4, 5, 6, 7],
1123            vec![8, 9, 10, 11],
1124            vec![12, 13, 14, 15],
1125            vec![0, 0, 0, 0],
1126            vec![0, 0, 0, 0],
1127        ];
1128        rs.encode(&mut shards).unwrap();
1129
1130        let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
1131        recovery[0] = None;
1132        recovery[1] = None;
1133
1134        assert_eq!(rs.reconstruct(&mut recovery), Err(Error::SingularMatrix));
1135    }
1136
1137    #[test]
1138    fn test_set_parity_matrix_invalidates_decode_matrix_cache() {
1139        let mut rs = ReedSolomon::new(4, 2).unwrap();
1140        let mut shards: Vec<Vec<u8>> = vec![
1141            vec![0, 1, 2, 3],
1142            vec![4, 5, 6, 7],
1143            vec![8, 9, 10, 11],
1144            vec![12, 13, 14, 15],
1145            vec![0, 0, 0, 0],
1146            vec![0, 0, 0, 0],
1147        ];
1148        rs.encode(&mut shards).unwrap();
1149
1150        let mut initial_recovery: Vec<Option<Vec<u8>>> = shards.iter().cloned().map(Some).collect();
1151        initial_recovery[0] = None;
1152        initial_recovery[1] = None;
1153        rs.reconstruct(&mut initial_recovery).unwrap();
1154
1155        rs.set_parity_matrix(&[1, 0, 0, 0, 1, 0, 0, 0]).unwrap();
1156
1157        let mut recovery_after_update: Vec<Option<Vec<u8>>> =
1158            shards.into_iter().map(Some).collect();
1159        recovery_after_update[0] = None;
1160        recovery_after_update[1] = None;
1161
1162        assert_eq!(
1163            rs.reconstruct(&mut recovery_after_update),
1164            Err(Error::SingularMatrix)
1165        );
1166    }
1167
1168    #[cfg(feature = "parallel")]
1169    #[test]
1170    fn test_parallel_encode_matches_sequential_path() {
1171        let rs = ReedSolomon::new(4, 2).unwrap();
1172        let shard_len = 200_000;
1173        let mut shards: Vec<Vec<u8>> = (0..6)
1174            .map(|i| {
1175                if i < 4 {
1176                    (0..shard_len).map(|j| ((i * 17 + j) % 251) as u8).collect()
1177                } else {
1178                    vec![0u8; shard_len]
1179                }
1180            })
1181            .collect();
1182
1183        let parity_rows = rs.get_parity_rows();
1184        let mut expected = vec![vec![0u8; shard_len]; 2];
1185
1186        {
1187            let (data, _) = shards.split_at_mut(4);
1188            ReedSolomon::encode_sequential(
1189                data,
1190                &mut expected,
1191                &parity_rows,
1192                rs.mul_slice_fn,
1193                rs.mul_slice_xor_fn,
1194            );
1195        }
1196
1197        rs.encode(&mut shards).unwrap();
1198
1199        assert_eq!(shards[4], expected[0]);
1200        assert_eq!(shards[5], expected[1]);
1201    }
1202
1203    #[test]
1204    fn test_various_shard_counts() {
1205        for data in [1, 2, 5, 10, 50, 127] {
1206            for parity in [1, 2, 3, 5] {
1207                if data + parity > 256 {
1208                    continue;
1209                }
1210                let rs = ReedSolomon::new(data, parity).unwrap();
1211                let mut shards: Vec<Vec<u8>> = (0..data + parity)
1212                    .map(|i| {
1213                        if i < data {
1214                            vec![i as u8; 64]
1215                        } else {
1216                            vec![0u8; 64]
1217                        }
1218                    })
1219                    .collect();
1220
1221                rs.encode(&mut shards).unwrap();
1222                assert!(
1223                    rs.verify(&shards).unwrap(),
1224                    "Verify failed for data={data}, parity={parity}"
1225                );
1226
1227                // Reconstruct with one missing data shard
1228                let original = shards.clone();
1229                let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
1230                recovery[0] = None;
1231                rs.reconstruct(&mut recovery).unwrap();
1232                assert_eq!(
1233                    recovery[0].as_ref().unwrap(),
1234                    &original[0],
1235                    "Reconstruct failed for data={data}, parity={parity}"
1236                );
1237            }
1238        }
1239    }
1240
1241    #[test]
1242    fn test_reconstruct_data_only() {
1243        let rs = ReedSolomon::new(4, 2).unwrap();
1244        let mut shards: Vec<Vec<u8>> = vec![
1245            vec![0, 1, 2, 3],
1246            vec![4, 5, 6, 7],
1247            vec![8, 9, 10, 11],
1248            vec![12, 13, 14, 15],
1249            vec![0, 0, 0, 0],
1250            vec![0, 0, 0, 0],
1251        ];
1252        rs.encode(&mut shards).unwrap();
1253
1254        let original_data = shards[0].clone();
1255
1256        let mut recovery: Vec<Option<Vec<u8>>> = shards.into_iter().map(Some).collect();
1257        recovery[0] = None;
1258
1259        rs.reconstruct_data(&mut recovery).unwrap();
1260
1261        assert_eq!(recovery[0].as_ref().unwrap(), &original_data);
1262    }
1263}