#![feature(portable_simd)]
#![deny(missing_docs)]
mod gf8;
mod reed_solomon;
mod error;
use reed_solomon::LANES;
pub const N: usize = 9;
pub const K: usize = 6;
pub const M: usize = 3;
pub const BLOCK_ALIGNMENT: usize = LANES;
pub use error::Error;
pub fn encode(data: [&[u8]; K], parity: [&mut [u8]; M]) -> Result<(), Error> {
let block_size = data[0].len();
validate_block_size(block_size)?;
for slice in data.iter().skip(1) {
if slice.len() != block_size {
return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
}
}
for slice in &parity {
if slice.len() != block_size {
return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
}
}
let survivors: [(usize, &[u8]); K] = std::array::from_fn(|i| (i, data[i]));
let mut j = K;
let to_fix: [(usize, &mut [u8]); M] = parity.map(|s| {
let idx = j;
j += 1;
(idx, s)
});
reed_solomon::fix_errors(survivors, to_fix);
Ok(())
}
pub fn recover<const MISSING: usize>(
known: [(usize, &[u8]); K],
missing: [(usize, &mut [u8]); MISSING],
) -> Result<(), Error> {
const { assert!(MISSING <= M, "cannot recover more than M blocks at once") };
if MISSING == 0 {
return Ok(());
}
let block_size = known[0].1.len();
validate_block_size(block_size)?;
let mut seen = 0u16;
for &(idx, slice) in &known {
if idx >= N {
return Err(Error::IndexOutOfRange(idx));
}
let bit = 1u16 << idx;
if seen & bit != 0 {
return Err(Error::DuplicateIndex(idx));
}
seen |= bit;
if slice.len() != block_size {
return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
}
}
for (idx, slice) in &missing {
if *idx >= N {
return Err(Error::IndexOutOfRange(*idx));
}
let bit = 1u16 << *idx;
if seen & bit != 0 {
return Err(Error::DuplicateIndex(*idx));
}
seen |= bit;
if slice.len() != block_size {
return Err(Error::BlockSizeMismatch { expected: block_size, got: slice.len() });
}
}
reed_solomon::fix_errors(known, missing);
Ok(())
}
fn validate_block_size(size: usize) -> Result<(), Error> {
if size == 0 || size % BLOCK_ALIGNMENT != 0 {
return Err(Error::InvalidBlockSize(size));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const SIZE: usize = 4 * BLOCK_ALIGNMENT;
fn make_data() -> [Vec<u8>; K] {
std::array::from_fn(|i| vec![i as u8; SIZE])
}
fn encode_stripe(data: &[Vec<u8>; K]) -> [Vec<u8>; M] {
let mut parity: [Vec<u8>; M] = std::array::from_fn(|_| vec![0u8; SIZE]);
let [p0, p1, p2] = &mut parity;
encode(
std::array::from_fn(|i| data[i].as_slice()),
[p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
)
.unwrap();
parity
}
#[test]
fn test_encode_recover_all_parity() {
let data = make_data();
let parity = encode_stripe(&data);
let mut r0 = vec![0u8; SIZE];
let mut r1 = vec![0u8; SIZE];
let mut r2 = vec![0u8; SIZE];
recover(
std::array::from_fn(|i| (i, data[i].as_slice())),
[(K, &mut r0), (K + 1, &mut r1), (K + 2, &mut r2)],
)
.unwrap();
assert_eq!(r0, parity[0]);
assert_eq!(r1, parity[1]);
assert_eq!(r2, parity[2]);
}
#[test]
fn test_recover_3_data_blocks() {
let data = make_data();
let [p0, p1, p2] = encode_stripe(&data);
let mut r3 = vec![0u8; SIZE];
let mut r4 = vec![0u8; SIZE];
let mut r5 = vec![0u8; SIZE];
recover(
[
(0, data[0].as_slice()),
(1, data[1].as_slice()),
(2, data[2].as_slice()),
(6, p0.as_slice()),
(7, p1.as_slice()),
(8, p2.as_slice()),
],
[(3, &mut r3), (4, &mut r4), (5, &mut r5)],
)
.unwrap();
assert_eq!(r3, data[3]);
assert_eq!(r4, data[4]);
assert_eq!(r5, data[5]);
}
#[test]
fn test_recover_mixed_data_and_parity() {
let data = make_data();
let [p0, p1, p2] = encode_stripe(&data);
let mut r0 = vec![0u8; SIZE];
let mut r6 = vec![0u8; SIZE];
let mut r8 = vec![0u8; SIZE];
recover(
[
(1, data[1].as_slice()),
(2, data[2].as_slice()),
(3, data[3].as_slice()),
(4, data[4].as_slice()),
(5, data[5].as_slice()),
(7, p1.as_slice()),
],
[(0, &mut r0), (6, &mut r6), (8, &mut r8)],
)
.unwrap();
assert_eq!(r0, data[0]);
assert_eq!(r6, p0);
assert_eq!(r8, p2);
}
#[test]
fn test_error_invalid_block_size_zero() {
let data: [Vec<u8>; K] = std::array::from_fn(|_| vec![]);
let mut p0 = vec![];
let mut p1 = vec![];
let mut p2 = vec![];
assert_eq!(
encode(
std::array::from_fn(|i| data[i].as_slice()),
[p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
),
Err(Error::InvalidBlockSize(0))
);
}
#[test]
fn test_error_invalid_block_size_unaligned() {
let sz = BLOCK_ALIGNMENT + 1;
let data: [Vec<u8>; K] = std::array::from_fn(|_| vec![0u8; sz]);
let mut p0 = vec![0u8; sz];
let mut p1 = vec![0u8; sz];
let mut p2 = vec![0u8; sz];
assert_eq!(
encode(
std::array::from_fn(|i| data[i].as_slice()),
[p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
),
Err(Error::InvalidBlockSize(sz))
);
}
#[test]
fn test_error_block_size_mismatch() {
let data: [Vec<u8>; K] =
std::array::from_fn(|i| vec![0u8; if i == 3 { 2 * BLOCK_ALIGNMENT } else { SIZE }]);
let mut p0 = vec![0u8; SIZE];
let mut p1 = vec![0u8; SIZE];
let mut p2 = vec![0u8; SIZE];
assert_eq!(
encode(
std::array::from_fn(|i| data[i].as_slice()),
[p0.as_mut_slice(), p1.as_mut_slice(), p2.as_mut_slice()],
),
Err(Error::BlockSizeMismatch { expected: SIZE, got: 2 * BLOCK_ALIGNMENT })
);
}
#[test]
fn test_error_index_out_of_range() {
let data = make_data();
let [p0, _, _] = encode_stripe(&data);
let mut r = vec![0u8; SIZE];
assert_eq!(
recover(
[
(0, data[0].as_slice()),
(1, data[1].as_slice()),
(2, data[2].as_slice()),
(3, data[3].as_slice()),
(4, data[4].as_slice()),
(9, p0.as_slice()),
],
[(5, &mut r)],
),
Err(Error::IndexOutOfRange(9))
);
}
#[test]
fn test_error_duplicate_index() {
let data = make_data();
let [p0, _, _] = encode_stripe(&data);
let mut r = vec![0u8; SIZE];
assert_eq!(
recover(
[
(0, data[0].as_slice()),
(1, data[1].as_slice()),
(2, data[2].as_slice()),
(3, data[3].as_slice()),
(4, data[4].as_slice()),
(4, p0.as_slice()),
],
[(5, &mut r)],
),
Err(Error::DuplicateIndex(4))
);
}
}