reed_solomon_16/
lib.rs

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// ======================================================================
27// Error - PUBLIC
28
29/// Represents all possible errors that can occur in this library.
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub enum Error {
32    /// Given shard has different size than given or inferred shard size.
33    ///
34    /// - Shard size is given explicitly to encoders/decoders
35    ///   and inferred for [`reed_solomon_16::encode`]
36    ///   and [`reed_solomon_16::decode`].
37    ///
38    /// [`reed_solomon_16::encode`]: crate::encode
39    /// [`reed_solomon_16::decode`]: crate::decode
40    DifferentShardSize {
41        /// Given or inferred shard size.
42        shard_bytes: usize,
43        /// Size of the given shard.
44        got: usize,
45    },
46
47    /// Decoder was given two original shards with same index.
48    DuplicateOriginalShardIndex {
49        /// Given duplicate index.
50        index: usize,
51    },
52
53    /// Decoder was given two recovery shards with same index.
54    DuplicateRecoveryShardIndex {
55        /// Given duplicate index.
56        index: usize,
57    },
58
59    /// Decoder was given original shard with invalid index,
60    /// i.e. `index >= original_count`.
61    InvalidOriginalShardIndex {
62        /// Configured number of original shards.
63        original_count: usize,
64        /// Given invalid index.
65        index: usize,
66    },
67
68    /// Decoder was given recovery shard with invalid index,
69    /// i.e. `index >= recovery_count`.
70    InvalidRecoveryShardIndex {
71        /// Configured number of recovery shards.
72        recovery_count: usize,
73        /// Given invalid index.
74        index: usize,
75    },
76
77    /// Given or inferred shard size is invalid:
78    /// Size must be non-zero and multiple of 64 bytes.
79    ///
80    /// - Shard size is given explicitly to encoders/decoders
81    ///   and inferred for [`reed_solomon_16::encode`]
82    ///   and [`reed_solomon_16::decode`].
83    ///
84    /// [`reed_solomon_16::encode`]: crate::encode
85    /// [`reed_solomon_16::decode`]: crate::decode
86    InvalidShardSize {
87        /// Given or inferred shard size.
88        shard_bytes: usize,
89    },
90
91    /// Decoder was given too few shards.
92    ///
93    /// Decoding requires as many shards as there were original shards
94    /// in total, in any combination of original shards and recovery shards.
95    NotEnoughShards {
96        /// Configured number of original shards.
97        original_count: usize,
98        /// Number of original shards given to decoder.
99        original_received_count: usize,
100        /// Number of recovery shards given to decoder.
101        recovery_received_count: usize,
102    },
103
104    /// Encoder was given less than `original_count` original shards.
105    TooFewOriginalShards {
106        /// Configured number of original shards.
107        original_count: usize,
108        /// Number of original shards given to encoder.
109        original_received_count: usize,
110    },
111
112    /// Encoder was given more than `original_count` original shards.
113    TooManyOriginalShards {
114        /// Configured number of original shards.
115        original_count: usize,
116    },
117
118    /// Given `original_count` / `recovery_count` combination is not supported.
119    UnsupportedShardCount {
120        /// Given number of original shards.
121        original_count: usize,
122        /// Given number of recovery shards.
123        recovery_count: usize,
124    },
125}
126
127// ======================================================================
128// Error - IMPL DISPLAY
129
130impl 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
224// ======================================================================
225// Error - IMPL ERROR
226
227impl std::error::Error for Error {}
228
229// ======================================================================
230// FUNCTIONS - PUBLIC
231
232/// Encodes in one go using [`ReedSolomonEncoder`],
233/// returning generated recovery shards.
234///
235/// - Original shards have indexes `0..original_count`
236///   corresponding to the order in which they are given.
237/// - Recovery shards have indexes `0..recovery_count`
238///   corresponding to their position in the returned `Vec`.
239/// - These same indexes must be used when decoding.
240///
241/// See [simple usage](crate#simple-usage) for an example.
242pub 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
281/// Decodes in one go using [`ReedSolomonDecoder`],
282/// returning restored original shards with their indexes.
283///
284/// - Given shard indexes must be the same that were used in encoding.
285///
286/// See [simple usage](crate#simple-usage) for an example and more details.
287pub 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        // NO RECOVERY SHARDS
313
314        let original_received_count = original.count();
315        if original_received_count == original_count {
316            // Nothing to do, original data is complete.
317            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// ======================================================================
347// TESTS
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use crate::test_util;
353
354    // ============================================================
355    // ROUNDTRIP
356
357    #[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    // ============================================================
373    // encode
374
375    mod encode {
376        use super::super::*;
377        use crate::Error;
378
379        // ==================================================
380        // ERRORS
381
382        #[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    // ============================================================
444    // decode
445
446    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        // ==================================================
457        // ERRORS
458
459        #[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}