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}