reed_solomon_simd/rate/
rate_low.rs

1use std::marker::PhantomData;
2
3use crate::{
4    engine::{self, Engine, GF_MODULUS, GF_ORDER},
5    rate::{DecoderWork, EncoderWork, Rate, RateDecoder, RateEncoder},
6    DecoderResult, EncoderResult, Error,
7};
8
9// ======================================================================
10// LowRate - PUBLIC
11
12/// Reed-Solomon encoder/decoder generator using only low rate.
13pub struct LowRate<E: Engine>(PhantomData<E>);
14
15impl<E: Engine> Rate<E> for LowRate<E> {
16    type RateEncoder = LowRateEncoder<E>;
17    type RateDecoder = LowRateDecoder<E>;
18
19    fn supports(original_count: usize, recovery_count: usize) -> bool {
20        original_count > 0
21            && recovery_count > 0
22            && original_count < GF_ORDER
23            && recovery_count < GF_ORDER
24            && original_count.next_power_of_two() + recovery_count <= GF_ORDER
25    }
26}
27
28// ======================================================================
29// LowRateEncoder - PUBLIC
30
31/// Reed-Solomon encoder using only low rate.
32pub struct LowRateEncoder<E: Engine> {
33    engine: E,
34    work: EncoderWork,
35}
36
37impl<E: Engine> RateEncoder<E> for LowRateEncoder<E> {
38    type Rate = LowRate<E>;
39
40    fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error> {
41        self.work.add_original_shard(original_shard)
42    }
43
44    fn encode(&mut self) -> Result<EncoderResult, Error> {
45        let (mut work, original_count, recovery_count) = self.work.encode_begin()?;
46        let chunk_size = original_count.next_power_of_two();
47        let engine = &self.engine;
48
49        // ZEROPAD ORIGINAL
50
51        work.zero(original_count..chunk_size);
52
53        // IFFT - ORIGINAL
54
55        engine.ifft(&mut work, 0, chunk_size, original_count, 0);
56
57        // COPY IFFT RESULT TO OTHER CHUNKS
58
59        let mut chunk_start = chunk_size;
60        while chunk_start < recovery_count {
61            work.copy_within(0, chunk_start, chunk_size);
62            chunk_start += chunk_size;
63        }
64
65        // FFT - FULL CHUNKS
66
67        let mut chunk_start = 0;
68        while chunk_start + chunk_size <= recovery_count {
69            engine::fft_skew_end(engine, &mut work, chunk_start, chunk_size, chunk_size);
70            chunk_start += chunk_size;
71        }
72
73        // FFT - FINAL PARTIAL CHUNK
74
75        let last_count = recovery_count % chunk_size;
76        if last_count > 0 {
77            engine::fft_skew_end(engine, &mut work, chunk_start, chunk_size, last_count);
78        }
79
80        // UNDO LAST CHUNK ENCODING
81
82        self.work.undo_last_chunk_encoding();
83
84        // DONE
85
86        Ok(EncoderResult::new(&mut self.work))
87    }
88
89    fn into_parts(self) -> (E, EncoderWork) {
90        (self.engine, self.work)
91    }
92
93    fn new(
94        original_count: usize,
95        recovery_count: usize,
96        shard_bytes: usize,
97        engine: E,
98        work: Option<EncoderWork>,
99    ) -> Result<Self, Error> {
100        let mut work = work.unwrap_or_default();
101        Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
102        Ok(Self { engine, work })
103    }
104
105    fn reset(
106        &mut self,
107        original_count: usize,
108        recovery_count: usize,
109        shard_bytes: usize,
110    ) -> Result<(), Error> {
111        Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
112    }
113}
114
115// ======================================================================
116// LowRateEncoder - PRIVATE
117
118impl<E: Engine> LowRateEncoder<E> {
119    fn reset_work(
120        original_count: usize,
121        recovery_count: usize,
122        shard_bytes: usize,
123        work: &mut EncoderWork,
124    ) -> Result<(), Error> {
125        Self::validate(original_count, recovery_count, shard_bytes)?;
126        work.reset(
127            original_count,
128            recovery_count,
129            shard_bytes,
130            Self::work_count(original_count, recovery_count),
131        );
132        Ok(())
133    }
134
135    fn work_count(original_count: usize, recovery_count: usize) -> usize {
136        debug_assert!(Self::supports(original_count, recovery_count));
137
138        let chunk_size = original_count.next_power_of_two();
139
140        recovery_count.next_multiple_of(chunk_size)
141    }
142}
143
144// ======================================================================
145// LowRateDecoder - PUBLIC
146
147/// Reed-Solomon decoder using only low rate.
148pub struct LowRateDecoder<E: Engine> {
149    engine: E,
150    work: DecoderWork,
151}
152
153impl<E: Engine> RateDecoder<E> for LowRateDecoder<E> {
154    type Rate = LowRate<E>;
155
156    fn add_original_shard<T: AsRef<[u8]>>(
157        &mut self,
158        index: usize,
159        original_shard: T,
160    ) -> Result<(), Error> {
161        self.work.add_original_shard(index, original_shard)
162    }
163
164    fn add_recovery_shard<T: AsRef<[u8]>>(
165        &mut self,
166        index: usize,
167        recovery_shard: T,
168    ) -> Result<(), Error> {
169        self.work.add_recovery_shard(index, recovery_shard)
170    }
171
172    fn decode(&mut self) -> Result<DecoderResult, Error> {
173        let Some((mut work, original_count, recovery_count, received)) =
174            self.work.decode_begin()?
175        else {
176            // Nothing to do, original data is complete.
177            return Ok(DecoderResult::new(&mut self.work));
178        };
179
180        let chunk_size = original_count.next_power_of_two();
181        let recovery_end = chunk_size + recovery_count;
182        let work_count = work.len();
183
184        // ERASURE LOCATIONS
185
186        let mut erasures = [0; GF_ORDER];
187
188        for i in 0..original_count {
189            if !received[i] {
190                erasures[i] = 1;
191            }
192        }
193
194        for i in chunk_size..recovery_end {
195            if !received[i] {
196                erasures[i] = 1;
197            }
198        }
199
200        erasures[recovery_end..].fill(1);
201
202        // EVALUATE POLYNOMIAL
203
204        E::eval_poly(&mut erasures, GF_ORDER);
205
206        // MULTIPLY SHARDS
207
208        // work[               .. original_count] = original * erasures
209        // work[original_count .. chunk_size    ] = 0
210        // work[chunk_size     .. original_end  ] = recovery * erasures
211        // work[recovery_end   ..               ] = 0
212
213        for i in 0..original_count {
214            if received[i] {
215                self.engine.mul(&mut work[i], erasures[i]);
216            } else {
217                work[i].fill([0; 64]);
218            }
219        }
220
221        work.zero(original_count..chunk_size);
222
223        for i in chunk_size..recovery_end {
224            if received[i] {
225                self.engine.mul(&mut work[i], erasures[i]);
226            } else {
227                work[i].fill([0; 64]);
228            }
229        }
230
231        work.zero(recovery_end..);
232
233        // IFFT / FORMAL DERIVATIVE / FFT
234
235        self.engine.ifft(&mut work, 0, work_count, recovery_end, 0);
236        engine::formal_derivative(&mut work);
237        self.engine.fft(&mut work, 0, work_count, recovery_end, 0);
238
239        // REVEAL ERASURES
240
241        for i in 0..original_count {
242            if !received[i] {
243                self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
244            }
245        }
246
247        // UNDO LAST CHUNK ENCODING
248
249        self.work.undo_last_chunk_encoding();
250
251        // DONE
252
253        Ok(DecoderResult::new(&mut self.work))
254    }
255
256    fn into_parts(self) -> (E, DecoderWork) {
257        (self.engine, self.work)
258    }
259
260    fn new(
261        original_count: usize,
262        recovery_count: usize,
263        shard_bytes: usize,
264        engine: E,
265        work: Option<DecoderWork>,
266    ) -> Result<Self, Error> {
267        let mut work = work.unwrap_or_default();
268        Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
269        Ok(Self { engine, work })
270    }
271
272    fn reset(
273        &mut self,
274        original_count: usize,
275        recovery_count: usize,
276        shard_bytes: usize,
277    ) -> Result<(), Error> {
278        Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
279    }
280}
281
282// ======================================================================
283// LowRateDecoder - PRIVATE
284
285impl<E: Engine> LowRateDecoder<E> {
286    fn reset_work(
287        original_count: usize,
288        recovery_count: usize,
289        shard_bytes: usize,
290        work: &mut DecoderWork,
291    ) -> Result<(), Error> {
292        Self::validate(original_count, recovery_count, shard_bytes)?;
293
294        // work[..original_count     ]  =  original
295        // work[original_count_pow2..]  =  recovery
296        work.reset(
297            original_count,
298            recovery_count,
299            shard_bytes,
300            0,
301            original_count.next_power_of_two(),
302            Self::work_count(original_count, recovery_count),
303        );
304
305        Ok(())
306    }
307
308    fn work_count(original_count: usize, recovery_count: usize) -> usize {
309        debug_assert!(Self::supports(original_count, recovery_count));
310
311        (original_count.next_power_of_two() + recovery_count).next_power_of_two()
312    }
313}
314
315// ======================================================================
316// TESTS
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use crate::test_util;
322
323    // ============================================================
324    // ROUNDTRIPS - SINGLE ROUND
325
326    #[test]
327    fn roundtrip_all_originals_missing() {
328        roundtrip_single!(
329            LowRate,
330            3,
331            3,
332            1024,
333            test_util::EITHER_3_3,
334            &[],
335            &[0..3],
336            133
337        );
338    }
339
340    #[test]
341    fn roundtrip_no_originals_missing() {
342        roundtrip_single!(LowRate, 2, 3, 1024, test_util::LOW_2_3, &[0, 1], &[], 123);
343    }
344
345    #[test]
346    fn roundtrips_tiny() {
347        for (original_count, recovery_count, seed, recovery_hash) in test_util::LOW_TINY {
348            roundtrip_single!(
349                LowRate,
350                *original_count,
351                *recovery_count,
352                1024,
353                recovery_hash,
354                &[*recovery_count..*original_count],
355                &[0..std::cmp::min(*original_count, *recovery_count)],
356                *seed,
357            );
358        }
359    }
360
361    #[test]
362    #[ignore]
363    fn roundtrip_3000_60000() {
364        roundtrip_single!(
365            LowRate,
366            3000,
367            60000,
368            64,
369            test_util::LOW_3000_60000_13,
370            &[],
371            &[0..3000],
372            13,
373        );
374    }
375
376    #[test]
377    #[ignore]
378    fn roundtrip_30000_3000() {
379        roundtrip_single!(
380            LowRate,
381            30000,
382            3000,
383            64,
384            test_util::LOW_30000_3000_15,
385            &[3000..30000],
386            &[0..3000],
387            15,
388        );
389    }
390
391    #[test]
392    #[ignore]
393    fn roundtrip_32768_32768() {
394        roundtrip_single!(
395            LowRate,
396            32768,
397            32768,
398            64,
399            test_util::EITHER_32768_32768_11,
400            &[],
401            &[0..32768],
402            11,
403        );
404    }
405
406    #[test]
407    fn roundtrip_2000_34000_shard_size_8() {
408        roundtrip_single!(
409            LowRate,
410            2000,
411            34000,
412            8,
413            test_util::LOW_2000_34000_123_8,
414            &[0..2000],
415            &[0..32000],
416            123
417        );
418    }
419
420    // ============================================================
421    // ROUNDTRIPS - TWO ROUNDS
422
423    #[test]
424    fn two_rounds_implicit_reset() {
425        roundtrip_two_rounds!(
426            LowRate,
427            false,
428            (2, 3, 1024, test_util::LOW_2_3, &[], &[0, 2], 123),
429            (2, 3, 1024, test_util::LOW_2_3_223, &[], &[1, 2], 223),
430        );
431    }
432
433    #[test]
434    fn two_rounds_explicit_reset() {
435        roundtrip_two_rounds!(
436            LowRate,
437            true,
438            (2, 3, 1024, test_util::LOW_2_3, &[], &[0, 2], 123),
439            (2, 5, 1024, test_util::LOW_2_5, &[], &[0, 4], 125),
440        );
441    }
442
443    // ============================================================
444    // LowRate
445
446    mod low_rate {
447        use crate::{
448            engine::NoSimd,
449            rate::{LowRate, Rate},
450            Error,
451        };
452
453        #[test]
454        fn decoder() {
455            assert!(LowRate::<NoSimd>::decoder(4096, 61440, 64, NoSimd::new(), None).is_ok());
456
457            assert_eq!(
458                LowRate::<NoSimd>::decoder(61440, 4096, 64, NoSimd::new(), None).err(),
459                Some(Error::UnsupportedShardCount {
460                    original_count: 61440,
461                    recovery_count: 4096,
462                })
463            );
464        }
465
466        #[test]
467        fn encoder() {
468            assert!(LowRate::<NoSimd>::encoder(4096, 61440, 64, NoSimd::new(), None).is_ok());
469
470            assert_eq!(
471                LowRate::<NoSimd>::encoder(61440, 4096, 64, NoSimd::new(), None).err(),
472                Some(Error::UnsupportedShardCount {
473                    original_count: 61440,
474                    recovery_count: 4096,
475                })
476            );
477        }
478
479        #[test]
480        fn supports() {
481            assert!(!LowRate::<NoSimd>::supports(0, 1));
482            assert!(!LowRate::<NoSimd>::supports(1, 0));
483
484            assert!(LowRate::<NoSimd>::supports(4096, 61440));
485            assert!(!LowRate::<NoSimd>::supports(4096, 61441));
486            assert!(!LowRate::<NoSimd>::supports(4097, 61440));
487
488            assert!(!LowRate::<NoSimd>::supports(61440, 4096));
489
490            assert!(!LowRate::<NoSimd>::supports(usize::MAX, usize::MAX));
491        }
492
493        #[test]
494        fn validate() {
495            assert_eq!(
496                LowRate::<NoSimd>::validate(1, 1, 123).err(),
497                Some(Error::InvalidShardSize { shard_bytes: 123 })
498            );
499
500            assert!(LowRate::<NoSimd>::validate(4096, 61440, 64).is_ok());
501
502            assert_eq!(
503                LowRate::<NoSimd>::validate(61440, 4096, 64).err(),
504                Some(Error::UnsupportedShardCount {
505                    original_count: 61440,
506                    recovery_count: 4096,
507                })
508            );
509        }
510    }
511
512    // ============================================================
513    // LowRateEncoder
514
515    mod low_rate_encoder {
516        use crate::{
517            engine::NoSimd,
518            rate::{LowRateEncoder, RateEncoder},
519            Error,
520        };
521
522        // ==================================================
523        // ERRORS
524
525        test_rate_encoder_errors! {LowRateEncoder}
526
527        // ==================================================
528        // supports
529
530        #[test]
531        fn supports() {
532            assert!(LowRateEncoder::<NoSimd>::supports(4096, 61440));
533            assert!(!LowRateEncoder::<NoSimd>::supports(61440, 4096));
534        }
535
536        // ==================================================
537        // validate
538
539        #[test]
540        fn validate() {
541            assert_eq!(
542                LowRateEncoder::<NoSimd>::validate(1, 1, 123).err(),
543                Some(Error::InvalidShardSize { shard_bytes: 123 })
544            );
545
546            assert!(LowRateEncoder::<NoSimd>::validate(4096, 61440, 64).is_ok());
547
548            assert_eq!(
549                LowRateEncoder::<NoSimd>::validate(61440, 4096, 64).err(),
550                Some(Error::UnsupportedShardCount {
551                    original_count: 61440,
552                    recovery_count: 4096,
553                })
554            );
555        }
556
557        // ==================================================
558        // work_count
559
560        #[test]
561        fn work_count() {
562            assert_eq!(LowRateEncoder::<NoSimd>::work_count(1, 1), 1);
563            assert_eq!(LowRateEncoder::<NoSimd>::work_count(1024, 4096), 4096);
564            assert_eq!(LowRateEncoder::<NoSimd>::work_count(1024, 4097), 5120);
565            assert_eq!(LowRateEncoder::<NoSimd>::work_count(1025, 4097), 6144);
566            assert_eq!(LowRateEncoder::<NoSimd>::work_count(32768, 32768), 32768);
567        }
568    }
569
570    // ============================================================
571    // LowRateDecoder
572
573    mod low_rate_decoder {
574        use crate::{
575            engine::NoSimd,
576            rate::{LowRateDecoder, RateDecoder},
577            Error,
578        };
579
580        // ==================================================
581        // ERRORS
582
583        test_rate_decoder_errors! {LowRateDecoder}
584
585        // ==================================================
586        // supports
587
588        #[test]
589        fn supports() {
590            assert!(LowRateDecoder::<NoSimd>::supports(4096, 61440));
591            assert!(!LowRateDecoder::<NoSimd>::supports(61440, 4096));
592        }
593
594        // ==================================================
595        // validate
596
597        #[test]
598        fn validate() {
599            assert_eq!(
600                LowRateDecoder::<NoSimd>::validate(1, 1, 123).err(),
601                Some(Error::InvalidShardSize { shard_bytes: 123 })
602            );
603
604            assert!(LowRateDecoder::<NoSimd>::validate(4096, 61440, 64).is_ok());
605
606            assert_eq!(
607                LowRateDecoder::<NoSimd>::validate(61440, 4096, 64).err(),
608                Some(Error::UnsupportedShardCount {
609                    original_count: 61440,
610                    recovery_count: 4096,
611                })
612            );
613        }
614
615        // ==================================================
616        // work_count
617
618        #[test]
619        fn work_count() {
620            assert_eq!(LowRateDecoder::<NoSimd>::work_count(1, 1), 2);
621            assert_eq!(LowRateDecoder::<NoSimd>::work_count(1024, 3072), 4096);
622            assert_eq!(LowRateDecoder::<NoSimd>::work_count(1024, 3073), 8192);
623            assert_eq!(LowRateDecoder::<NoSimd>::work_count(1025, 2048), 4096);
624            assert_eq!(LowRateDecoder::<NoSimd>::work_count(1025, 2049), 8192);
625            assert_eq!(LowRateDecoder::<NoSimd>::work_count(32768, 32768), 65536);
626        }
627    }
628}