Skip to main content

commonware_cryptography/reed_solomon/rate/
rate_default.rs

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