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
9pub 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
28pub 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 work.zero(original_count..chunk_size);
52
53 engine.ifft(&mut work, 0, chunk_size, original_count, 0);
56
57 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 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 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 self.work.undo_last_chunk_encoding();
83
84 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
115impl<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
144pub 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 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 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 E::eval_poly(&mut erasures, GF_ORDER);
205
206 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 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 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 self.work.undo_last_chunk_encoding();
250
251 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
282impl<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.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#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::test_util;
322
323 #[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 #[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 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 mod low_rate_encoder {
516 use crate::{
517 engine::NoSimd,
518 rate::{LowRateEncoder, RateEncoder},
519 Error,
520 };
521
522 test_rate_encoder_errors! {LowRateEncoder}
526
527 #[test]
531 fn supports() {
532 assert!(LowRateEncoder::<NoSimd>::supports(4096, 61440));
533 assert!(!LowRateEncoder::<NoSimd>::supports(61440, 4096));
534 }
535
536 #[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 #[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 mod low_rate_decoder {
574 use crate::{
575 engine::NoSimd,
576 rate::{LowRateDecoder, RateDecoder},
577 Error,
578 };
579
580 test_rate_decoder_errors! {LowRateDecoder}
584
585 #[test]
589 fn supports() {
590 assert!(LowRateDecoder::<NoSimd>::supports(4096, 61440));
591 assert!(!LowRateDecoder::<NoSimd>::supports(61440, 4096));
592 }
593
594 #[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 #[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}