Media FEC: a generator for rs-gf256-v1 known answers from the reference crate
Before reed-solomon-erasure is replaced by an owned GF(2^8) construction, it produces the vectors the owned code must meet. The ignored test encodes every standard video geometry (k = 1..=16), standard audio 4+2 at 1, 37 and 1300 bytes, a 4000-byte shard, zero and 0xff data, and the policy's edges (1+1, 255+1, 1+255, 2+254, 128+128, 200+56, 250+6), and reconstructs four erasure patterns per case, asserting the crate returns the original data. It is run once and deleted with the crate; git keeps its provenance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
42ba0f082a
commit
73bf44212e
1 changed files with 95 additions and 0 deletions
|
|
@ -1141,3 +1141,98 @@ fn a_budget_at_the_media_record_ceiling_admits_large_shards_end_to_end() {
|
|||
.unwrap();
|
||||
decode_media_wire_record(&wire).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Known-answer generator: reed-solomon-erasure 6.0.0 is the reference that
|
||||
// produced `fixtures/media_fec_rs_gf256_v1.kat`. It runs once, before the
|
||||
// crate is replaced by the owned construction, and is deleted with the crate.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The data of a fixture case, as the fixture's header defines it.
|
||||
fn kat_data(k: usize, m: usize, shard_bytes: usize, fill: &str) -> Vec<Vec<u8>> {
|
||||
(0..k)
|
||||
.map(|i| match fill {
|
||||
"lcg" => bytes((k * 65_536 + m * 256 + i) as u32, shard_bytes),
|
||||
"zero" => vec![0; shard_bytes],
|
||||
"ff" => vec![0xff; shard_bytes],
|
||||
other => panic!("unknown fill {other}"),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Erasure patterns of exactly `min(m, k)`-ish shards, each leaving at least
|
||||
/// one parity shard present and at least one data shard missing.
|
||||
fn kat_erasures(k: usize, m: usize) -> Vec<Vec<usize>> {
|
||||
let mut patterns = Vec::new();
|
||||
let d = m.min(k);
|
||||
patterns.push((0..d).collect()); // data from the front
|
||||
patterns.push((k - d..k).collect()); // data from the back
|
||||
let spread = m.div_ceil(2).min(k);
|
||||
let mut mixed: Vec<usize> = (0..spread).map(|s| s * k / spread).collect();
|
||||
mixed.extend(k..k + (m - spread));
|
||||
patterns.push(mixed); // data spread across the block, parity from the front
|
||||
let mut one: Vec<usize> = vec![k / 2];
|
||||
one.extend(k..k + (m - 1));
|
||||
patterns.push(one); // one data shard, every parity shard but the last
|
||||
patterns.sort();
|
||||
patterns.dedup();
|
||||
patterns
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "run once to write the rs-gf256-v1 fixture from the reference crate"]
|
||||
fn generate_rs_gf256_v1_known_answers() {
|
||||
use reed_solomon_erasure::galois_8::ReedSolomon;
|
||||
let mut cases: Vec<(usize, usize, usize, &str)> = Vec::new();
|
||||
// Every standard video block geometry, k = 1..=16, m = max(2, ceil(k / 4)).
|
||||
for k in 1..=16_u16 {
|
||||
cases.push((usize::from(k), usize::from(STANDARD.video_parity_shards(k)), 37, "lcg"));
|
||||
}
|
||||
// Standard audio, 4 + 2, at 1 byte, an odd length and a full datagram.
|
||||
cases.extend([(4, 2, 1, "lcg"), (4, 2, 37, "lcg"), (4, 2, 1300, "lcg")]);
|
||||
// The widest standard video block at a full datagram, and a long shard.
|
||||
cases.extend([(16, 4, 1300, "lcg"), (3, 2, 4000, "lcg")]);
|
||||
// Degenerate data.
|
||||
cases.extend([(4, 2, 16, "zero"), (16, 4, 16, "ff")]);
|
||||
// The policy's edges: one shard each side, and 256 shards split every way.
|
||||
cases.extend([
|
||||
(1, 1, 1, "lcg"),
|
||||
(1, 1, 9, "lcg"),
|
||||
(2, 1, 5, "lcg"),
|
||||
(255, 1, 1, "lcg"),
|
||||
(255, 1, 3, "lcg"),
|
||||
(1, 255, 1, "lcg"),
|
||||
(1, 255, 4, "lcg"),
|
||||
(2, 254, 2, "lcg"),
|
||||
(128, 128, 1, "lcg"),
|
||||
(128, 128, 4, "lcg"),
|
||||
(200, 56, 2, "lcg"),
|
||||
(250, 6, 5, "lcg"),
|
||||
]);
|
||||
|
||||
let mut out = String::new();
|
||||
for (k, m, shard_bytes, fill) in cases {
|
||||
let data = kat_data(k, m, shard_bytes, fill);
|
||||
let code = ReedSolomon::new(k, m).unwrap();
|
||||
let mut parity = vec![vec![0_u8; shard_bytes]; m];
|
||||
code.encode_sep(&data, &mut parity).unwrap();
|
||||
out.push_str(&format!("case {k} {m} {shard_bytes} {fill}\n"));
|
||||
for (p, shard) in parity.iter().enumerate() {
|
||||
out.push_str(&format!("parity {p} {}\n", hex(shard)));
|
||||
}
|
||||
for pattern in kat_erasures(k, m) {
|
||||
let mut shards: Vec<Option<Vec<u8>>> =
|
||||
data.iter().chain(parity.iter()).cloned().map(Some).collect();
|
||||
for &slot in &pattern {
|
||||
shards[slot] = None;
|
||||
}
|
||||
code.reconstruct_data(&mut shards).unwrap();
|
||||
for (i, shard) in shards.iter().take(k).enumerate() {
|
||||
assert_eq!(shard.as_ref().unwrap(), &data[i], "k={k} m={m} {pattern:?}");
|
||||
}
|
||||
let list: Vec<String> = pattern.iter().map(usize::to_string).collect();
|
||||
out.push_str(&format!("erase {}\n", list.join(",")));
|
||||
}
|
||||
}
|
||||
println!("BEGIN-KAT\n{out}END-KAT");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue