Skip to main content

simd_rs63/
lib.rs

1#![feature(portable_simd)]
2#![deny(missing_docs)]
3
4//! Reed-Solomon erasure coding over GF(2⁸).
5//!
6//! This crate implements a systematic RS(9, 6) code: stripes of [`N`] = 9 equal-sized blocks,
7//! of which [`K`] = 6 carry data and [`M`] = 3 are parity. Any combination of up to [`M`]
8//! lost blocks can be recovered from the remaining [`K`] blocks.
9//!
10//! # Quick start
11//!
12//! ```
13//! use reed_solomon::{encode, recover, K, M, BLOCK_ALIGNMENT};
14//!
15//! let block_size = 4 * BLOCK_ALIGNMENT;
16//!
17//! // Build K data blocks.
18//! let data: Vec<Vec<u8>> = (0..K).map(|i| vec![i as u8; block_size]).collect();
19//!
20//! // Encode: compute M parity blocks.
21//! let mut parity: Vec<Vec<u8>> = vec![vec![0u8; block_size]; M];
22//! encode(
23//!     std::array::from_fn(|i| data[i].as_slice()),
24//!     std::array::from_fn(|i| parity[i].as_mut_slice()),
25//! ).unwrap();
26//!
27//! // Simulate losing data blocks 3, 4 and 5.
28//! let mut r3 = vec![0u8; block_size];
29//! let mut r4 = vec![0u8; block_size];
30//! let mut r5 = vec![0u8; block_size];
31//!
32//! // Recover them from the surviving blocks.
33//! recover(
34//!     [(0, data[0].as_slice()), (1, data[1].as_slice()), (2, data[2].as_slice()),
35//!      (6, parity[0].as_slice()), (7, parity[1].as_slice()), (8, parity[2].as_slice())],
36//!     [(3, &mut r3), (4, &mut r4), (5, &mut r5)],
37//! ).unwrap();
38//!
39//! assert_eq!(r3, data[3]);
40//! assert_eq!(r4, data[4]);
41//! assert_eq!(r5, data[5]);
42//! ```
43//!
44//! # Block sizes
45//!
46//! All blocks in a call must be the same size, and that size must be a positive multiple of
47//! [`BLOCK_ALIGNMENT`]. On this platform [`BLOCK_ALIGNMENT`] is chosen to match the widest
48//! SIMD shuffle instruction available, so it varies by CPU (16 on NEON/SSSE3, 32 on AVX2,
49//! 64 on AVX-512 VBMI).
50
51mod gf8;
52mod reed_solomon;
53mod error;
54
55use reed_solomon::LANES;
56
57/// Total number of blocks per stripe (data + parity).
58pub const N: usize = 9;
59
60/// Number of data blocks per stripe.
61pub const K: usize = 6;
62
63/// Number of parity blocks per stripe.
64///
65/// Any combination of up to `M` lost blocks can be recovered from the remaining [`K`].
66pub const M: usize = 3;
67
68/// Required alignment for block sizes.
69///
70/// Every block passed to [`encode`] or [`recover`] must have a length that is a positive
71/// multiple of this value. On this platform it equals the width of the widest SIMD shuffle
72/// instruction available (16, 32, or 64 bytes).
73pub const BLOCK_ALIGNMENT: usize = LANES;
74
75pub use error::Error;
76
77/// Computes the [`M`] parity blocks from [`K`] data blocks.
78///
79/// Data blocks are assigned indices `0..K` and parity blocks indices `K..N`. All blocks
80/// must have the same length, which must be a positive multiple of [`BLOCK_ALIGNMENT`].
81///
82/// # Errors
83///
84/// - [`Error::InvalidBlockSize`] — block length is zero or not a multiple of
85///   [`BLOCK_ALIGNMENT`].
86/// - [`Error::BlockSizeMismatch`] — not all blocks have the same length.
87///
88/// # Example
89///
90/// ```
91/// use reed_solomon::{encode, BLOCK_ALIGNMENT};
92///
93/// let block_size = 4 * BLOCK_ALIGNMENT;
94///
95/// let data = [
96///     vec![0; block_size],
97///     vec![1; block_size],
98///     vec![2; block_size],
99///     vec![3; block_size],
100///     vec![4; block_size],
101///     vec![5; block_size],
102/// ];
103///
104/// let mut parity = [
105///     vec![0; block_size],
106///     vec![0; block_size],
107///     vec![0; block_size],
108/// ];
109///
110/// let data_shards = [
111///     data[0].as_slice(),
112///     data[1].as_slice(),
113///     data[2].as_slice(),
114///     data[3].as_slice(),
115///     data[4].as_slice(),
116///     data[5].as_slice(),
117/// ];
118///
119/// let parity_shards = [
120///     parity[0].as_mut_slice(),
121///     parity[1].as_mut_slice(),
122///     parity[2].as_mut_slice(),
123/// ];
124///
125/// encode(data_shards, parity_shards).unwrap();
126/// ```
127pub fn encode(data: [&[u8]; K], parity: [&mut [u8]; M]) -> Result<(), Error> {
128    let block_size = data[0].len();
129    validate_block_size(block_size)?;
130    for slice in data.iter().skip(1) {
131        if slice.len() != block_size {
132            return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
133        }
134    }
135    for slice in &parity {
136        if slice.len() != block_size {
137            return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
138        }
139    }
140
141    let survivors: [(usize, &[u8]); K] = std::array::from_fn(|i| (i, data[i]));
142    let mut j = K;
143    let to_fix: [(usize, &mut [u8]); M] = parity.map(|s| {
144        let idx = j;
145        j += 1;
146        (idx, s)
147    });
148
149    reed_solomon::fix_errors(survivors, to_fix);
150    Ok(())
151}
152
153/// Recovers up to [`M`] missing blocks from any [`K`] known blocks.
154///
155/// `known` contains exactly [`K`] blocks, each paired with its stripe index in `0..N`.
156/// `missing` pairs output buffers with the indices of the blocks to recover. All blocks
157/// must have the same length, which must be a positive multiple of [`BLOCK_ALIGNMENT`].
158/// Indices across `known` and `missing` must be distinct.
159///
160/// The number of missing blocks `MISSING` must be at most [`M`]; this is enforced at
161/// compile time.
162///
163/// # Errors
164///
165/// - [`Error::InvalidBlockSize`] — block length is zero or not a multiple of
166///   [`BLOCK_ALIGNMENT`].
167/// - [`Error::BlockSizeMismatch`] — not all blocks have the same length.
168/// - [`Error::IndexOutOfRange`] — an index is ≥ [`N`].
169/// - [`Error::DuplicateIndex`] — an index appears more than once.
170///
171/// # Example
172///
173/// ```
174/// use reed_solomon::{encode, recover, K, M, BLOCK_ALIGNMENT};
175///
176/// let block_size = 4 * BLOCK_ALIGNMENT;
177///
178/// let data: Vec<Vec<u8>> = (0..K)
179///     .map(|i| vec![i as u8; block_size])
180///     .collect();
181///
182/// let mut parity: Vec<Vec<u8>> = vec![vec![0; block_size]; M];
183///
184/// let data_refs = [
185///     data[0].as_slice(),
186///     data[1].as_slice(),
187///     data[2].as_slice(),
188///     data[3].as_slice(),
189///     data[4].as_slice(),
190///     data[5].as_slice(),
191/// ];
192///
193/// let parity_refs = [
194///     parity[0].as_mut_slice(),
195///     parity[1].as_mut_slice(),
196///     parity[2].as_mut_slice(),
197/// ];
198///
199/// encode(data_refs, parity_refs).unwrap();
200///
201/// // Recover data shard 0 using data shards 1..=5 and parity shard 0.
202/// // Shard indexes 0..K are data shards, and K..K+M are parity shards.
203/// let available = [
204///     (1, data[1].as_slice()),
205///     (2, data[2].as_slice()),
206///     (3, data[3].as_slice()),
207///     (4, data[4].as_slice()),
208///     (5, data[5].as_slice()),
209///     (K, parity[0].as_slice()),
210/// ];
211///
212/// let mut recovered = vec![0; block_size];
213/// let missing = [(0, recovered.as_mut_slice())];
214///
215/// recover(available, missing).unwrap();
216///
217/// assert_eq!(recovered, data[0]);
218/// ```
219pub fn recover<const MISSING: usize>(
220    known: [(usize, &[u8]); K],
221    missing: [(usize, &mut [u8]); MISSING],
222) -> Result<(), Error> {
223    const { assert!(MISSING <= M, "cannot recover more than M blocks at once") };
224
225    if MISSING == 0 {
226        return Ok(());
227    }
228
229    let block_size = known[0].1.len();
230    validate_block_size(block_size)?;
231
232    let mut seen = 0u16;
233    for &(idx, slice) in &known {
234        if idx >= N {
235            return Err(Error::IndexOutOfRange(idx));
236        }
237        let bit = 1u16 << idx;
238        if seen & bit != 0 {
239            return Err(Error::DuplicateIndex(idx));
240        }
241        seen |= bit;
242        if slice.len() != block_size {
243            return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
244        }
245    }
246    for (idx, slice) in &missing {
247        if *idx >= N {
248            return Err(Error::IndexOutOfRange(*idx));
249        }
250        let bit = 1u16 << *idx;
251        if seen & bit != 0 {
252            return Err(Error::DuplicateIndex(*idx));
253        }
254        seen |= bit;
255        if slice.len() != block_size {
256            return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
257        }
258    }
259
260    reed_solomon::fix_errors(known, missing);
261    Ok(())
262}
263
264fn validate_block_size(size: usize) -> Result<(), Error> {
265    if size == 0 || size % BLOCK_ALIGNMENT != 0 {
266        return Err(Error::InvalidBlockSize(size));
267    }
268    Ok(())
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    const SIZE: usize = 4 * BLOCK_ALIGNMENT;
276
277    fn make_data() -> [Vec<u8>; K] {
278        std::array::from_fn(|i| vec![i as u8; SIZE])
279    }
280
281    fn encode_stripe(data: &[Vec<u8>; K]) -> [Vec<u8>; M] {
282        let mut parity: [Vec<u8>; M] = std::array::from_fn(|_| vec![0u8; SIZE]);
283        let [p0, p1, p2] = &mut parity;
284        encode(
285            std::array::from_fn(|i| data[i].as_slice()),
286            [p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
287        )
288        .unwrap();
289        parity
290    }
291
292    #[test]
293    fn test_encode_recover_all_parity() {
294        let data = make_data();
295        let parity = encode_stripe(&data);
296
297        let mut r0 = vec![0u8; SIZE];
298        let mut r1 = vec![0u8; SIZE];
299        let mut r2 = vec![0u8; SIZE];
300        recover(
301            std::array::from_fn(|i| (i, data[i].as_slice())),
302            [(K, &mut r0), (K + 1, &mut r1), (K + 2, &mut r2)],
303        )
304        .unwrap();
305
306        assert_eq!(r0, parity[0]);
307        assert_eq!(r1, parity[1]);
308        assert_eq!(r2, parity[2]);
309    }
310
311    #[test]
312    fn test_recover_3_data_blocks() {
313        let data = make_data();
314        let [p0, p1, p2] = encode_stripe(&data);
315
316        let mut r3 = vec![0u8; SIZE];
317        let mut r4 = vec![0u8; SIZE];
318        let mut r5 = vec![0u8; SIZE];
319        recover(
320            [
321                (0, data[0].as_slice()),
322                (1, data[1].as_slice()),
323                (2, data[2].as_slice()),
324                (6, p0.as_slice()),
325                (7, p1.as_slice()),
326                (8, p2.as_slice()),
327            ],
328            [(3, &mut r3), (4, &mut r4), (5, &mut r5)],
329        )
330        .unwrap();
331
332        assert_eq!(r3, data[3]);
333        assert_eq!(r4, data[4]);
334        assert_eq!(r5, data[5]);
335    }
336
337    #[test]
338    fn test_recover_mixed_data_and_parity() {
339        let data = make_data();
340        let [p0, p1, p2] = encode_stripe(&data);
341
342        let mut r0 = vec![0u8; SIZE];
343        let mut r6 = vec![0u8; SIZE];
344        let mut r8 = vec![0u8; SIZE];
345        recover(
346            [
347                (1, data[1].as_slice()),
348                (2, data[2].as_slice()),
349                (3, data[3].as_slice()),
350                (4, data[4].as_slice()),
351                (5, data[5].as_slice()),
352                (7, p1.as_slice()),
353            ],
354            [(0, &mut r0), (6, &mut r6), (8, &mut r8)],
355        )
356        .unwrap();
357
358        assert_eq!(r0, data[0]);
359        assert_eq!(r6, p0);
360        assert_eq!(r8, p2);
361    }
362
363    #[test]
364    fn test_error_invalid_block_size_zero() {
365        let data: [Vec<u8>; K] = std::array::from_fn(|_| vec![]);
366        let mut p0 = vec![];
367        let mut p1 = vec![];
368        let mut p2 = vec![];
369        assert_eq!(
370            encode(
371                std::array::from_fn(|i| data[i].as_slice()),
372                [p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
373            ),
374            Err(Error::InvalidBlockSize(0))
375        );
376    }
377
378    #[test]
379    fn test_error_invalid_block_size_unaligned() {
380        let sz = BLOCK_ALIGNMENT + 1;
381        let data: [Vec<u8>; K] = std::array::from_fn(|_| vec![0u8; sz]);
382        let mut p0 = vec![0u8; sz];
383        let mut p1 = vec![0u8; sz];
384        let mut p2 = vec![0u8; sz];
385        assert_eq!(
386            encode(
387                std::array::from_fn(|i| data[i].as_slice()),
388                [p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
389            ),
390            Err(Error::InvalidBlockSize(sz))
391        );
392    }
393
394    #[test]
395    fn test_error_block_size_mismatch() {
396        let data: [Vec<u8>; K] =
397            std::array::from_fn(|i| vec![0u8; if i == 3 { 2 * BLOCK_ALIGNMENT } else { SIZE }]);
398        let mut p0 = vec![0u8; SIZE];
399        let mut p1 = vec![0u8; SIZE];
400        let mut p2 = vec![0u8; SIZE];
401        assert_eq!(
402            encode(
403                std::array::from_fn(|i| data[i].as_slice()),
404                [p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
405            ),
406            Err(Error::BlockSizeMismatch { expected: SIZE, got: 2 * BLOCK_ALIGNMENT })
407        );
408    }
409
410    #[test]
411    fn test_error_index_out_of_range() {
412        let data = make_data();
413        let [p0, _, _] = encode_stripe(&data);
414        let mut r = vec![0u8; SIZE];
415        assert_eq!(
416            recover(
417                [
418                    (0, data[0].as_slice()),
419                    (1, data[1].as_slice()),
420                    (2, data[2].as_slice()),
421                    (3, data[3].as_slice()),
422                    (4, data[4].as_slice()),
423                    (9, p0.as_slice()),
424                ],
425                [(5, &mut r)],
426            ),
427            Err(Error::IndexOutOfRange(9))
428        );
429    }
430
431    #[test]
432    fn test_error_duplicate_index() {
433        let data = make_data();
434        let [p0, _, _] = encode_stripe(&data);
435        let mut r = vec![0u8; SIZE];
436        assert_eq!(
437            recover(
438                [
439                    (0, data[0].as_slice()),
440                    (1, data[1].as_slice()),
441                    (2, data[2].as_slice()),
442                    (3, data[3].as_slice()),
443                    (4, data[4].as_slice()),
444                    (4, p0.as_slice()),
445                ],
446                [(5, &mut r)],
447            ),
448            Err(Error::DuplicateIndex(4))
449        );
450    }
451}