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}