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}