Skip to main content

commonware_cryptography/reed_solomon/rate/
rate_default.rs

1use crate::reed_solomon::{
2    engine::{Engine, GF_ORDER},
3    rate::{
4        DecoderWork, EncoderWork, HighRate, HighRateDecoder, HighRateEncoder, LowRate,
5        LowRateDecoder, LowRateEncoder, Rate, RateDecoder, RateEncoder,
6    },
7    DecoderResult, EncoderResult, Error,
8};
9use core::{cmp::Ordering, marker::PhantomData};
10
11// ======================================================================
12// FUNCTIONS - PRIVATE
13
14fn use_high_rate(original_count: usize, recovery_count: usize) -> Result<bool, Error> {
15    if original_count > GF_ORDER || recovery_count > GF_ORDER {
16        return Err(Error::UnsupportedShardCount {
17            original_count,
18            recovery_count,
19        });
20    }
21
22    let original_count_pow2 = original_count.next_power_of_two();
23    let recovery_count_pow2 = recovery_count.next_power_of_two();
24
25    let smaller_pow2 = core::cmp::min(original_count_pow2, recovery_count_pow2);
26    let larger = core::cmp::max(original_count, recovery_count);
27
28    if original_count == 0 || recovery_count == 0 || smaller_pow2 + larger > GF_ORDER {
29        return Err(Error::UnsupportedShardCount {
30            original_count,
31            recovery_count,
32        });
33    }
34
35    match original_count_pow2.cmp(&recovery_count_pow2) {
36        Ordering::Less => {
37            // The "correct" rate is generally faster here,
38            // and also must be used if `recovery_count > 32768`.
39
40            Ok(false)
41        }
42
43        Ordering::Greater => {
44            // The "correct" rate is generally faster here,
45            // and also must be used if `original_count > 32768`.
46
47            Ok(true)
48        }
49
50        Ordering::Equal => {
51            // Here counter-intuitively the "wrong" rate is generally faster
52            // in decoding if `original_count` and `recovery_count` differ a lot.
53
54            if original_count <= recovery_count {
55                // Using the "wrong" rate on purpose.
56                Ok(true)
57            } else {
58                // Using the "wrong" rate on purpose.
59                Ok(false)
60            }
61        }
62    }
63}
64
65fn validate_rate<E: Engine>(
66    original_count: usize,
67    recovery_count: usize,
68    shard_bytes: usize,
69) -> Result<bool, Error> {
70    let use_high = use_high_rate(original_count, recovery_count)?;
71    if use_high {
72        HighRate::<E>::validate(original_count, recovery_count, shard_bytes)?;
73    } else {
74        LowRate::<E>::validate(original_count, recovery_count, shard_bytes)?;
75    }
76    Ok(use_high)
77}
78
79// ======================================================================
80// DefaultRate - PUBLIC
81
82/// Reed-Solomon encoder/decoder generator using high or low rate as appropriate.
83pub struct DefaultRate<E: Engine>(PhantomData<E>);
84
85impl<E: Engine> Rate<E> for DefaultRate<E> {
86    type RateEncoder = DefaultRateEncoder<E>;
87    type RateDecoder = DefaultRateDecoder<E>;
88
89    fn supports(original_count: usize, recovery_count: usize) -> bool {
90        use_high_rate(original_count, recovery_count).is_ok()
91    }
92}
93
94// ======================================================================
95// InnerEncoder - PRIVATE
96
97#[derive(Default)]
98enum InnerEncoder<E: Engine> {
99    High(HighRateEncoder<E>),
100    Low(LowRateEncoder<E>),
101
102    // Used only after reset validation, while switching rates.
103    #[default]
104    None,
105}
106
107// ======================================================================
108// DefaultRateEncoder - PUBLIC
109
110/// Reed-Solomon encoder using high or low rate as appropriate.
111///
112/// This is basically same as [`Encoder`]
113/// except with slightly different API which allows
114/// specifying [`Engine`] and [`EncoderWork`].
115///
116/// [`Encoder`]: crate::reed_solomon::Encoder
117pub struct DefaultRateEncoder<E: Engine>(InnerEncoder<E>);
118
119impl<E: Engine> RateEncoder<E> for DefaultRateEncoder<E> {
120    type Rate = DefaultRate<E>;
121
122    fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error> {
123        match &mut self.0 {
124            InnerEncoder::High(high) => high.add_original_shard(original_shard),
125            InnerEncoder::Low(low) => low.add_original_shard(original_shard),
126            InnerEncoder::None => unreachable!(),
127        }
128    }
129
130    fn encode(&mut self) -> Result<EncoderResult<'_>, Error> {
131        match &mut self.0 {
132            InnerEncoder::High(high) => high.encode(),
133            InnerEncoder::Low(low) => low.encode(),
134            InnerEncoder::None => unreachable!(),
135        }
136    }
137
138    fn into_parts(self) -> (E, EncoderWork) {
139        match self.0 {
140            InnerEncoder::High(high) => high.into_parts(),
141            InnerEncoder::Low(low) => low.into_parts(),
142            InnerEncoder::None => unreachable!(),
143        }
144    }
145
146    fn new(
147        original_count: usize,
148        recovery_count: usize,
149        shard_bytes: usize,
150        engine: E,
151        work: Option<EncoderWork>,
152    ) -> Result<Self, Error> {
153        let inner = if use_high_rate(original_count, recovery_count)? {
154            InnerEncoder::High(HighRateEncoder::new(
155                original_count,
156                recovery_count,
157                shard_bytes,
158                engine,
159                work,
160            )?)
161        } else {
162            InnerEncoder::Low(LowRateEncoder::new(
163                original_count,
164                recovery_count,
165                shard_bytes,
166                engine,
167                work,
168            )?)
169        };
170
171        Ok(Self(inner))
172    }
173
174    fn reset(
175        &mut self,
176        original_count: usize,
177        recovery_count: usize,
178        shard_bytes: usize,
179    ) -> Result<(), Error> {
180        let new_rate_is_high = validate_rate::<E>(original_count, recovery_count, shard_bytes)?;
181
182        match &mut self.0 {
183            InnerEncoder::High(high) if new_rate_is_high => {
184                return high.reset(original_count, recovery_count, shard_bytes);
185            }
186            InnerEncoder::Low(low) if !new_rate_is_high => {
187                return low.reset(original_count, recovery_count, shard_bytes);
188            }
189            _ => {}
190        }
191
192        self.0 = match core::mem::take(&mut self.0) {
193            InnerEncoder::High(high) => {
194                let (engine, work) = high.into_parts();
195                InnerEncoder::Low(
196                    LowRateEncoder::new(
197                        original_count,
198                        recovery_count,
199                        shard_bytes,
200                        engine,
201                        Some(work),
202                    )
203                    .expect("low-rate encoder configuration was validated"),
204                )
205            }
206
207            InnerEncoder::Low(low) => {
208                let (engine, work) = low.into_parts();
209                InnerEncoder::High(
210                    HighRateEncoder::new(
211                        original_count,
212                        recovery_count,
213                        shard_bytes,
214                        engine,
215                        Some(work),
216                    )
217                    .expect("high-rate encoder configuration was validated"),
218                )
219            }
220
221            InnerEncoder::None => unreachable!(),
222        };
223
224        Ok(())
225    }
226}
227
228// ======================================================================
229// InnerDecoder - PRIVATE
230
231#[derive(Default)]
232enum InnerDecoder<E: Engine> {
233    High(HighRateDecoder<E>),
234    Low(LowRateDecoder<E>),
235
236    // Used only after reset validation, while switching rates.
237    #[default]
238    None,
239}
240
241// ======================================================================
242// DefaultRateDecoder - PUBLIC
243
244/// Reed-Solomon decoder using high or low rate as appropriate.
245///
246/// This is basically same as [`Decoder`]
247/// except with slightly different API which allows
248/// specifying [`Engine`] and [`DecoderWork`].
249///
250/// [`Decoder`]: crate::reed_solomon::Decoder
251pub struct DefaultRateDecoder<E: Engine>(InnerDecoder<E>);
252
253impl<E: Engine> RateDecoder<E> for DefaultRateDecoder<E> {
254    type Rate = DefaultRate<E>;
255
256    fn add_original_shard<T: AsRef<[u8]>>(
257        &mut self,
258        index: usize,
259        original_shard: T,
260    ) -> Result<(), Error> {
261        match &mut self.0 {
262            InnerDecoder::High(high) => high.add_original_shard(index, original_shard),
263            InnerDecoder::Low(low) => low.add_original_shard(index, original_shard),
264            InnerDecoder::None => unreachable!(),
265        }
266    }
267
268    fn add_recovery_shard<T: AsRef<[u8]>>(
269        &mut self,
270        index: usize,
271        recovery_shard: T,
272    ) -> Result<(), Error> {
273        match &mut self.0 {
274            InnerDecoder::High(high) => high.add_recovery_shard(index, recovery_shard),
275            InnerDecoder::Low(low) => low.add_recovery_shard(index, recovery_shard),
276            InnerDecoder::None => unreachable!(),
277        }
278    }
279
280    fn decode(&mut self, compute_recovery: bool) -> Result<Option<DecoderResult<'_>>, Error> {
281        match &mut self.0 {
282            InnerDecoder::High(high) => high.decode(compute_recovery),
283            InnerDecoder::Low(low) => low.decode(compute_recovery),
284            InnerDecoder::None => unreachable!(),
285        }
286    }
287
288    fn into_parts(self) -> (E, DecoderWork) {
289        match self.0 {
290            InnerDecoder::High(high) => high.into_parts(),
291            InnerDecoder::Low(low) => low.into_parts(),
292            InnerDecoder::None => unreachable!(),
293        }
294    }
295
296    fn new(
297        original_count: usize,
298        recovery_count: usize,
299        shard_bytes: usize,
300        engine: E,
301        work: Option<DecoderWork>,
302    ) -> Result<Self, Error> {
303        let inner = if use_high_rate(original_count, recovery_count)? {
304            InnerDecoder::High(HighRateDecoder::new(
305                original_count,
306                recovery_count,
307                shard_bytes,
308                engine,
309                work,
310            )?)
311        } else {
312            InnerDecoder::Low(LowRateDecoder::new(
313                original_count,
314                recovery_count,
315                shard_bytes,
316                engine,
317                work,
318            )?)
319        };
320
321        Ok(Self(inner))
322    }
323
324    fn reset(
325        &mut self,
326        original_count: usize,
327        recovery_count: usize,
328        shard_bytes: usize,
329    ) -> Result<(), Error> {
330        let new_rate_is_high = validate_rate::<E>(original_count, recovery_count, shard_bytes)?;
331
332        match &mut self.0 {
333            InnerDecoder::High(high) if new_rate_is_high => {
334                return high.reset(original_count, recovery_count, shard_bytes);
335            }
336            InnerDecoder::Low(low) if !new_rate_is_high => {
337                return low.reset(original_count, recovery_count, shard_bytes);
338            }
339            _ => {}
340        }
341
342        self.0 = match core::mem::take(&mut self.0) {
343            InnerDecoder::High(high) => {
344                let (engine, work) = high.into_parts();
345                InnerDecoder::Low(
346                    LowRateDecoder::new(
347                        original_count,
348                        recovery_count,
349                        shard_bytes,
350                        engine,
351                        Some(work),
352                    )
353                    .expect("low-rate decoder configuration was validated"),
354                )
355            }
356
357            InnerDecoder::Low(low) => {
358                let (engine, work) = low.into_parts();
359                InnerDecoder::High(
360                    HighRateDecoder::new(
361                        original_count,
362                        recovery_count,
363                        shard_bytes,
364                        engine,
365                        Some(work),
366                    )
367                    .expect("high-rate decoder configuration was validated"),
368                )
369            }
370
371            InnerDecoder::None => unreachable!(),
372        };
373
374        Ok(())
375    }
376}
377
378// ======================================================================
379// TESTS
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use crate::reed_solomon::test_util;
385
386    // ============================================================
387    // ROUNDTRIPS - SINGLE ROUND
388
389    #[test]
390    fn roundtrips_tiny() {
391        for (original_count, recovery_count, seed, recovery_hash) in test_util::DEFAULT_TINY {
392            roundtrip_single!(
393                DefaultRate,
394                *original_count,
395                *recovery_count,
396                1024,
397                recovery_hash,
398                &[test_util::range(*recovery_count, *original_count)],
399                &[test_util::range(
400                    0,
401                    core::cmp::min(*original_count, *recovery_count)
402                )],
403                *seed,
404            );
405        }
406    }
407
408    // ============================================================
409    // ROUNDTRIPS - TWO ROUNDS
410
411    #[test]
412    fn two_rounds_implicit_reset() {
413        roundtrip_two_rounds!(
414            DefaultRate,
415            false,
416            (
417                2,
418                3,
419                1024,
420                test_util::LOW_2_3,
421                &[],
422                &[test_util::index(0), test_util::index(2)],
423                123
424            ),
425            (
426                2,
427                3,
428                1024,
429                test_util::LOW_2_3_223,
430                &[test_util::index(0)],
431                &[test_util::index(1)],
432                223
433            ),
434        );
435    }
436
437    #[test]
438    fn two_rounds_reset_high_to_high() {
439        roundtrip_two_rounds!(
440            DefaultRate,
441            true,
442            (
443                3,
444                2,
445                1024,
446                test_util::HIGH_3_2,
447                &[test_util::index(1)],
448                &[test_util::index(0), test_util::index(1)],
449                132
450            ),
451            (
452                5,
453                3,
454                1024,
455                test_util::HIGH_5_3,
456                &[test_util::index(1), test_util::index(3)],
457                &[
458                    test_util::index(0),
459                    test_util::index(1),
460                    test_util::index(2)
461                ],
462                153
463            ),
464        );
465    }
466
467    #[test]
468    fn two_rounds_reset_high_to_low() {
469        roundtrip_two_rounds!(
470            DefaultRate,
471            true,
472            (
473                3,
474                2,
475                1024,
476                test_util::HIGH_3_2,
477                &[test_util::index(1)],
478                &[test_util::index(0), test_util::index(1)],
479                132
480            ),
481            (
482                2,
483                3,
484                1024,
485                test_util::LOW_2_3,
486                &[],
487                &[test_util::index(0), test_util::index(2)],
488                123
489            ),
490        );
491    }
492
493    #[test]
494    fn two_rounds_reset_low_to_high() {
495        roundtrip_two_rounds!(
496            DefaultRate,
497            true,
498            (
499                2,
500                3,
501                1024,
502                test_util::LOW_2_3,
503                &[],
504                &[test_util::index(0), test_util::index(1)],
505                123
506            ),
507            (
508                3,
509                2,
510                1024,
511                test_util::HIGH_3_2,
512                &[test_util::index(1)],
513                &[test_util::index(0), test_util::index(1)],
514                132
515            ),
516        );
517    }
518
519    #[test]
520    fn two_rounds_reset_low_to_low() {
521        roundtrip_two_rounds!(
522            DefaultRate,
523            true,
524            (
525                2,
526                3,
527                1024,
528                test_util::LOW_2_3,
529                &[],
530                &[test_util::index(0), test_util::index(2)],
531                123
532            ),
533            (
534                3,
535                5,
536                1024,
537                test_util::LOW_3_5,
538                &[],
539                &[
540                    test_util::index(0),
541                    test_util::index(2),
542                    test_util::index(4)
543                ],
544                135
545            ),
546        );
547    }
548
549    // ============================================================
550    // use_high_rate
551
552    #[test]
553    fn use_high_rate() {
554        fn err(original_count: usize, recovery_count: usize) -> Result<bool, Error> {
555            Err(Error::UnsupportedShardCount {
556                original_count,
557                recovery_count,
558            })
559        }
560
561        for (original_count, recovery_count, expected) in [
562            (0, 1, err(0, 1)),
563            (1, 0, err(1, 0)),
564            // CORRECT/WRONG RATE
565            (3, 3, Ok(true)),
566            (3, 4, Ok(true)),
567            (3, 5, Ok(false)),
568            (4, 3, Ok(false)),
569            (5, 3, Ok(true)),
570            // LOW RATE LIMIT
571            (4096, 61440, Ok(false)),
572            (4096, 61441, err(4096, 61441)),
573            (4097, 61440, err(4097, 61440)),
574            // HIGH RATE LIMIT
575            (61440, 4096, Ok(true)),
576            (61440, 4097, err(61440, 4097)),
577            (61441, 4096, err(61441, 4096)),
578            // OVERFLOW CHECK
579            (usize::MAX, usize::MAX, err(usize::MAX, usize::MAX)),
580        ] {
581            assert_eq!(
582                super::use_high_rate(original_count, recovery_count),
583                expected
584            );
585        }
586    }
587}