Skip to main content

reed_solomon_simd/
reed_solomon.rs

1use crate::{
2    engine::DefaultEngine,
3    rate::{DefaultRate, DefaultRateDecoder, DefaultRateEncoder, Rate, RateDecoder, RateEncoder},
4    DecoderResult, EncoderResult, Error,
5};
6
7// ======================================================================
8// ReedSolomonEncoder - PUBLIC
9
10/// Reed-Solomon encoder using [`DefaultEngine`] and [`DefaultRate`].
11///
12/// [`DefaultEngine`]: crate::engine::DefaultEngine
13pub struct ReedSolomonEncoder(DefaultRateEncoder<DefaultEngine>);
14
15impl ReedSolomonEncoder {
16    /// Adds one original shard to the encoder.
17    ///
18    /// Original shards have indexes `0..original_count` corresponding to the order
19    /// in which they are added and these same indexes must be used when decoding.
20    ///
21    /// See [basic usage](crate#basic-usage) for an example.
22    pub fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error> {
23        self.0.add_original_shard(original_shard)
24    }
25
26    /// Encodes the added original shards returning [`EncoderResult`]
27    /// which contains the generated recovery shards.
28    ///
29    /// When returned [`EncoderResult`] is dropped the encoder is
30    /// automatically [`reset`] and ready for new round of encoding.
31    ///
32    /// See [basic usage](crate#basic-usage) for an example.
33    ///
34    /// [`reset`]: ReedSolomonEncoder::reset
35    pub fn encode(&mut self) -> Result<EncoderResult<'_>, Error> {
36        self.0.encode()
37    }
38
39    /// Creates new encoder with given configuration
40    /// and allocates required working space.
41    ///
42    /// See [basic usage](crate#basic-usage) for an example.
43    pub fn new(
44        original_count: usize,
45        recovery_count: usize,
46        shard_bytes: usize,
47    ) -> Result<Self, Error> {
48        Ok(Self(DefaultRateEncoder::new(
49            original_count,
50            recovery_count,
51            shard_bytes,
52            DefaultEngine::new(),
53            None,
54        )?))
55    }
56
57    /// Resets encoder to given configuration.
58    ///
59    /// - Added original shards are forgotten.
60    /// - Existing working space is re-used if it's large enough
61    ///   or re-allocated otherwise.
62    pub fn reset(
63        &mut self,
64        original_count: usize,
65        recovery_count: usize,
66        shard_bytes: usize,
67    ) -> Result<(), Error> {
68        self.0.reset(original_count, recovery_count, shard_bytes)
69    }
70
71    /// Returns `true` if given `original_count` / `recovery_count`
72    /// combination is supported.
73    ///
74    /// # Examples
75    ///
76    /// ```rust
77    /// use reed_solomon_simd::ReedSolomonEncoder;
78    ///
79    /// assert_eq!(ReedSolomonEncoder::supports(60_000, 4_000), true);
80    /// assert_eq!(ReedSolomonEncoder::supports(60_000, 5_000), false);
81    /// ```
82    pub fn supports(original_count: usize, recovery_count: usize) -> bool {
83        DefaultRate::<DefaultEngine>::supports(original_count, recovery_count)
84    }
85}
86
87// ======================================================================
88// ReedSolomonDecoder - PUBLIC
89
90/// Reed-Solomon decoder using [`DefaultEngine`] and [`DefaultRate`].
91///
92/// [`DefaultEngine`]: crate::engine::DefaultEngine
93pub struct ReedSolomonDecoder(DefaultRateDecoder<DefaultEngine>);
94
95impl ReedSolomonDecoder {
96    /// Adds one original shard to the decoder.
97    ///
98    /// - Shards can be added in any order.
99    /// - Index must be the same that was used in encoding.
100    ///
101    /// See [basic usage](crate#basic-usage) for an example.
102    pub fn add_original_shard<T: AsRef<[u8]>>(
103        &mut self,
104        index: usize,
105        original_shard: T,
106    ) -> Result<(), Error> {
107        self.0.add_original_shard(index, original_shard)
108    }
109
110    /// Adds one recovery shard to the decoder.
111    ///
112    /// - Shards can be added in any order.
113    /// - Index must be the same that was used in encoding.
114    ///
115    /// See [basic usage](crate#basic-usage) for an example.
116    pub fn add_recovery_shard<T: AsRef<[u8]>>(
117        &mut self,
118        index: usize,
119        recovery_shard: T,
120    ) -> Result<(), Error> {
121        self.0.add_recovery_shard(index, recovery_shard)
122    }
123
124    /// Decodes the added shards returning [`DecoderResult`]
125    /// which contains the restored original shards.
126    ///
127    /// When returned [`DecoderResult`] is dropped the decoder is
128    /// automatically [`reset`] and ready for new round of decoding.
129    ///
130    /// See [basic usage](crate#basic-usage) for an example.
131    ///
132    /// [`reset`]: ReedSolomonDecoder::reset
133    pub fn decode(&mut self) -> Result<DecoderResult<'_>, Error> {
134        self.0.decode()
135    }
136
137    /// Creates new decoder with given configuration
138    /// and allocates required working space.
139    ///
140    /// See [basic usage](crate#basic-usage) for an example.
141    pub fn new(
142        original_count: usize,
143        recovery_count: usize,
144        shard_bytes: usize,
145    ) -> Result<Self, Error> {
146        Ok(Self(DefaultRateDecoder::new(
147            original_count,
148            recovery_count,
149            shard_bytes,
150            DefaultEngine::new(),
151            None,
152        )?))
153    }
154
155    /// Resets decoder to given configuration.
156    ///
157    /// - Added shards are forgotten.
158    /// - Existing working space is re-used if it's large enough
159    ///   or re-allocated otherwise.
160    pub fn reset(
161        &mut self,
162        original_count: usize,
163        recovery_count: usize,
164        shard_bytes: usize,
165    ) -> Result<(), Error> {
166        self.0.reset(original_count, recovery_count, shard_bytes)
167    }
168
169    /// Returns `true` if given `original_count` / `recovery_count`
170    /// combination is supported.
171    ///
172    /// # Examples
173    ///
174    /// ```rust
175    /// use reed_solomon_simd::ReedSolomonDecoder;
176    ///
177    /// assert_eq!(ReedSolomonDecoder::supports(60_000, 4_000), true);
178    /// assert_eq!(ReedSolomonDecoder::supports(60_000, 5_000), false);
179    /// ```
180    pub fn supports(original_count: usize, recovery_count: usize) -> bool {
181        DefaultRate::<DefaultEngine>::supports(original_count, recovery_count)
182    }
183}
184
185// ======================================================================
186// TESTS
187
188#[cfg(test)]
189mod tests {
190    use alloc::collections::BTreeMap;
191    #[cfg(not(feature = "std"))]
192    use alloc::vec::Vec;
193
194    use fixedbitset::FixedBitSet;
195
196    use super::*;
197    use crate::test_util;
198
199    // ============================================================
200    // HELPERS
201
202    fn roundtrip(
203        encoder: &mut ReedSolomonEncoder,
204        decoder: &mut ReedSolomonDecoder,
205        original_count: usize,
206        recovery_hash: &str,
207        decoder_original: &[usize],
208        decoder_recovery: &[usize],
209        seed: u8,
210    ) {
211        let original = test_util::generate_original(original_count, 1024, seed);
212
213        for original in &original {
214            encoder.add_original_shard(original).unwrap();
215        }
216
217        let result = encoder.encode().unwrap();
218        let recovery: Vec<_> = result.recovery_iter().collect();
219
220        test_util::assert_hash(&recovery, recovery_hash);
221
222        let mut original_received = FixedBitSet::with_capacity(original_count);
223
224        for i in decoder_original {
225            decoder.add_original_shard(*i, &original[*i]).unwrap();
226            original_received.set(*i, true);
227        }
228
229        for i in decoder_recovery {
230            decoder.add_recovery_shard(*i, recovery[*i]).unwrap();
231        }
232
233        let result = decoder.decode().unwrap();
234        let restored: BTreeMap<_, _> = result.restored_original_iter().collect();
235
236        for i in 0..original_count {
237            if !original_received[i] {
238                assert_eq!(restored[&i], original[i]);
239            }
240        }
241    }
242
243    // ============================================================
244    // ROUNDTRIP - TWO ROUNDS
245
246    #[test]
247    fn roundtrip_two_rounds_reset_low_to_high() {
248        let mut encoder = ReedSolomonEncoder::new(2, 3, 1024).unwrap();
249        let mut decoder = ReedSolomonDecoder::new(2, 3, 1024).unwrap();
250
251        roundtrip(
252            &mut encoder,
253            &mut decoder,
254            2,
255            test_util::LOW_2_3,
256            &[],
257            &[0, 1],
258            123,
259        );
260
261        encoder.reset(3, 2, 1024).unwrap();
262        decoder.reset(3, 2, 1024).unwrap();
263
264        roundtrip(
265            &mut encoder,
266            &mut decoder,
267            3,
268            test_util::HIGH_3_2,
269            &[1],
270            &[0, 1],
271            132,
272        );
273    }
274
275    // ==================================================
276    // supports
277
278    #[test]
279    fn supports() {
280        assert!(ReedSolomonEncoder::supports(4096, 61440));
281        assert!(ReedSolomonEncoder::supports(61440, 4096));
282
283        assert!(ReedSolomonDecoder::supports(4096, 61440));
284        assert!(ReedSolomonDecoder::supports(61440, 4096));
285    }
286}