rusty_erasure-core 0.4.0

Core of the rusty_erasure ISA-L remake: GF(2^8) arithmetic, coding matrices, DecodePlan and the scalar kernels that are the permanent conformance oracle. no_std + alloc, zero dependencies, forbid(unsafe_code). MIT OR Apache-2.0.
Documentation
//! Replay the golden encode vectors generated by real ISA-L v2.32.1 `_base`
//! code (`tools/oracle/gen_vectors.c`, output checked in at
//! `corpus/golden/encode_vectors.bin` — see PROVENANCE.md).
//!
//! Byte-for-byte, per case: the expanded tables (`ec_init_tables_base`), the
//! one-shot encode (`ec_encode_data_base`), and the completed update sequence
//! (`ec_encode_data_update_base`). Deterministic — one run is the verdict.

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}"));

        // Table layout: byte-identical to the C ec_init_tables_base output,
        // including its 64-bit fast-path construction.
        assert_eq!(coder.gftbls(), gftbls, "{tag}: gftbls");

        let data: Vec<&[u8]> = (0..k)
            .map(|j| &data_bytes[j * len..(j + 1) * len])
            .collect();

        // One-shot encode.
        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");

        // Update sequence from zeroed parity, source-major order like the C
        // check; random orders are covered by the property tests.
        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");

        // Recovery spot-check against ground truth on the golden data: drop
        // the first min(p, k) sources, rebuild, compare. (Exhaustive loss
        // patterns live in coder.rs.)
        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");
}