use rusty_erasure_core::{Coder, Matrix};
static VECTORS: &[u8] = include_bytes!("../../../corpus/golden/encode_vectors.bin");
struct Cursor<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn take(&mut self, n: usize) -> &'a [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]
#[cfg_attr(
miri,
ignore = "77-case byte-identity replay is deterministic and UB-free; too slow interpreted"
)]
fn encode_tables_and_update_match_isal_vectors() {
let mut c = Cursor {
buf: VECTORS,
pos: 0,
};
assert_eq!(c.take(4), b"REV1", "vector file magic");
let count = c.u32();
assert!(count > 0);
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);
let gftbls = c.take(p * k * 32);
let parity_expect = c.take(p * len);
let tag = format!("case {case}: kind={kind} k={k} p={p} len={len}");
let matrix = match kind {
0 => Matrix::reed_solomon(k, p),
1 => Matrix::cauchy(k, p),
other => panic!("unknown matrix kind {other}"),
}
.unwrap_or_else(|e| panic!("{tag}: matrix: {e}"));
let coder = Coder::new(matrix).unwrap_or_else(|e| panic!("{tag}: coder: {e}"));
assert_eq!(coder.gftbls(), gftbls, "{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();
coder
.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 mut parity2 = vec![vec![0u8; len]; p];
{
let mut refs: Vec<&mut [u8]> = parity2.iter_mut().map(|b| b.as_mut_slice()).collect();
for (j, d) in data.iter().enumerate() {
coder
.update(j, d, &mut refs)
.unwrap_or_else(|e| panic!("{tag}: update {j}: {e}"));
}
}
assert_eq!(parity2, parity, "{tag}: update sequence != one-shot");
let drop = p.min(k);
let shards: Vec<Option<&[u8]>> = (0..k + p)
.map(|i| {
if i < drop {
None
} else if i < k {
Some(data[i])
} else {
Some(parity[i - k].as_slice())
}
})
.collect();
let rebuild: Vec<usize> = (0..drop).collect();
let mut out = vec![vec![0u8; len]; drop];
{
let mut refs: Vec<&mut [u8]> = out.iter_mut().map(|b| b.as_mut_slice()).collect();
coder
.recover(&shards, &rebuild, &mut refs)
.unwrap_or_else(|e| panic!("{tag}: recover: {e}"));
}
for (x, got) in rebuild.iter().zip(&out) {
assert_eq!(got.as_slice(), data[*x], "{tag}: recovered shard {x}");
}
}
assert_eq!(c.pos, VECTORS.len(), "trailing bytes in vector file");
}