Add duplicate publisher determinism proof
This commit is contained in:
parent
5d0f3077d3
commit
91dad67fc2
18 changed files with 21569 additions and 595 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
|
@ -46,6 +47,15 @@ fn blake3_hex(path: &Path) -> anyhow::Result<String> {
|
|||
Ok(blake3::hash(&bytes).to_hex().to_string())
|
||||
}
|
||||
|
||||
fn command_available(name: &str) -> bool {
|
||||
Command::new(name)
|
||||
.arg("-version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn concat_init_and_segment(init: &Path, seg: &Path, out: &Path) -> anyhow::Result<()> {
|
||||
let init_bytes = std::fs::read(init)?;
|
||||
let seg_bytes = std::fs::read(seg)?;
|
||||
|
|
@ -157,11 +167,15 @@ fn write_deterministic_ts(out_path: &Path) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_ladder(ec_node: &Path, input_ts: &Path, out_dir: &Path) -> anyhow::Result<()> {
|
||||
fn run_ladder_with_identity(
|
||||
ec_node: &Path,
|
||||
input_ts: &Path,
|
||||
out_dir: &Path,
|
||||
stream_id: &str,
|
||||
broadcast_name: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let signing_key = "11".repeat(32);
|
||||
let network_secret = "22".repeat(32);
|
||||
let stream_id = "every.channel/determinism/cmaf-ladder";
|
||||
let broadcast_name = "every.channel/determinism/cmaf-ladder";
|
||||
|
||||
let mut cmd = Command::new(ec_node);
|
||||
cmd.env("EVERY_CHANNEL_MANIFEST_SIGNING_KEY", &signing_key)
|
||||
|
|
@ -210,6 +224,40 @@ fn run_ladder(ec_node: &Path, input_ts: &Path, out_dir: &Path) -> anyhow::Result
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_ladder(ec_node: &Path, input_ts: &Path, out_dir: &Path) -> anyhow::Result<()> {
|
||||
run_ladder_with_identity(
|
||||
ec_node,
|
||||
input_ts,
|
||||
out_dir,
|
||||
"every.channel/determinism/cmaf-ladder",
|
||||
"every.channel/determinism/cmaf-ladder",
|
||||
)
|
||||
}
|
||||
|
||||
fn ladder_artifact_hashes(root: &Path) -> BTreeMap<String, String> {
|
||||
let mut hashes = BTreeMap::new();
|
||||
for variant in ["1080p", "720p", "480p"] {
|
||||
let variant_dir = root.join("cmaf-ladder").join(variant);
|
||||
// `moq-publish --max-chunks 3` publishes init plus segments 0..=2.
|
||||
// ffmpeg can race ahead and leave an unpublished tail segment before it is killed.
|
||||
let init = variant_dir.join("init.mp4");
|
||||
assert!(init.exists(), "missing init for {variant}");
|
||||
hashes.insert(format!("{variant}/init.mp4"), blake3_hex(&init).unwrap());
|
||||
|
||||
for idx in 0..3 {
|
||||
let name = format!("segment_{idx:06}.m4s");
|
||||
let path = variant_dir.join(&name);
|
||||
assert!(path.exists(), "missing {name} for {variant}");
|
||||
hashes.insert(format!("{variant}/{name}"), blake3_hex(&path).unwrap());
|
||||
}
|
||||
}
|
||||
hashes
|
||||
}
|
||||
|
||||
fn assert_ladder_bytes_match(left: &Path, right: &Path) {
|
||||
assert_eq!(ladder_artifact_hashes(left), ladder_artifact_hashes(right));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn deterministic_cmaf_ladder_outputs_match_across_runs() {
|
||||
|
|
@ -235,36 +283,53 @@ fn deterministic_cmaf_ladder_outputs_match_across_runs() {
|
|||
run_ladder(&ec_node, &input_ts, &run1).expect("run ladder 1");
|
||||
run_ladder(&ec_node, &input_ts, &run2).expect("run ladder 2");
|
||||
|
||||
for variant in ["1080p", "720p", "480p"] {
|
||||
let v1 = run1.join("cmaf-ladder").join(variant);
|
||||
let v2 = run2.join("cmaf-ladder").join(variant);
|
||||
assert_ladder_bytes_match(&run1, &run2);
|
||||
}
|
||||
|
||||
let init1 = v1.join("init.mp4");
|
||||
let init2 = v2.join("init.mp4");
|
||||
assert!(
|
||||
init1.exists() && init2.exists(),
|
||||
"missing init for {variant}"
|
||||
);
|
||||
assert_eq!(
|
||||
blake3_hex(&init1).unwrap(),
|
||||
blake3_hex(&init2).unwrap(),
|
||||
"init differs for {variant}"
|
||||
);
|
||||
|
||||
for idx in 0..3 {
|
||||
let s1 = v1.join(format!("segment_{idx:06}.m4s"));
|
||||
let s2 = v2.join(format!("segment_{idx:06}.m4s"));
|
||||
assert!(
|
||||
s1.exists() && s2.exists(),
|
||||
"missing segment {idx} for {variant}"
|
||||
);
|
||||
assert_eq!(
|
||||
blake3_hex(&s1).unwrap(),
|
||||
blake3_hex(&s2).unwrap(),
|
||||
"segment {idx} differs for {variant}"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn duplicate_publishers_same_input_produce_identical_cmaf_ladder_bytes() {
|
||||
if !command_available("ffmpeg") {
|
||||
eprintln!("skipping duplicate publisher CMAF ladder determinism test: ffmpeg unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
let ec_node = ec_node_path();
|
||||
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
let tmp = std::env::temp_dir().join(format!("ec-duplicate-publisher-cmaf-ladder-{ts}"));
|
||||
let _ = std::fs::create_dir_all(&tmp);
|
||||
|
||||
let input_ts = tmp.join("input.ts");
|
||||
write_deterministic_ts(&input_ts).expect("write deterministic TS");
|
||||
|
||||
let publisher_a = tmp.join("publisher-a");
|
||||
let publisher_b = tmp.join("publisher-b");
|
||||
let _ = std::fs::remove_dir_all(&publisher_a);
|
||||
let _ = std::fs::remove_dir_all(&publisher_b);
|
||||
std::fs::create_dir_all(&publisher_a).unwrap();
|
||||
std::fs::create_dir_all(&publisher_b).unwrap();
|
||||
|
||||
run_ladder_with_identity(
|
||||
&ec_node,
|
||||
&input_ts,
|
||||
&publisher_a,
|
||||
"every.channel/determinism/duplicate/publisher-a/la-kcop",
|
||||
"publisher-a-la-kcop",
|
||||
)
|
||||
.expect("run duplicate publisher a");
|
||||
run_ladder_with_identity(
|
||||
&ec_node,
|
||||
&input_ts,
|
||||
&publisher_b,
|
||||
"every.channel/determinism/duplicate/publisher-b/la-kcop",
|
||||
"publisher-b-la-kcop",
|
||||
)
|
||||
.expect("run duplicate publisher b");
|
||||
|
||||
assert_ladder_bytes_match(&publisher_a, &publisher_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue