use rusty_erasure::{Matrix, coder};
struct Cursor {
buf: Vec<u8>,
pos: usize,
}
impl Cursor {
fn take(&mut self, n: usize) -> &[u8] {
let s = &self.buf[self.pos..self.pos + n];
self.pos += n;
s
}
fn u8(&mut self) -> u8 {
self.take(1)[0]
}
fn u16(&mut self) -> u16 {
u16::from_le_bytes(self.take(2).try_into().expect("2 bytes"))
}
fn u32(&mut self) -> u32 {
u32::from_le_bytes(self.take(4).try_into().expect("4 bytes"))
}
}
#[test]
#[ignore = "env-gated: set RUSTY_ERASURE_FULLGRID to the generated vector file"]
fn full_grid_matches_isal_through_the_shipping_path() {
let path = std::env::var("RUSTY_ERASURE_FULLGRID").expect("RUSTY_ERASURE_FULLGRID not set");
let buf = std::fs::read(&path).expect("vector file readable");
let mut c = Cursor { buf, pos: 0 };
assert_eq!(c.take(4), b"REV1", "magic");
let count = c.u32();
assert!(count > 0);
let mut kernel_name = "";
for case in 0..count {
let kind = c.u8();
let k = c.u16() as usize;
let p = c.u16() as usize;
let len = c.u32() as usize;
let data_bytes = c.take(k * len).to_vec();
let gftbls_expect = c.take(p * k * 32).to_vec();
let parity_expect = c.take(p * len).to_vec();
let tag = format!("case {case}: kind={kind} k={k} p={p} len={len}");
let matrix = match kind {
0 => Matrix::reed_solomon(k, p),
_ => Matrix::cauchy(k, p),
}
.unwrap_or_else(|e| panic!("{tag}: matrix: {e}"));
let cdr = coder(matrix).unwrap_or_else(|e| panic!("{tag}: coder: {e}"));
kernel_name = cdr.kernels().name;
if cdr.kernels().table_bytes == 32 {
assert_eq!(cdr.gftbls(), gftbls_expect, "{tag}: gftbls");
}
let data: Vec<&[u8]> = (0..k)
.map(|j| &data_bytes[j * len..(j + 1) * len])
.collect();
let mut parity = vec![vec![0u8; len]; p];
{
let mut refs: Vec<&mut [u8]> = parity.iter_mut().map(|b| b.as_mut_slice()).collect();
cdr.encode(&data, &mut refs)
.unwrap_or_else(|e| panic!("{tag}: encode: {e}"));
}
let flat: Vec<u8> = parity.iter().flat_map(|b| b.iter().copied()).collect();
assert_eq!(flat, parity_expect, "{tag}: encode output");
let n = k + p;
for missing in [
(0..p.min(k)).collect::<Vec<usize>>(),
(0..p).map(|i| (i * n) / p).collect::<Vec<usize>>(),
] {
let mut missing = missing;
missing.sort_unstable();
missing.dedup();
if missing.is_empty() {
continue;
}
let shards: Vec<Option<&[u8]>> = (0..n)
.map(|i| {
if missing.contains(&i) {
None
} else if i < k {
Some(data[i])
} else {
Some(parity[i - k].as_slice())
}
})
.collect();
let mut out = vec![vec![0u8; len]; missing.len()];
{
let mut orefs: Vec<&mut [u8]> = out.iter_mut().map(|b| b.as_mut_slice()).collect();
cdr.recover(&shards, &missing, &mut orefs)
.unwrap_or_else(|e| panic!("{tag}: recover {missing:?}: {e}"));
}
for (&x, got) in missing.iter().zip(&out) {
let expect: &[u8] = if x < k { data[x] } else { &parity[x - k] };
assert_eq!(got.as_slice(), expect, "{tag}: recovered shard {x}");
}
}
}
assert_eq!(c.pos, c.buf.len(), "trailing bytes");
eprintln!("full grid: {count} cases conformant through kernel set {kernel_name}");
}