1#![doc = include_str!(concat!(env!("OUT_DIR"), "/README-rustdocified.md"))]
2#![deny(missing_docs)]
3
4use std::{collections::HashMap, fmt};
5
6pub use crate::{
7 decoder_result::{DecoderResult, RestoredOriginal},
8 encoder_result::{EncoderResult, Recovery},
9 reed_solomon::{ReedSolomonDecoder, ReedSolomonEncoder},
10};
11
12#[cfg(test)]
13#[macro_use]
14mod test_util;
15
16mod decoder_result;
17mod encoder_result;
18mod reed_solomon;
19
20pub mod algorithm {
21 #![doc = include_str!("../algorithm.md")]
22}
23pub mod engine;
24pub mod rate;
25
26#[derive(Clone, Copy, Debug, PartialEq)]
31pub enum Error {
32 DifferentShardSize {
41 shard_bytes: usize,
43 got: usize,
45 },
46
47 DuplicateOriginalShardIndex {
49 index: usize,
51 },
52
53 DuplicateRecoveryShardIndex {
55 index: usize,
57 },
58
59 InvalidOriginalShardIndex {
62 original_count: usize,
64 index: usize,
66 },
67
68 InvalidRecoveryShardIndex {
71 recovery_count: usize,
73 index: usize,
75 },
76
77 InvalidShardSize {
87 shard_bytes: usize,
89 },
90
91 NotEnoughShards {
96 original_count: usize,
98 original_received_count: usize,
100 recovery_received_count: usize,
102 },
103
104 TooFewOriginalShards {
106 original_count: usize,
108 original_received_count: usize,
110 },
111
112 TooManyOriginalShards {
114 original_count: usize,
116 },
117
118 UnsupportedShardCount {
120 original_count: usize,
122 recovery_count: usize,
124 },
125}
126
127impl fmt::Display for Error {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 Error::DifferentShardSize { shard_bytes, got } => {
134 write!(
135 f,
136 "different shard size: expected {} bytes, got {} bytes",
137 shard_bytes, got
138 )
139 }
140
141 Error::DuplicateOriginalShardIndex { index } => {
142 write!(f, "duplicate original shard index: {}", index)
143 }
144
145 Error::DuplicateRecoveryShardIndex { index } => {
146 write!(f, "duplicate recovery shard index: {}", index)
147 }
148
149 Error::InvalidOriginalShardIndex {
150 original_count,
151 index,
152 } => {
153 write!(
154 f,
155 "invalid original shard index: {} >= original_count {}",
156 index, original_count,
157 )
158 }
159
160 Error::InvalidRecoveryShardIndex {
161 recovery_count,
162 index,
163 } => {
164 write!(
165 f,
166 "invalid recovery shard index: {} >= recovery_count {}",
167 index, recovery_count,
168 )
169 }
170
171 Error::InvalidShardSize { shard_bytes } => {
172 write!(
173 f,
174 "invalid shard size: {} bytes (must non-zero and multiple of 64)",
175 shard_bytes
176 )
177 }
178
179 Error::NotEnoughShards {
180 original_count,
181 original_received_count,
182 recovery_received_count,
183 } => {
184 write!(
185 f,
186 "not enough shards: {} original + {} recovery < {} original_count",
187 original_received_count, recovery_received_count, original_count,
188 )
189 }
190
191 Error::TooFewOriginalShards {
192 original_count,
193 original_received_count,
194 } => {
195 write!(
196 f,
197 "too few original shards: got {} shards while original_count is {}",
198 original_received_count, original_count
199 )
200 }
201
202 Error::TooManyOriginalShards { original_count } => {
203 write!(
204 f,
205 "too many original shards: got more than original_count ({}) shards",
206 original_count
207 )
208 }
209
210 Error::UnsupportedShardCount {
211 original_count,
212 recovery_count,
213 } => {
214 write!(
215 f,
216 "unsupported shard count: {} original shards with {} recovery shards",
217 original_count, recovery_count
218 )
219 }
220 }
221 }
222}
223
224impl std::error::Error for Error {}
228
229pub fn encode<T>(
243 original_count: usize,
244 recovery_count: usize,
245 original: T,
246) -> Result<Vec<Vec<u8>>, Error>
247where
248 T: IntoIterator,
249 T::Item: AsRef<[u8]>,
250{
251 if !ReedSolomonEncoder::supports(original_count, recovery_count) {
252 return Err(Error::UnsupportedShardCount {
253 original_count,
254 recovery_count,
255 });
256 }
257
258 let mut original = original.into_iter();
259
260 let (shard_bytes, first) = if let Some(first) = original.next() {
261 (first.as_ref().len(), first)
262 } else {
263 return Err(Error::TooFewOriginalShards {
264 original_count,
265 original_received_count: 0,
266 });
267 };
268
269 let mut encoder = ReedSolomonEncoder::new(original_count, recovery_count, shard_bytes)?;
270
271 encoder.add_original_shard(first)?;
272 for original in original {
273 encoder.add_original_shard(original)?;
274 }
275
276 let result = encoder.encode()?;
277
278 Ok(result.recovery_iter().map(|s| s.to_vec()).collect())
279}
280
281pub fn decode<O, R, OT, RT>(
288 original_count: usize,
289 recovery_count: usize,
290 original: O,
291 recovery: R,
292) -> Result<HashMap<usize, Vec<u8>>, Error>
293where
294 O: IntoIterator<Item = (usize, OT)>,
295 R: IntoIterator<Item = (usize, RT)>,
296 OT: AsRef<[u8]>,
297 RT: AsRef<[u8]>,
298{
299 if !ReedSolomonDecoder::supports(original_count, recovery_count) {
300 return Err(Error::UnsupportedShardCount {
301 original_count,
302 recovery_count,
303 });
304 }
305
306 let original = original.into_iter();
307 let mut recovery = recovery.into_iter();
308
309 let (shard_bytes, first_recovery) = if let Some(first_recovery) = recovery.next() {
310 (first_recovery.1.as_ref().len(), first_recovery)
311 } else {
312 let original_received_count = original.count();
315 if original_received_count == original_count {
316 return Ok(HashMap::new());
318 } else {
319 return Err(Error::NotEnoughShards {
320 original_count,
321 original_received_count,
322 recovery_received_count: 0,
323 });
324 }
325 };
326
327 let mut decoder = ReedSolomonDecoder::new(original_count, recovery_count, shard_bytes)?;
328
329 for (index, original) in original {
330 decoder.add_original_shard(index, original)?;
331 }
332
333 decoder.add_recovery_shard(first_recovery.0, first_recovery.1)?;
334 for (index, recovery) in recovery {
335 decoder.add_recovery_shard(index, recovery)?;
336 }
337
338 let mut result = HashMap::new();
339 for (index, original) in decoder.decode()?.restored_original_iter() {
340 result.insert(index, original.to_vec());
341 }
342
343 Ok(result)
344}
345
346#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::test_util;
353
354 #[test]
358 fn roundtrip() {
359 let original = test_util::generate_original(2, 1024, 123);
360
361 let recovery = encode(2, 3, &original).unwrap();
362
363 test_util::assert_hash(&recovery, test_util::LOW_2_3);
364
365 let restored = decode(2, 3, [(0, ""); 0], [(0, &recovery[0]), (1, &recovery[1])]).unwrap();
366
367 assert_eq!(restored.len(), 2);
368 assert_eq!(restored[&0], original[0]);
369 assert_eq!(restored[&1], original[1]);
370 }
371
372 mod encode {
376 use super::super::*;
377 use crate::Error;
378
379 #[test]
383 fn different_shard_size_with_different_original_shard_sizes() {
384 assert_eq!(
385 encode(2, 1, &[&[0u8; 64] as &[u8], &[0u8; 128]]),
386 Err(Error::DifferentShardSize {
387 shard_bytes: 64,
388 got: 128
389 })
390 );
391 }
392
393 #[test]
394 fn invalid_shard_size_with_empty_shard() {
395 assert_eq!(
396 encode(1, 1, &[&[0u8; 0]]),
397 Err(Error::InvalidShardSize { shard_bytes: 0 })
398 );
399 }
400
401 #[test]
402 fn too_few_original_shards_with_zero_shards_given() {
403 assert_eq!(
404 encode(1, 1, &[] as &[&[u8]]),
405 Err(Error::TooFewOriginalShards {
406 original_count: 1,
407 original_received_count: 0,
408 })
409 );
410 }
411
412 #[test]
413 fn too_many_original_shards() {
414 assert_eq!(
415 encode(1, 1, &[[0u8; 64], [0u8; 64]]),
416 Err(Error::TooManyOriginalShards { original_count: 1 })
417 );
418 }
419
420 #[test]
421 fn unsupported_shard_count_with_zero_original_count() {
422 assert_eq!(
423 encode(0, 1, &[] as &[&[u8]]),
424 Err(Error::UnsupportedShardCount {
425 original_count: 0,
426 recovery_count: 1,
427 })
428 );
429 }
430
431 #[test]
432 fn unsupported_shard_count_with_zero_recovery_count() {
433 assert_eq!(
434 encode(1, 0, &[[0u8; 64]]),
435 Err(Error::UnsupportedShardCount {
436 original_count: 1,
437 recovery_count: 0,
438 })
439 );
440 }
441 }
442
443 mod decode {
447 use super::super::*;
448 use crate::Error;
449
450 #[test]
451 fn no_original_missing_with_no_recovery_given() {
452 let restored = decode(1, 1, [(0, &[0u8; 64])], [(0, ""); 0]).unwrap();
453 assert!(restored.is_empty());
454 }
455
456 #[test]
460 fn different_shard_size_with_different_original_shard_sizes() {
461 assert_eq!(
462 decode(
463 2,
464 1,
465 [(0, &[0u8; 64] as &[u8]), (1, &[0u8; 128])],
466 [(0, &[0u8; 64])],
467 ),
468 Err(Error::DifferentShardSize {
469 shard_bytes: 64,
470 got: 128
471 })
472 );
473 }
474
475 #[test]
476 fn different_shard_size_with_different_recovery_shard_sizes() {
477 assert_eq!(
478 decode(
479 1,
480 2,
481 [(0, &[0u8; 64])],
482 [(0, &[0u8; 64] as &[u8]), (1, &[0u8; 128])],
483 ),
484 Err(Error::DifferentShardSize {
485 shard_bytes: 64,
486 got: 128
487 })
488 );
489 }
490
491 #[test]
492 fn different_shard_size_with_empty_original_shard() {
493 assert_eq!(
494 decode(1, 1, [(0, &[0u8; 0])], [(0, &[0u8; 64])]),
495 Err(Error::DifferentShardSize {
496 shard_bytes: 64,
497 got: 0
498 })
499 );
500 }
501
502 #[test]
503 fn duplicate_original_shard_index() {
504 assert_eq!(
505 decode(2, 1, [(0, &[0u8; 64]), (0, &[0u8; 64])], [(0, &[0u8; 64])]),
506 Err(Error::DuplicateOriginalShardIndex { index: 0 })
507 );
508 }
509
510 #[test]
511 fn duplicate_recovery_shard_index() {
512 assert_eq!(
513 decode(1, 2, [(0, &[0u8; 64])], [(0, &[0u8; 64]), (0, &[0u8; 64])]),
514 Err(Error::DuplicateRecoveryShardIndex { index: 0 })
515 );
516 }
517
518 #[test]
519 fn invalid_original_shard_index() {
520 assert_eq!(
521 decode(1, 1, [(1, &[0u8; 64])], [(0, &[0u8; 64])]),
522 Err(Error::InvalidOriginalShardIndex {
523 original_count: 1,
524 index: 1,
525 })
526 );
527 }
528
529 #[test]
530 fn invalid_recovery_shard_index() {
531 assert_eq!(
532 decode(1, 1, [(0, &[0u8; 64])], [(1, &[0u8; 64])]),
533 Err(Error::InvalidRecoveryShardIndex {
534 recovery_count: 1,
535 index: 1,
536 })
537 );
538 }
539
540 #[test]
541 fn invalid_shard_size_with_empty_recovery_shard() {
542 assert_eq!(
543 decode(1, 1, [(0, &[0u8; 64])], [(0, &[0u8; 0])]),
544 Err(Error::InvalidShardSize { shard_bytes: 0 })
545 );
546 }
547
548 #[test]
549 fn not_enough_shards() {
550 assert_eq!(
551 decode(1, 1, [(0, ""); 0], [(0, ""); 0]),
552 Err(Error::NotEnoughShards {
553 original_count: 1,
554 original_received_count: 0,
555 recovery_received_count: 0,
556 })
557 );
558 }
559
560 #[test]
561 fn unsupported_shard_count_with_zero_original_count() {
562 assert_eq!(
563 decode(0, 1, [(0, ""); 0], [(0, ""); 0]),
564 Err(Error::UnsupportedShardCount {
565 original_count: 0,
566 recovery_count: 1,
567 })
568 );
569 }
570
571 #[test]
572 fn unsupported_shard_count_with_zero_recovery_count() {
573 assert_eq!(
574 decode(1, 0, [(0, ""); 0], [(0, ""); 0]),
575 Err(Error::UnsupportedShardCount {
576 original_count: 1,
577 recovery_count: 0,
578 })
579 );
580 }
581 }
582}