Skip to main content

commonware_cryptography/reed_solomon/
mod.rs

1//! Vendored version of [`reed_solomon_simd`].
2//!
3//! # Changes vs. Upstream
4//!
5//! - Moved the crate into `commonware_cryptography::reed_solomon` and rewrote internal
6//!   `crate::` paths accordingly.
7//! - Retained the upstream `LICENSE` file, including the BSD-3-Clause notice for Leopard-RS.
8//! - Removed the build script and README rustdoc generation.
9//! - Removed upstream examples. Kept upstream-style benchmarks under Commonware's benchmark layout
10//!   and naming rules.
11//! - Uses workspace dependencies and [`commonware_formatting`] in the test harness.
12//! - Uses [`thiserror`] for error display formatting.
13//! - Renamed upstream `ReedSolomonEncoder` and `ReedSolomonDecoder` to [`Encoder`] and [`Decoder`].
14//! - Uses plain code references for cfg-gated SIMD engine docs so rustdoc works on all targets.
15//!
16//! [`reed_solomon_simd`]: https://crates.io/crates/reed-solomon-simd
17//! [`thiserror`]: https://docs.rs/thiserror
18
19pub use self::{
20    decoder_result::{DecoderResult, Originals, Recoveries, RecoveryDecoderResult},
21    encoder_result::{EncoderResult, Recovery},
22    engine::SHARD_CHUNK_BYTES,
23    wrappers::{Decoder, Encoder},
24};
25use thiserror::Error;
26
27#[cfg(test)]
28#[macro_use]
29mod test_util;
30
31mod decoder_result;
32mod encoder_result;
33mod wrappers;
34
35pub mod algorithm {
36    #![doc = include_str!("algorithm.md")]
37}
38pub mod engine;
39pub mod rate;
40
41/// Represents all possible errors that can occur in this library.
42#[derive(Clone, Copy, Debug, Error, PartialEq)]
43pub enum Error {
44    /// Given shard has different size than the configured shard size.
45    #[error("different shard size: expected {shard_bytes} bytes, got {got} bytes")]
46    DifferentShardSize {
47        /// Configured shard size.
48        shard_bytes: usize,
49        /// Size of the given shard.
50        got: usize,
51    },
52
53    /// Decoder was given two original shards with same index.
54    #[error("duplicate original shard index: {index}")]
55    DuplicateOriginalShardIndex {
56        /// Given duplicate index.
57        index: usize,
58    },
59
60    /// Decoder was given two recovery shards with same index.
61    #[error("duplicate recovery shard index: {index}")]
62    DuplicateRecoveryShardIndex {
63        /// Given duplicate index.
64        index: usize,
65    },
66
67    /// Decoder was given original shard with invalid index,
68    /// i.e. `index >= original_count`.
69    #[error("invalid original shard index: {index} >= original_count {original_count}")]
70    InvalidOriginalShardIndex {
71        /// Configured number of original shards.
72        original_count: usize,
73        /// Given invalid index.
74        index: usize,
75    },
76
77    /// Decoder was given recovery shard with invalid index,
78    /// i.e. `index >= recovery_count`.
79    #[error("invalid recovery shard index: {index} >= recovery_count {recovery_count}")]
80    InvalidRecoveryShardIndex {
81        /// Configured number of recovery shards.
82        recovery_count: usize,
83        /// Given invalid index.
84        index: usize,
85    },
86
87    /// Configured shard size is invalid: size must be non-zero and even.
88    #[error("invalid shard size: {shard_bytes} bytes (must non-zero and multiple of 2)")]
89    InvalidShardSize {
90        /// Configured shard size.
91        shard_bytes: usize,
92    },
93
94    /// Decoder was given too few shards.
95    ///
96    /// Decoding requires as many shards as there were original shards
97    /// in total, in any combination of original shards and recovery shards.
98    #[error(
99        "not enough shards: {original_received_count} original + {recovery_received_count} recovery < {original_count} original_count"
100    )]
101    NotEnoughShards {
102        /// Configured number of original shards.
103        original_count: usize,
104        /// Number of original shards given to decoder.
105        original_received_count: usize,
106        /// Number of recovery shards given to decoder.
107        recovery_received_count: usize,
108    },
109
110    /// Encoder was given less than `original_count` original shards.
111    #[error(
112        "too few original shards: got {original_received_count} shards while original_count is {original_count}"
113    )]
114    TooFewOriginalShards {
115        /// Configured number of original shards.
116        original_count: usize,
117        /// Number of original shards given to encoder.
118        original_received_count: usize,
119    },
120
121    /// Encoder was given more than `original_count` original shards.
122    #[error("too many original shards: got more than original_count ({original_count}) shards")]
123    TooManyOriginalShards {
124        /// Configured number of original shards.
125        original_count: usize,
126    },
127
128    /// Given `original_count` / `recovery_count` combination is not supported.
129    #[error(
130        "unsupported shard count: {original_count} original shards with {recovery_count} recovery shards"
131    )]
132    UnsupportedShardCount {
133        /// Given number of original shards.
134        original_count: usize,
135        /// Given number of recovery shards.
136        recovery_count: usize,
137    },
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::reed_solomon::{engine::DefaultEngine, rate::DefaultRate};
144
145    #[test]
146    fn roundtrip() {
147        let original = test_util::generate_original(2, 1024, 123);
148
149        let mut encoder = Encoder::new(2, 3, 1024).unwrap();
150        for shard in &original {
151            encoder.add_original_shard(shard).unwrap();
152        }
153        let encoding = encoder.encode().unwrap();
154        let recovery: Vec<_> = encoding.recovery_iter().map(<[u8]>::to_vec).collect();
155
156        test_util::assert_hash(&recovery, test_util::LOW_2_3);
157
158        let mut decoder = Decoder::new(2, 3, 1024).unwrap();
159        decoder.add_recovery_shard(0, &recovery[0]).unwrap();
160        decoder.add_recovery_shard(1, &recovery[1]).unwrap();
161        let decoding = decoder.decode().unwrap().unwrap();
162        let mut restored = decoding.original_iter();
163
164        assert_eq!(restored.next(), Some((0, original[0].as_slice())));
165        assert_eq!(restored.next(), Some((1, original[1].as_slice())));
166        assert_eq!(restored.next(), None);
167    }
168
169    #[test]
170    fn test_send() {
171        fn assert_send<T: Send>() {}
172        assert_send::<Encoder>();
173        assert_send::<Decoder>();
174        assert_send::<DefaultEngine>();
175        assert_send::<DefaultRate<DefaultEngine>>();
176        assert_send::<DecoderResult<'_>>();
177        assert_send::<RecoveryDecoderResult<'_>>();
178        assert_send::<EncoderResult<'_>>();
179        assert_send::<Error>();
180    }
181
182    #[test]
183    fn test_sync() {
184        fn assert_sync<T: Sync>() {}
185        assert_sync::<Encoder>();
186        assert_sync::<Decoder>();
187        assert_sync::<DefaultEngine>();
188        assert_sync::<DefaultRate<DefaultEngine>>();
189        assert_sync::<DecoderResult<'_>>();
190        assert_sync::<RecoveryDecoderResult<'_>>();
191        assert_sync::<EncoderResult<'_>>();
192        assert_sync::<Error>();
193    }
194}