1use crate::reed_solomon::{
2 DecoderResult, EncoderResult, Error, Plan,
3 engine::{self, Engine, GF_MODULUS, GF_ORDER, GfElement, SHARD_CHUNK_BYTES},
4 rate::{DecoderWork, EncoderWork, Rate, RateDecoder, RateEncoder},
5};
6#[cfg(not(feature = "std"))]
7use alloc::vec;
8use core::marker::PhantomData;
9use fixedbitset::FixedBitSet;
10
11pub(crate) fn eval_erasures<E: Engine>(
22 erasures: &mut [GfElement],
23 original_count: usize,
24 recovery_count: usize,
25 received: &FixedBitSet,
26) {
27 let chunk_size = recovery_count.next_power_of_two();
28 let end = chunk_size + original_count;
29 for i in 0..recovery_count {
30 if !received[i] {
31 erasures[i] = 1;
32 }
33 }
34 erasures[recovery_count..chunk_size].fill(1);
35 for i in chunk_size..end {
36 if !received[i] {
37 erasures[i] = 1;
38 }
39 }
40 super::eval_locator::<E>(erasures, end);
41}
42
43pub struct HighRate<E: Engine>(PhantomData<E>);
45
46impl<E: Engine> Rate<E> for HighRate<E> {
47 type RateEncoder = HighRateEncoder<E>;
48 type RateDecoder = HighRateDecoder<E>;
49
50 fn supports(original_count: usize, recovery_count: usize) -> bool {
51 original_count > 0
52 && recovery_count > 0
53 && original_count < GF_ORDER
54 && recovery_count < GF_ORDER
55 && recovery_count.next_power_of_two() + original_count <= GF_ORDER
56 }
57}
58
59pub struct HighRateEncoder<E: Engine> {
61 engine: E,
62 work: EncoderWork,
65}
66
67impl<E: Engine> RateEncoder<E> for HighRateEncoder<E> {
68 type Rate = HighRate<E>;
69
70 fn validate(
71 original_count: usize,
72 recovery_count: usize,
73 shard_bytes: usize,
74 ) -> Result<(), Error> {
75 Self::Rate::validate(original_count, recovery_count, shard_bytes)?;
76 super::validate_work_size(
77 shard_bytes,
78 Self::work_count(original_count, recovery_count),
79 )
80 }
81
82 fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error> {
83 self.work.add_original_shard(original_shard)
84 }
85
86 fn encode(&mut self) -> Result<EncoderResult<'_>, Error> {
87 let (mut work, original_count, recovery_count) = self.work.encode_begin()?;
88 let chunk_size = recovery_count.next_power_of_two();
89 let engine = &self.engine;
90
91 let first_count = core::cmp::min(original_count, chunk_size);
93 work.zero(first_count..chunk_size);
94 engine::ifft_skew_end(engine, &mut work, 0, chunk_size, first_count);
95
96 if original_count > chunk_size {
97 let mut chunk_start = chunk_size;
99 while chunk_start + chunk_size <= original_count {
100 engine::ifft_skew_end(engine, &mut work, chunk_start, chunk_size, chunk_size);
101 engine::xor_within(&mut work, 0, chunk_start, chunk_size);
102 chunk_start += chunk_size;
103 }
104
105 let last_count = original_count % chunk_size;
107 if last_count > 0 {
108 work.zero(chunk_start + last_count..);
109 engine::ifft_skew_end(engine, &mut work, chunk_start, chunk_size, last_count);
110 engine::xor_within(&mut work, 0, chunk_start, chunk_size);
111 }
112 }
113
114 engine.fft(&mut work, 0, chunk_size, recovery_count, 0);
115
116 self.work.undo_last_chunk_encoding();
117
118 Ok(EncoderResult::new(&mut self.work))
119 }
120
121 fn into_parts(self) -> (E, EncoderWork) {
122 (self.engine, self.work)
123 }
124
125 fn new(
126 original_count: usize,
127 recovery_count: usize,
128 shard_bytes: usize,
129 engine: E,
130 work: Option<EncoderWork>,
131 ) -> Result<Self, Error> {
132 let mut work = work.unwrap_or_default();
133 Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
134 Ok(Self { engine, work })
135 }
136
137 fn reset(
138 &mut self,
139 original_count: usize,
140 recovery_count: usize,
141 shard_bytes: usize,
142 ) -> Result<(), Error> {
143 Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
144 }
145}
146
147impl<E: Engine> HighRateEncoder<E> {
148 fn reset_work(
152 original_count: usize,
153 recovery_count: usize,
154 shard_bytes: usize,
155 work: &mut EncoderWork,
156 ) -> Result<(), Error> {
157 Self::validate(original_count, recovery_count, shard_bytes)?;
158 work.reset(
159 original_count,
160 recovery_count,
161 shard_bytes,
162 Self::work_count(original_count, recovery_count),
163 );
164 Ok(())
165 }
166
167 fn work_count(original_count: usize, recovery_count: usize) -> usize {
177 assert!(Self::supports(original_count, recovery_count));
178
179 let chunk_size = recovery_count.next_power_of_two();
180
181 original_count.next_multiple_of(chunk_size)
182 }
183}
184
185pub struct HighRateDecoder<E: Engine> {
187 engine: E,
188 work: DecoderWork,
191}
192
193impl<E: Engine> RateDecoder<E> for HighRateDecoder<E> {
194 type Rate = HighRate<E>;
195
196 fn validate(
197 original_count: usize,
198 recovery_count: usize,
199 shard_bytes: usize,
200 ) -> Result<(), Error> {
201 Self::Rate::validate(original_count, recovery_count, shard_bytes)?;
202 super::validate_work_size(
203 shard_bytes,
204 Self::work_count(original_count, recovery_count),
205 )
206 }
207
208 fn add_original_shard<T: AsRef<[u8]>>(
209 &mut self,
210 index: usize,
211 original_shard: T,
212 ) -> Result<(), Error> {
213 self.work.add_original_shard(index, original_shard)
214 }
215
216 fn add_recovery_shard<T: AsRef<[u8]>>(
217 &mut self,
218 index: usize,
219 recovery_shard: T,
220 ) -> Result<(), Error> {
221 self.work.add_recovery_shard(index, recovery_shard)
222 }
223
224 fn decode(&mut self, compute_recovery: bool) -> Result<Option<DecoderResult<'_>>, Error> {
225 self.decode_impl(compute_recovery, None)
226 }
227
228 fn into_parts(self) -> (E, DecoderWork) {
229 (self.engine, self.work)
230 }
231
232 fn new(
233 original_count: usize,
234 recovery_count: usize,
235 shard_bytes: usize,
236 engine: E,
237 work: Option<DecoderWork>,
238 ) -> Result<Self, Error> {
239 let mut work = work.unwrap_or_default();
240 Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
241 Ok(Self { engine, work })
242 }
243
244 fn reset(
245 &mut self,
246 original_count: usize,
247 recovery_count: usize,
248 shard_bytes: usize,
249 ) -> Result<(), Error> {
250 Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
251 }
252}
253
254impl<E: Engine> HighRateDecoder<E> {
255 pub(crate) fn decode_with_plan(
261 &mut self,
262 compute_recovery: bool,
263 plan: &Plan,
264 ) -> Result<Option<DecoderResult<'_>>, Error> {
265 self.decode_impl(compute_recovery, Some(plan))
266 }
267}
268
269impl<E: Engine> HighRateDecoder<E> {
270 fn decode_impl(
275 &mut self,
276 compute_recovery: bool,
277 plan: Option<&Plan>,
278 ) -> Result<Option<DecoderResult<'_>>, Error> {
279 if let Some(plan) = plan {
280 self.work.validate_plan(plan, true)?;
281 }
282 let Some((mut work, original_count, recovery_count, received)) =
283 self.work.decode_begin()?
284 else {
285 self.work.reset_received();
288 return Ok(None);
289 };
290
291 let chunk_size = recovery_count.next_power_of_two();
292 let original_end = chunk_size + original_count;
293 let work_count = work.len();
294
295 let mut owned_erasures;
297 #[expect(
298 clippy::option_if_let_else,
299 reason = "The fallback initializes a scratch buffer and returns a borrow of it."
300 )]
301 let erasures: &[GfElement] = match plan {
302 Some(plan) => plan.coefficients(),
303 None => {
304 owned_erasures = vec![0; original_end.next_power_of_two()];
305 eval_erasures::<E>(
306 &mut owned_erasures,
307 original_count,
308 recovery_count,
309 received,
310 );
311 &owned_erasures
312 }
313 };
314
315 for i in 0..recovery_count {
322 if received[i] {
323 self.engine.mul(&mut work[i], erasures[i]);
324 } else {
325 work[i].fill([0; SHARD_CHUNK_BYTES]);
326 }
327 }
328 work.zero(recovery_count..chunk_size);
329 for i in chunk_size..original_end {
330 if received[i] {
331 self.engine.mul(&mut work[i], erasures[i]);
332 } else {
333 work[i].fill([0; SHARD_CHUNK_BYTES]);
334 }
335 }
336 work.zero(original_end..);
337
338 self.engine.ifft(&mut work, 0, work_count, original_end, 0);
340 engine::formal_derivative(&mut work);
341 self.engine.fft(&mut work, 0, work_count, original_end, 0);
342
343 for i in chunk_size..original_end {
345 if !received[i] {
346 self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
347 }
348 }
349
350 if compute_recovery {
355 for i in 0..recovery_count {
356 if !received[i] {
357 self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
358 }
359 }
360 }
361
362 self.work.undo_last_chunk_encoding();
364 if compute_recovery {
365 self.work.undo_last_chunk_encoding_recovery();
366 }
367
368 Ok(Some(DecoderResult::new(&mut self.work)))
369 }
370
371 fn reset_work(
375 original_count: usize,
376 recovery_count: usize,
377 shard_bytes: usize,
378 work: &mut DecoderWork,
379 ) -> Result<(), Error> {
380 Self::validate(original_count, recovery_count, shard_bytes)?;
381
382 work.reset(
385 original_count,
386 recovery_count,
387 shard_bytes,
388 recovery_count.next_power_of_two(),
389 0,
390 Self::work_count(original_count, recovery_count),
391 );
392
393 Ok(())
394 }
395
396 fn work_count(original_count: usize, recovery_count: usize) -> usize {
405 assert!(Self::supports(original_count, recovery_count));
406
407 (recovery_count.next_power_of_two() + original_count).next_power_of_two()
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::reed_solomon::test_util;
415 use commonware_macros::test_group;
416
417 #[test]
418 fn roundtrip_all_originals_missing() {
419 roundtrip_single!(
420 HighRate,
421 3,
422 3,
423 1024,
424 test_util::EITHER_3_3,
425 &[],
426 &[test_util::range(0, 3)],
427 133,
428 );
429 }
430
431 #[test]
432 fn roundtrip_no_originals_missing() {
433 roundtrip_single!(
434 HighRate,
435 3,
436 2,
437 1024,
438 test_util::HIGH_3_2,
439 &[test_util::range(0, 3)],
440 &[],
441 132
442 );
443 }
444
445 #[test]
446 fn roundtrips_tiny() {
447 for (original_count, recovery_count, seed, recovery_hash) in test_util::HIGH_TINY {
448 roundtrip_single!(
449 HighRate,
450 *original_count,
451 *recovery_count,
452 1024,
453 recovery_hash,
454 &[test_util::range(*recovery_count, *original_count)],
455 &[test_util::range(
456 0,
457 core::cmp::min(*original_count, *recovery_count)
458 )],
459 *seed,
460 );
461 }
462 }
463
464 #[test_group("slow")]
465 #[test]
466 fn roundtrip_3000_30000() {
467 roundtrip_single!(
468 HighRate,
469 3000,
470 30000,
471 crate::reed_solomon::SHARD_CHUNK_BYTES,
472 test_util::HIGH_3000_30000_14,
473 &[],
474 &[test_util::range(0, 3000)],
475 14,
476 );
477 }
478
479 #[test_group("slow")]
480 #[test]
481 fn roundtrip_32768_32768() {
482 roundtrip_single!(
483 HighRate,
484 32768,
485 32768,
486 crate::reed_solomon::SHARD_CHUNK_BYTES,
487 test_util::EITHER_32768_32768_11,
488 &[],
489 &[test_util::range(0, 32768)],
490 11,
491 );
492 }
493
494 #[test_group("slow")]
495 #[test]
496 fn roundtrip_60000_3000() {
497 roundtrip_single!(
498 HighRate,
499 60000,
500 3000,
501 crate::reed_solomon::SHARD_CHUNK_BYTES,
502 test_util::HIGH_60000_3000_12,
503 &[test_util::range(3000, 60000)],
504 &[test_util::range(0, 3000)],
505 12,
506 );
507 }
508
509 #[test]
510 fn roundtrip_34000_2000_shard_size_8() {
511 roundtrip_single!(
512 HighRate,
513 34000,
514 2000,
515 8,
516 test_util::HIGH_34000_2000_123_8,
517 &[test_util::range(0, 32000)],
518 &[test_util::range(0, 2000)],
519 123
520 );
521 }
522
523 #[test]
524 fn two_rounds_implicit_reset() {
525 roundtrip_two_rounds!(
526 HighRate,
527 false,
528 (
529 3,
530 2,
531 1024,
532 test_util::HIGH_3_2,
533 &[test_util::index(1)],
534 &[test_util::index(0), test_util::index(1)],
535 132
536 ),
537 (
538 3,
539 2,
540 1024,
541 test_util::HIGH_3_2_232,
542 &[test_util::index(0)],
543 &[test_util::index(0), test_util::index(1)],
544 232
545 ),
546 );
547 }
548
549 #[test]
550 fn two_rounds_explicit_reset() {
551 roundtrip_two_rounds!(
552 HighRate,
553 true,
554 (
555 3,
556 2,
557 1024,
558 test_util::HIGH_3_2,
559 &[test_util::index(1)],
560 &[test_util::index(0), test_util::index(1)],
561 132
562 ),
563 (
564 5,
565 2,
566 1024,
567 test_util::HIGH_5_2,
568 &[
569 test_util::index(0),
570 test_util::index(2),
571 test_util::index(4)
572 ],
573 &[test_util::index(0), test_util::index(1)],
574 152
575 ),
576 );
577 }
578
579 mod high_rate {
580 use crate::reed_solomon::{
581 Error, SHARD_CHUNK_BYTES,
582 engine::Scalar,
583 rate::{HighRate, Rate},
584 };
585
586 #[test]
587 fn decoder() {
588 assert_eq!(
589 HighRate::<Scalar>::decoder(4096, 61440, SHARD_CHUNK_BYTES, Scalar::new(), None)
590 .err(),
591 Some(Error::UnsupportedShardCount {
592 original_count: 4096,
593 recovery_count: 61440,
594 })
595 );
596
597 assert!(
598 HighRate::<Scalar>::decoder(61440, 4096, SHARD_CHUNK_BYTES, Scalar::new(), None)
599 .is_ok()
600 );
601 }
602
603 #[test]
604 fn encoder() {
605 assert_eq!(
606 HighRate::<Scalar>::encoder(4096, 61440, SHARD_CHUNK_BYTES, Scalar::new(), None)
607 .err(),
608 Some(Error::UnsupportedShardCount {
609 original_count: 4096,
610 recovery_count: 61440,
611 })
612 );
613
614 assert!(
615 HighRate::<Scalar>::encoder(61440, 4096, SHARD_CHUNK_BYTES, Scalar::new(), None)
616 .is_ok()
617 );
618 }
619
620 #[test]
621 fn supports() {
622 assert!(!HighRate::<Scalar>::supports(0, 1));
623 assert!(!HighRate::<Scalar>::supports(1, 0));
624
625 assert!(!HighRate::<Scalar>::supports(4096, 61440));
626
627 assert!(HighRate::<Scalar>::supports(61440, 4096));
628 assert!(!HighRate::<Scalar>::supports(61440, 4097));
629 assert!(!HighRate::<Scalar>::supports(61441, 4096));
630
631 assert!(!HighRate::<Scalar>::supports(usize::MAX, usize::MAX));
632 }
633
634 #[test]
635 fn validate() {
636 assert_eq!(
637 HighRate::<Scalar>::validate(1, 1, 123).err(),
638 Some(Error::InvalidShardSize { shard_bytes: 123 })
639 );
640
641 assert_eq!(
642 HighRate::<Scalar>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
643 Some(Error::UnsupportedShardCount {
644 original_count: 4096,
645 recovery_count: 61440,
646 })
647 );
648
649 assert!(HighRate::<Scalar>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
650 }
651 }
652
653 mod high_rate_encoder {
654 use crate::reed_solomon::{
655 Error, SHARD_CHUNK_BYTES,
656 engine::Scalar,
657 rate::{HighRateEncoder, RateEncoder},
658 };
659
660 test_rate_encoder_errors! {HighRateEncoder}
661
662 #[test]
663 fn supports() {
664 assert!(!HighRateEncoder::<Scalar>::supports(4096, 61440));
665 assert!(HighRateEncoder::<Scalar>::supports(61440, 4096));
666 }
667
668 #[test]
669 fn validate() {
670 assert_eq!(
671 HighRateEncoder::<Scalar>::validate(1, 1, 123).err(),
672 Some(Error::InvalidShardSize { shard_bytes: 123 })
673 );
674
675 assert_eq!(
676 HighRateEncoder::<Scalar>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
677 Some(Error::UnsupportedShardCount {
678 original_count: 4096,
679 recovery_count: 61440,
680 })
681 );
682
683 assert!(HighRateEncoder::<Scalar>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
684 }
685
686 #[test]
687 fn work_count() {
688 assert_eq!(HighRateEncoder::<Scalar>::work_count(1, 1), 1);
689 assert_eq!(HighRateEncoder::<Scalar>::work_count(4096, 1024), 4096);
690 assert_eq!(HighRateEncoder::<Scalar>::work_count(4097, 1024), 5120);
691 assert_eq!(HighRateEncoder::<Scalar>::work_count(4097, 1025), 6144);
692 assert_eq!(HighRateEncoder::<Scalar>::work_count(32768, 32768), 32768);
693 }
694 }
695
696 mod high_rate_decoder {
697 use crate::reed_solomon::{
698 Error, SHARD_CHUNK_BYTES,
699 engine::Scalar,
700 rate::{HighRateDecoder, RateDecoder},
701 };
702
703 test_rate_decoder_errors! {HighRateDecoder}
704
705 #[test]
706 fn supports() {
707 assert!(!HighRateDecoder::<Scalar>::supports(4096, 61440));
708 assert!(HighRateDecoder::<Scalar>::supports(61440, 4096));
709 }
710
711 #[test]
712 fn validate() {
713 assert_eq!(
714 HighRateDecoder::<Scalar>::validate(1, 1, 123).err(),
715 Some(Error::InvalidShardSize { shard_bytes: 123 })
716 );
717
718 assert_eq!(
719 HighRateDecoder::<Scalar>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
720 Some(Error::UnsupportedShardCount {
721 original_count: 4096,
722 recovery_count: 61440,
723 })
724 );
725
726 assert!(HighRateDecoder::<Scalar>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
727 }
728
729 #[test]
730 fn work_count() {
731 assert_eq!(HighRateDecoder::<Scalar>::work_count(1, 1), 2);
732 assert_eq!(HighRateDecoder::<Scalar>::work_count(2048, 1025), 4096);
733 assert_eq!(HighRateDecoder::<Scalar>::work_count(2049, 1025), 8192);
734 assert_eq!(HighRateDecoder::<Scalar>::work_count(3072, 1024), 4096);
735 assert_eq!(HighRateDecoder::<Scalar>::work_count(3073, 1024), 8192);
736 assert_eq!(HighRateDecoder::<Scalar>::work_count(32768, 32768), 65536);
737 }
738 }
739}