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