1use crate::reed_solomon::{
2 engine::{self, Engine, GF_MODULUS, GF_ORDER, SHARD_CHUNK_BYTES},
3 rate::{DecoderWork, EncoderWork, Rate, RateDecoder, RateEncoder},
4 DecoderResult, EncoderResult, Error,
5};
6use core::marker::PhantomData;
7
8pub struct LowRate<E: Engine>(PhantomData<E>);
13
14impl<E: Engine> Rate<E> for LowRate<E> {
15 type RateEncoder = LowRateEncoder<E>;
16 type RateDecoder = LowRateDecoder<E>;
17
18 fn supports(original_count: usize, recovery_count: usize) -> bool {
19 original_count > 0
20 && recovery_count > 0
21 && original_count < GF_ORDER
22 && recovery_count < GF_ORDER
23 && original_count.next_power_of_two() + recovery_count <= GF_ORDER
24 }
25}
26
27pub struct LowRateEncoder<E: Engine> {
32 engine: E,
33 work: EncoderWork,
34}
35
36impl<E: Engine> RateEncoder<E> for LowRateEncoder<E> {
37 type Rate = LowRate<E>;
38
39 fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error> {
40 self.work.add_original_shard(original_shard)
41 }
42
43 fn encode(&mut self) -> Result<EncoderResult<'_>, Error> {
44 let (mut work, original_count, recovery_count) = self.work.encode_begin()?;
45 let chunk_size = original_count.next_power_of_two();
46 let engine = &self.engine;
47
48 work.zero(original_count..chunk_size);
51
52 engine.ifft(&mut work, 0, chunk_size, original_count, 0);
55
56 let mut chunk_start = chunk_size;
59 while chunk_start < recovery_count {
60 work.copy_within(0, chunk_start, chunk_size);
61 chunk_start += chunk_size;
62 }
63
64 let mut chunk_start = 0;
67 while chunk_start + chunk_size <= recovery_count {
68 engine::fft_skew_end(engine, &mut work, chunk_start, chunk_size, chunk_size);
69 chunk_start += chunk_size;
70 }
71
72 let last_count = recovery_count % chunk_size;
75 if last_count > 0 {
76 engine::fft_skew_end(engine, &mut work, chunk_start, chunk_size, last_count);
77 }
78
79 self.work.undo_last_chunk_encoding();
82
83 Ok(EncoderResult::new(&mut self.work))
86 }
87
88 fn into_parts(self) -> (E, EncoderWork) {
89 (self.engine, self.work)
90 }
91
92 fn new(
93 original_count: usize,
94 recovery_count: usize,
95 shard_bytes: usize,
96 engine: E,
97 work: Option<EncoderWork>,
98 ) -> Result<Self, Error> {
99 let mut work = work.unwrap_or_default();
100 Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
101 Ok(Self { engine, work })
102 }
103
104 fn reset(
105 &mut self,
106 original_count: usize,
107 recovery_count: usize,
108 shard_bytes: usize,
109 ) -> Result<(), Error> {
110 Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
111 }
112}
113
114impl<E: Engine> LowRateEncoder<E> {
118 fn reset_work(
119 original_count: usize,
120 recovery_count: usize,
121 shard_bytes: usize,
122 work: &mut EncoderWork,
123 ) -> Result<(), Error> {
124 Self::validate(original_count, recovery_count, shard_bytes)?;
125 work.reset(
126 original_count,
127 recovery_count,
128 shard_bytes,
129 Self::work_count(original_count, recovery_count),
130 );
131 Ok(())
132 }
133
134 fn work_count(original_count: usize, recovery_count: usize) -> usize {
135 assert!(Self::supports(original_count, recovery_count));
136
137 let chunk_size = original_count.next_power_of_two();
138
139 recovery_count.next_multiple_of(chunk_size)
140 }
141}
142
143pub struct LowRateDecoder<E: Engine> {
148 engine: E,
149 work: DecoderWork,
150}
151
152impl<E: Engine> RateDecoder<E> for LowRateDecoder<E> {
153 type Rate = LowRate<E>;
154
155 fn add_original_shard<T: AsRef<[u8]>>(
156 &mut self,
157 index: usize,
158 original_shard: T,
159 ) -> Result<(), Error> {
160 self.work.add_original_shard(index, original_shard)
161 }
162
163 fn add_recovery_shard<T: AsRef<[u8]>>(
164 &mut self,
165 index: usize,
166 recovery_shard: T,
167 ) -> Result<(), Error> {
168 self.work.add_recovery_shard(index, recovery_shard)
169 }
170
171 fn decode(&mut self, compute_recovery: bool) -> Result<Option<DecoderResult<'_>>, Error> {
172 let Some((mut work, original_count, recovery_count, received)) =
173 self.work.decode_begin()?
174 else {
175 self.work.reset_received();
178 return Ok(None);
179 };
180
181 let chunk_size = original_count.next_power_of_two();
182 let recovery_end = chunk_size + recovery_count;
183 let work_count = work.len();
184
185 let mut erasures = [0; GF_ORDER];
188
189 for i in 0..original_count {
190 if !received[i] {
191 erasures[i] = 1;
192 }
193 }
194
195 for i in chunk_size..recovery_end {
196 if !received[i] {
197 erasures[i] = 1;
198 }
199 }
200
201 erasures[recovery_end..].fill(1);
202
203 E::eval_poly(&mut erasures, GF_ORDER);
206
207 for i in 0..original_count {
215 if received[i] {
216 self.engine.mul(&mut work[i], erasures[i]);
217 } else {
218 work[i].fill([0; SHARD_CHUNK_BYTES]);
219 }
220 }
221
222 work.zero(original_count..chunk_size);
223
224 for i in chunk_size..recovery_end {
225 if received[i] {
226 self.engine.mul(&mut work[i], erasures[i]);
227 } else {
228 work[i].fill([0; SHARD_CHUNK_BYTES]);
229 }
230 }
231
232 work.zero(recovery_end..);
233
234 self.engine.ifft(&mut work, 0, work_count, recovery_end, 0);
237 engine::formal_derivative(&mut work);
238 self.engine.fft(&mut work, 0, work_count, recovery_end, 0);
239
240 for i in 0..original_count {
243 if !received[i] {
244 self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
245 }
246 }
247
248 if compute_recovery {
256 for i in chunk_size..recovery_end {
257 if !received[i] {
258 self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
259 }
260 }
261 }
262
263 self.work.undo_last_chunk_encoding();
266 if compute_recovery {
267 self.work.undo_last_chunk_encoding_recovery();
268 }
269
270 Ok(Some(DecoderResult::new(&mut self.work)))
273 }
274
275 fn into_parts(self) -> (E, DecoderWork) {
276 (self.engine, self.work)
277 }
278
279 fn new(
280 original_count: usize,
281 recovery_count: usize,
282 shard_bytes: usize,
283 engine: E,
284 work: Option<DecoderWork>,
285 ) -> Result<Self, Error> {
286 let mut work = work.unwrap_or_default();
287 Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
288 Ok(Self { engine, work })
289 }
290
291 fn reset(
292 &mut self,
293 original_count: usize,
294 recovery_count: usize,
295 shard_bytes: usize,
296 ) -> Result<(), Error> {
297 Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
298 }
299}
300
301impl<E: Engine> LowRateDecoder<E> {
305 fn reset_work(
306 original_count: usize,
307 recovery_count: usize,
308 shard_bytes: usize,
309 work: &mut DecoderWork,
310 ) -> Result<(), Error> {
311 Self::validate(original_count, recovery_count, shard_bytes)?;
312
313 work.reset(
316 original_count,
317 recovery_count,
318 shard_bytes,
319 0,
320 original_count.next_power_of_two(),
321 Self::work_count(original_count, recovery_count),
322 );
323
324 Ok(())
325 }
326
327 fn work_count(original_count: usize, recovery_count: usize) -> usize {
328 assert!(Self::supports(original_count, recovery_count));
329
330 (original_count.next_power_of_two() + recovery_count).next_power_of_two()
331 }
332}
333
334#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::reed_solomon::test_util;
341
342 #[test]
346 fn roundtrip_all_originals_missing() {
347 roundtrip_single!(
348 LowRate,
349 3,
350 3,
351 1024,
352 test_util::EITHER_3_3,
353 &[],
354 &[test_util::range(0, 3)],
355 133
356 );
357 }
358
359 #[test]
360 fn roundtrip_no_originals_missing() {
361 roundtrip_single!(
362 LowRate,
363 2,
364 3,
365 1024,
366 test_util::LOW_2_3,
367 &[test_util::index(0), test_util::index(1)],
368 &[],
369 123
370 );
371 }
372
373 #[test]
374 fn roundtrips_tiny() {
375 for (original_count, recovery_count, seed, recovery_hash) in test_util::LOW_TINY {
376 roundtrip_single!(
377 LowRate,
378 *original_count,
379 *recovery_count,
380 1024,
381 recovery_hash,
382 &[test_util::range(*recovery_count, *original_count)],
383 &[test_util::range(
384 0,
385 core::cmp::min(*original_count, *recovery_count)
386 )],
387 *seed,
388 );
389 }
390 }
391
392 #[test]
393 #[ignore]
394 fn roundtrip_3000_60000() {
395 roundtrip_single!(
396 LowRate,
397 3000,
398 60000,
399 crate::reed_solomon::SHARD_CHUNK_BYTES,
400 test_util::LOW_3000_60000_13,
401 &[],
402 &[test_util::range(0, 3000)],
403 13,
404 );
405 }
406
407 #[test]
408 #[ignore]
409 fn roundtrip_30000_3000() {
410 roundtrip_single!(
411 LowRate,
412 30000,
413 3000,
414 crate::reed_solomon::SHARD_CHUNK_BYTES,
415 test_util::LOW_30000_3000_15,
416 &[test_util::range(3000, 30000)],
417 &[test_util::range(0, 3000)],
418 15,
419 );
420 }
421
422 #[test]
423 #[ignore]
424 fn roundtrip_32768_32768() {
425 roundtrip_single!(
426 LowRate,
427 32768,
428 32768,
429 crate::reed_solomon::SHARD_CHUNK_BYTES,
430 test_util::EITHER_32768_32768_11,
431 &[],
432 &[test_util::range(0, 32768)],
433 11,
434 );
435 }
436
437 #[test]
438 fn roundtrip_2000_34000_shard_size_8() {
439 roundtrip_single!(
440 LowRate,
441 2000,
442 34000,
443 8,
444 test_util::LOW_2000_34000_123_8,
445 &[test_util::range(0, 2000)],
446 &[test_util::range(0, 32000)],
447 123
448 );
449 }
450
451 #[test]
455 fn two_rounds_implicit_reset() {
456 roundtrip_two_rounds!(
457 LowRate,
458 false,
459 (
460 2,
461 3,
462 1024,
463 test_util::LOW_2_3,
464 &[],
465 &[test_util::index(0), test_util::index(2)],
466 123
467 ),
468 (
469 2,
470 3,
471 1024,
472 test_util::LOW_2_3_223,
473 &[],
474 &[test_util::index(1), test_util::index(2)],
475 223
476 ),
477 );
478 }
479
480 #[test]
481 fn two_rounds_explicit_reset() {
482 roundtrip_two_rounds!(
483 LowRate,
484 true,
485 (
486 2,
487 3,
488 1024,
489 test_util::LOW_2_3,
490 &[],
491 &[test_util::index(0), test_util::index(2)],
492 123
493 ),
494 (
495 2,
496 5,
497 1024,
498 test_util::LOW_2_5,
499 &[],
500 &[test_util::index(0), test_util::index(4)],
501 125
502 ),
503 );
504 }
505
506 mod low_rate {
510 use crate::reed_solomon::{
511 engine::NoSimd,
512 rate::{LowRate, Rate},
513 Error, SHARD_CHUNK_BYTES,
514 };
515
516 #[test]
517 fn decoder() {
518 assert!(LowRate::<NoSimd>::decoder(
519 4096,
520 61440,
521 SHARD_CHUNK_BYTES,
522 NoSimd::new(),
523 None
524 )
525 .is_ok());
526
527 assert_eq!(
528 LowRate::<NoSimd>::decoder(61440, 4096, SHARD_CHUNK_BYTES, NoSimd::new(), None)
529 .err(),
530 Some(Error::UnsupportedShardCount {
531 original_count: 61440,
532 recovery_count: 4096,
533 })
534 );
535 }
536
537 #[test]
538 fn encoder() {
539 assert!(LowRate::<NoSimd>::encoder(
540 4096,
541 61440,
542 SHARD_CHUNK_BYTES,
543 NoSimd::new(),
544 None
545 )
546 .is_ok());
547
548 assert_eq!(
549 LowRate::<NoSimd>::encoder(61440, 4096, SHARD_CHUNK_BYTES, NoSimd::new(), None)
550 .err(),
551 Some(Error::UnsupportedShardCount {
552 original_count: 61440,
553 recovery_count: 4096,
554 })
555 );
556 }
557
558 #[test]
559 fn supports() {
560 assert!(!LowRate::<NoSimd>::supports(0, 1));
561 assert!(!LowRate::<NoSimd>::supports(1, 0));
562
563 assert!(LowRate::<NoSimd>::supports(4096, 61440));
564 assert!(!LowRate::<NoSimd>::supports(4096, 61441));
565 assert!(!LowRate::<NoSimd>::supports(4097, 61440));
566
567 assert!(!LowRate::<NoSimd>::supports(61440, 4096));
568
569 assert!(!LowRate::<NoSimd>::supports(usize::MAX, usize::MAX));
570 }
571
572 #[test]
573 fn validate() {
574 assert_eq!(
575 LowRate::<NoSimd>::validate(1, 1, 123).err(),
576 Some(Error::InvalidShardSize { shard_bytes: 123 })
577 );
578
579 assert!(LowRate::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).is_ok());
580
581 assert_eq!(
582 LowRate::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).err(),
583 Some(Error::UnsupportedShardCount {
584 original_count: 61440,
585 recovery_count: 4096,
586 })
587 );
588 }
589 }
590
591 mod low_rate_encoder {
595 use crate::reed_solomon::{
596 engine::NoSimd,
597 rate::{LowRateEncoder, RateEncoder},
598 Error, SHARD_CHUNK_BYTES,
599 };
600
601 test_rate_encoder_errors! {LowRateEncoder}
605
606 #[test]
610 fn supports() {
611 assert!(LowRateEncoder::<NoSimd>::supports(4096, 61440));
612 assert!(!LowRateEncoder::<NoSimd>::supports(61440, 4096));
613 }
614
615 #[test]
619 fn validate() {
620 assert_eq!(
621 LowRateEncoder::<NoSimd>::validate(1, 1, 123).err(),
622 Some(Error::InvalidShardSize { shard_bytes: 123 })
623 );
624
625 assert!(LowRateEncoder::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).is_ok());
626
627 assert_eq!(
628 LowRateEncoder::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).err(),
629 Some(Error::UnsupportedShardCount {
630 original_count: 61440,
631 recovery_count: 4096,
632 })
633 );
634 }
635
636 #[test]
640 fn work_count() {
641 assert_eq!(LowRateEncoder::<NoSimd>::work_count(1, 1), 1);
642 assert_eq!(LowRateEncoder::<NoSimd>::work_count(1024, 4096), 4096);
643 assert_eq!(LowRateEncoder::<NoSimd>::work_count(1024, 4097), 5120);
644 assert_eq!(LowRateEncoder::<NoSimd>::work_count(1025, 4097), 6144);
645 assert_eq!(LowRateEncoder::<NoSimd>::work_count(32768, 32768), 32768);
646 }
647 }
648
649 mod low_rate_decoder {
653 use crate::reed_solomon::{
654 engine::NoSimd,
655 rate::{LowRateDecoder, RateDecoder},
656 Error, SHARD_CHUNK_BYTES,
657 };
658
659 test_rate_decoder_errors! {LowRateDecoder}
663
664 #[test]
668 fn supports() {
669 assert!(LowRateDecoder::<NoSimd>::supports(4096, 61440));
670 assert!(!LowRateDecoder::<NoSimd>::supports(61440, 4096));
671 }
672
673 #[test]
677 fn validate() {
678 assert_eq!(
679 LowRateDecoder::<NoSimd>::validate(1, 1, 123).err(),
680 Some(Error::InvalidShardSize { shard_bytes: 123 })
681 );
682
683 assert!(LowRateDecoder::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).is_ok());
684
685 assert_eq!(
686 LowRateDecoder::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).err(),
687 Some(Error::UnsupportedShardCount {
688 original_count: 61440,
689 recovery_count: 4096,
690 })
691 );
692 }
693
694 #[test]
698 fn work_count() {
699 assert_eq!(LowRateDecoder::<NoSimd>::work_count(1, 1), 2);
700 assert_eq!(LowRateDecoder::<NoSimd>::work_count(1024, 3072), 4096);
701 assert_eq!(LowRateDecoder::<NoSimd>::work_count(1024, 3073), 8192);
702 assert_eq!(LowRateDecoder::<NoSimd>::work_count(1025, 2048), 4096);
703 assert_eq!(LowRateDecoder::<NoSimd>::work_count(1025, 2049), 8192);
704 assert_eq!(LowRateDecoder::<NoSimd>::work_count(32768, 32768), 65536);
705 }
706 }
707}