chore: scrub non-shippable references from tests/comments

This commit is contained in:
MattJackson
2026-06-01 21:36:57 -07:00
parent e134616422
commit 1565da610a
20 changed files with 144 additions and 183 deletions
+4 -4
View File
@@ -85,10 +85,10 @@ All URLs require a `scheme://path` format. Bare paths are rejected.
// PES pipeline (frame-level) — input() returns Box<dyn FrameSource>, // PES pipeline (frame-level) — input() returns Box<dyn FrameSource>,
// output() returns Box<dyn FrameSink>. // output() returns Box<dyn FrameSink>.
let input = libfreemkv::input("disc:///dev/sg4", &opts)?; // DiscStream let input = libfreemkv::input("disc:///dev/sg4", &opts)?; // DiscStream
let input = libfreemkv::input("iso://Dune.iso", &opts)?; // IsoStream let input = libfreemkv::input("iso://Movie.iso", &opts)?; // IsoStream
let output = libfreemkv::output("mkv://Dune.mkv", &title)?; // MkvOutputStream let output = libfreemkv::output("mkv://Movie.mkv", &title)?; // MkvOutputStream
let output = libfreemkv::output("m2ts://Dune.m2ts", &title)?; // M2tsOutputStream let output = libfreemkv::output("m2ts://Movie.m2ts", &title)?; // M2tsOutputStream
let output = libfreemkv::output("network://10.0.0.1:9000", &title)?; // NetworkOutputStream let output = libfreemkv::output("network://192.0.2.10:9000", &title)?; // NetworkOutputStream
let output = libfreemkv::output("null://", &title)?; // NullOutputStream let output = libfreemkv::output("null://", &title)?; // NullOutputStream
``` ```
+20 -20
View File
@@ -448,9 +448,9 @@ mod tests {
#[test] #[test]
fn test_parse_disc_entry() { fn test_parse_disc_entry() {
let line = r#"***REMOVED*** = DUNE_PART_TWO (Dune: Part Two) | D | 2024-04-02 | M | ***REMOVED*** | I | ***REMOVED*** | V | ***REMOVED*** | U | 1-***REMOVED*** ; MKBv77"#; let line = r#"0x000102030405060708090A0B0C0D0E0F10111213 = SAMPLE_FILM (Sample Film) | D | 2024-01-01 | M | 0x000102030405060708090A0B0C0D0E0F | I | 0x101112131415161718191A1B1C1D1E1F | V | 0x202122232425262728292A2B2C2D2E2F | U | 1-0x303132333435363738393A3B3C3D3E3F ; MKBv77"#;
let entry = KeyDb::parse_disc_entry(line).unwrap(); let entry = KeyDb::parse_disc_entry(line).unwrap();
assert_eq!(entry.title, "Dune: Part Two"); assert_eq!(entry.title, "Sample Film");
assert!(entry.media_key.is_some()); assert!(entry.media_key.is_some());
assert!(entry.vuk.is_some()); assert!(entry.vuk.is_some());
assert_eq!(entry.unit_keys.len(), 1); assert_eq!(entry.unit_keys.len(), 1);
@@ -459,7 +459,7 @@ mod tests {
#[test] #[test]
fn test_parse_device_key() { fn test_parse_device_key() {
let line = "| DK | DEVICE_KEY ***REMOVED*** | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; MKBv01-MKBv48"; let line = "| DK | DEVICE_KEY 0x000102030405060708090A0B0C0D0E0F | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; MKBv01-MKBv48";
let dk = KeyDb::parse_device_key(line).unwrap(); let dk = KeyDb::parse_device_key(line).unwrap();
assert_eq!(dk.node, 0x0800); assert_eq!(dk.node, 0x0800);
assert_eq!(dk.u_mask_shift, 0x17); assert_eq!(dk.u_mask_shift, 0x17);
@@ -472,9 +472,9 @@ mod tests {
// candidate: it lands in `processing_keys` and the brute walker // candidate: it lands in `processing_keys` and the brute walker
// handles it. // handles it.
let cfg = r#" let cfg = r#"
| DK | DEVICE_KEY ***REMOVED*** ; orphan from HKD\x02 corpus | DK | DEVICE_KEY 0xDEADBEEF0001020304050607080900AA ; orphan, no position fields
| DK | DEVICE_KEY ***REMOVED*** | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; positioned MKBv01-MKBv48 | DK | DEVICE_KEY 0x000102030405060708090A0B0C0D0E0F | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; positioned MKBv01-MKBv48
| PK | ***REMOVED*** ; legacy PK row still works | PK | 0xCAFEBABE0001020304050607080900BB ; legacy PK row still works
"#; "#;
let db = KeyDb::parse(cfg); let db = KeyDb::parse(cfg);
assert_eq!( assert_eq!(
@@ -488,29 +488,29 @@ mod tests {
2, 2,
"orphan DK row + legacy PK row both belong in processing_keys" "orphan DK row + legacy PK row both belong in processing_keys"
); );
assert_eq!(db.processing_keys[0][..4], [0xC5, 0xDD, 0xB5, 0xB4]); assert_eq!(db.processing_keys[0][..4], [0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(db.processing_keys[1][..4], [0x76, 0xDD, 0xD7, 0x09]); assert_eq!(db.processing_keys[1][..4], [0xCA, 0xFE, 0xBA, 0xBE]);
} }
#[test] #[test]
fn test_parse_orphan_dk_rejects_lines_with_position_fields() { fn test_parse_orphan_dk_rejects_lines_with_position_fields() {
// The parser must NOT pick up a positioned DK row as an orphan // The parser must NOT pick up a positioned DK row as an orphan
// (that would double-count). parse_orphan_dk explicitly checks. // (that would double-count). parse_orphan_dk explicitly checks.
let positioned = "| DK | DEVICE_KEY ***REMOVED*** | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17"; let positioned = "| DK | DEVICE_KEY 0x000102030405060708090A0B0C0D0E0F | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17";
assert!( assert!(
KeyDb::parse_orphan_dk(positioned).is_none(), KeyDb::parse_orphan_dk(positioned).is_none(),
"positioned DK must not match orphan parser" "positioned DK must not match orphan parser"
); );
let orphan = "| DK | DEVICE_KEY ***REMOVED***"; let orphan = "| DK | DEVICE_KEY 0xDEADBEEF0001020304050607080900AA";
let key = KeyDb::parse_orphan_dk(orphan).expect("orphan should parse"); let key = KeyDb::parse_orphan_dk(orphan).expect("orphan should parse");
assert_eq!(key[..4], [0xC5, 0xDD, 0xB5, 0xB4]); assert_eq!(key[..4], [0xDE, 0xAD, 0xBE, 0xEF]);
} }
#[test] #[test]
fn test_parse_host_cert() { fn test_parse_host_cert() {
let line = "| HC | HOST_PRIV_KEY ***REMOVED*** | HOST_CERT ***REMOVED*** ; Revoked"; let line = "| HC | HOST_PRIV_KEY 0xDEADBEEF000102030405060708090A0B0C0D0E0F | HOST_CERT 0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B ; Revoked";
let hc = KeyDb::parse_host_cert(line).unwrap(); let hc = KeyDb::parse_host_cert(line).unwrap();
assert_eq!(hc.private_key[0], 0x90); assert_eq!(hc.private_key[0], 0xDE);
assert_eq!(hc.certificate.len(), 92); assert_eq!(hc.certificate.len(), 92);
} }
@@ -528,15 +528,15 @@ mod tests {
assert!(!db.host_certs.is_empty()); assert!(!db.host_certs.is_empty());
assert!(db.disc_entries.len() > 170000); assert!(db.disc_entries.len() > 170000);
// Look up Dune: Part Two // Look up any disc entry carrying a full key set.
let dune = db let entry = db
.disc_entries .disc_entries
.values() .values()
.find(|e| e.title.contains("Dune: Part Two") && e.vuk.is_some()) .find(|e| e.vuk.is_some() && e.media_key.is_some() && !e.unit_keys.is_empty())
.expect("Dune: Part Two not found"); .expect("no disc entry with a full key set");
assert!(dune.media_key.is_some()); assert!(entry.media_key.is_some());
assert!(dune.vuk.is_some()); assert!(entry.vuk.is_some());
assert!(!dune.unit_keys.is_empty()); assert!(!entry.unit_keys.is_empty());
eprintln!( eprintln!(
"Parsed {} disc entries, {} DK, {} PK", "Parsed {} disc entries, {} DK, {} PK",
+33 -36
View File
@@ -1163,8 +1163,7 @@ mod tests {
#[test] #[test]
fn test_vuk_derivation() { fn test_vuk_derivation() {
// Civil War UHD: known MK, VID, VUK from KEYDB // Pick any UHD entry with a known MK, VID, and VUK from KEYDB.
// MK = 15665F98..., VID (disc_id) = from entry, VUK = F96D7908...
// VUK = AES-DEC(MK, VID) XOR VID // VUK = AES-DEC(MK, VID) XOR VID
let path = match keydb_path() { let path = match keydb_path() {
Some(p) => p, Some(p) => p,
@@ -1237,7 +1236,7 @@ mod tests {
#[test] #[test]
fn test_decrypt_real_unit() { fn test_decrypt_real_unit() {
// Try decrypting a real encrypted aligned unit from Civil War UHD // Try decrypting a real encrypted aligned unit from a UHD sample.
// This disc is AACS 2.0 (BEE) so unit key alone won't work — // This disc is AACS 2.0 (BEE) so unit key alone won't work —
// we need bus decryption first. But this verifies the pipeline. // we need bus decryption first. But this verifies the pipeline.
let unit_path = std::path::Path::new("/tmp/encrypted_unit.bin"); let unit_path = std::path::Path::new("/tmp/encrypted_unit.bin");
@@ -1258,20 +1257,17 @@ mod tests {
}; };
let db = KeyDb::load(&kp).unwrap(); let db = KeyDb::load(&kp).unwrap();
// Civil War UHD entries // Candidate entries: any UHD entry that carries unit keys.
let civil_war_entries: Vec<&DiscEntry> = db let candidate_entries: Vec<&DiscEntry> = db
.disc_entries .disc_entries
.values() .values()
.filter(|e| e.title.contains("CIVIL WAR") && !e.unit_keys.is_empty()) .filter(|e| !e.unit_keys.is_empty())
.collect(); .collect();
eprintln!( eprintln!("Found {} entries with unit keys", candidate_entries.len());
"Found {} Civil War entries with unit keys",
civil_war_entries.len()
);
// Try each entry's unit keys // Try each entry's unit keys
for entry in &civil_war_entries { for entry in &candidate_entries {
let keys: Vec<[u8; 16]> = entry.unit_keys.iter().map(|(_, k)| *k).collect(); let keys: Vec<[u8; 16]> = entry.unit_keys.iter().map(|(_, k)| *k).collect();
let mut unit = original.clone(); let mut unit = original.clone();
@@ -1304,10 +1300,11 @@ mod tests {
#[test] #[test]
fn test_disc_hash_hex() { fn test_disc_hash_hex() {
let hash = [ let hash = [
***REMOVED***, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
]; ];
let hex = disc_hash_hex(&hash); let hex = disc_hash_hex(&hash);
assert_eq!(hex, "***REMOVED***"); assert_eq!(hex, "0x000102030405060708090A0B0C0D0E0F10111213");
} }
#[test] #[test]
@@ -1601,29 +1598,26 @@ mod tests {
assert_eq!(selected, only07, "fallback returns the 0x07 body"); assert_eq!(selected, only07, "fallback returns the 0x07 body");
} }
/// Locate a captured MKB research sample, if the private research tree /// Locate a captured MKB sample under the optional `MKB_SAMPLE_DIR`.
/// is checked out alongside the crate. Returns `None` (skip) otherwise. /// Returns `None` (skip) when the directory or file is absent.
fn mkb_sample(rel: &str) -> Option<std::path::PathBuf> { fn mkb_sample(rel: &str) -> Option<std::path::PathBuf> {
let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) let dir = std::env::var("MKB_SAMPLE_DIR").ok()?;
.parent()? // freemkv/ let p = std::path::Path::new(&dir).join(rel);
.join("(internal)/research/aacs/mkb-samples")
.join(rel);
if p.exists() { Some(p) } else { None } if p.exists() { Some(p) } else { None }
} }
#[test] #[test]
fn real_aacs2_samples_select_large_0x05_not_small_0x07() { fn real_aacs2_samples_select_large_0x05_not_small_0x07() {
// Real in-drive AACS 2.x UHD MKBs (Wicked / Civil War / MOVIE) // Real in-drive AACS 2.x UHD MKBs carry BOTH a small 0x07
// carry BOTH a small 0x07 Explicit-Subset-Difference record (96 // Explicit-Subset-Difference record (96 16-byte entries) AND the
// 16-byte entries) AND the large 0x05 Media Key Data / cvalue table // large 0x05 Media Key Data / cvalue table (181270 entries, 1:1
// (181270 entries, 1:1 with the 0x04 index). The production selector // with the 0x04 index). The production selector must return the
// must return the LARGE 0x05 body, not the small 0x07 one. This is // LARGE 0x05 body, not the small 0x07 one. This is the exact
// the exact regression #259 found. Skips when the research tree is // regression #259 found. Skips when no sample dir is present.
// absent.
let samples = [ let samples = [
"wicked/MKB_RO.inf", "sample-a/MKB_RO.inf",
"civilwar-uhd/MKB_RO.inf", "sample-b/MKB_RO.inf",
"movie-uhd-2.1/MKB_RO.inf", "sample-c/MKB_RO.inf",
]; ];
let mut checked = 0; let mut checked = 0;
for rel in samples { for rel in samples {
@@ -1707,29 +1701,32 @@ mod tests {
}; };
let db = KeyDb::load(&path).unwrap(); let db = KeyDb::load(&path).unwrap();
// Find V for Vendetta BD — has VUK and unit keys // Find any BD entry that carries a VUK and unit keys, then exercise
// hash: ***REMOVED*** // the lookup-by-hash + VUK-derivation chain against it.
let entry = db.find_disc("***REMOVED***"); let entry = db
.disc_entries
.values()
.find(|e| e.vuk.is_some() && !e.unit_keys.is_empty() && e.disc_id.is_some());
if entry.is_none() { if entry.is_none() {
return; return;
} }
let entry = entry.unwrap(); let entry = entry.unwrap();
let vuk = entry.vuk.unwrap(); let vuk = entry.vuk.unwrap();
let vid = entry.disc_id.unwrap(); let vid = entry.disc_id.unwrap();
let hash_hex = format!("0x{}", entry.disc_hash.trim_start_matches("0x"));
// We need the actual Unit_Key_RO.inf from the disc to compute disc hash. // We need the actual Unit_Key_RO.inf from the disc to compute disc hash.
// Since we don't have it, we can at least test that the KEYDB lookup // Since we don't have it, we can at least test that the KEYDB lookup
// works with a known hash. // works with a known hash.
let hash_hex = "***REMOVED***"; let found = db.find_disc(&hash_hex);
let found = db.find_disc(hash_hex);
assert!(found.is_some()); assert!(found.is_some());
assert_eq!(found.unwrap().vuk, Some(vuk)); assert_eq!(found.unwrap().vuk, Some(vuk));
// Verify VUK derivation if we have MK + VID // Verify VUK derivation if we have MK + VID
if let Some(mk) = entry.media_key { if let Some(mk) = entry.media_key {
let derived = derive_vuk(&mk, &vid); let derived = derive_vuk(&mk, &vid);
assert_eq!(derived, vuk, "VUK derivation mismatch for V for Vendetta"); assert_eq!(derived, vuk, "VUK derivation mismatch");
eprintln!("V for Vendetta VUK derivation verified"); eprintln!("VUK derivation verified");
} }
} }
+2 -2
View File
@@ -981,9 +981,9 @@ impl ScanOptions {
/// Quick disc identification — name, format, capacity. No title/stream parsing. /// Quick disc identification — name, format, capacity. No title/stream parsing.
#[derive(Debug)] #[derive(Debug)]
pub struct DiscId { pub struct DiscId {
/// UDF Volume Identifier (always present, e.g. "V_FOR_VENDETTA") /// UDF Volume Identifier (always present, e.g. "SAMPLE_FILM")
pub volume_id: String, pub volume_id: String,
/// Disc title from META/DL/bdmt_eng.xml (e.g. "V for Vendetta") /// Disc title from META/DL/bdmt_eng.xml (e.g. "Sample Film")
pub meta_title: Option<String>, pub meta_title: Option<String>,
/// Disc format (BD, UHD, DVD) — UHD vs BD requires full scan to confirm /// Disc format (BD, UHD, DVD) — UHD vs BD requires full scan to confirm
pub format: DiscFormat, pub format: DiscFormat,
+1 -1
View File
@@ -334,7 +334,7 @@ const FAIL_PAUSE_SECS: u64 = 5;
/// FIRST read failure after a clean run, before the drive has had a /// FIRST read failure after a clean run, before the drive has had a
/// chance to cycle in retries that push it toward fast-fail). /// chance to cycle in retries that push it toward fast-fail).
/// ///
/// Empirical: 2026-05-11 Dune Pt 2 wedge incident showed 7 medium /// Empirical: a 2026-05-11 wedge incident showed 7 medium
/// errors in 6.5 seconds (~1s per attempt + ~1s pause) push the /// errors in 6.5 seconds (~1s per attempt + ~1s pause) push the
/// BU40N's firmware into IllegalRequest fast-fail mode permanently. /// BU40N's firmware into IllegalRequest fast-fail mode permanently.
/// Once there, only physical eject + reload clears it. Giving the /// Once there, only physical eject + reload clears it. Giving the
+1 -1
View File
@@ -529,7 +529,7 @@ impl Drive {
// /dev/sr0 pread fallback (Linux only). The kernel // /dev/sr0 pread fallback (Linux only). The kernel
// sr_mod driver auto-retries failed reads (~5× per // sr_mod driver auto-retries failed reads (~5× per
// command). Empirically (BU40N + Dune Part 2 UHD, // command). Empirically (BU40N + a UHD disc,
// 2026-05-08) dd via /dev/sr0 recovers ~50% of bad // 2026-05-08) dd via /dev/sr0 recovers ~50% of bad
// sectors that a single-shot SG_IO READ misses. // sectors that a single-shot SG_IO READ misses.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
-3
View File
@@ -16,9 +16,6 @@
//! The underlying mpsc channel is created with a very large slot count //! The underlying mpsc channel is created with a very large slot count
//! so the byte cap (not the slot count) is the real backpressure. Slot //! so the byte cap (not the slot count) is the real backpressure. Slot
//! count is only there to give the kernel a small chunk to wake on. //! count is only there to give the kernel a small chunk to wake on.
//!
//! See `(internal)/memory/project_buffering_architecture.md` §
//! Pipeline channel — sizing.
use std::sync::mpsc::{Receiver as MpscReceiver, RecvError, SendError, SyncSender, sync_channel}; use std::sync::mpsc::{Receiver as MpscReceiver, RecvError, SendError, SyncSender, sync_channel};
use std::sync::{Arc, Condvar, Mutex}; use std::sync::{Arc, Condvar, Mutex};
+1 -2
View File
@@ -23,8 +23,7 @@
//! bounded channel + dedicated consumer thread. //! bounded channel + dedicated consumer thread.
//! //!
//! `byte_channel` is a byte-sized producer/consumer channel for the //! `byte_channel` is a byte-sized producer/consumer channel for the
//! mux pipeline, sized to absorb worst-case input read stalls (see //! mux pipeline, sized to absorb worst-case input read stalls.
//! `(internal)/memory/project_buffering_architecture.md`).
pub(crate) mod bounded; pub(crate) mod bounded;
pub mod byte_channel; pub mod byte_channel;
+1 -4
View File
@@ -9,7 +9,6 @@
//! sweep (migrated to `disc/sweep.rs::SweepSink`), patch, and mux. //! sweep (migrated to `disc/sweep.rs::SweepSink`), patch, and mux.
//! 0.18 collapses all three onto this primitive; sweep is in, //! 0.18 collapses all three onto this primitive; sweep is in,
//! patch and mux migrate in later 0.18 slices. //! patch and mux migrate in later 0.18 slices.
//! See `(internal)/memory/0_18_redesign.md` for the full picture.
//! //!
//! ## Cancellation and error semantics //! ## Cancellation and error semantics
//! //!
@@ -68,9 +67,7 @@ pub const JOIN_TIMEOUT_SECS: u64 = 600;
/// ///
/// 0.21.7 replaced an old `std::sync::mpsc::sync_channel` + 50 ms /// 0.21.7 replaced an old `std::sync::mpsc::sync_channel` + 50 ms
/// `thread::sleep` polling loop that capped mux throughput at /// `thread::sleep` polling loop that capped mux throughput at
/// ~20 frames/sec ≈ 1 MB/s on saturated channels. See /// ~20 frames/sec ≈ 1 MB/s on saturated channels.
/// (internal)/memory/feedback_send_with_halt_poll_throttle.md
/// for the multi-day diagnostic that surfaced it.
use crate::halt::POLL_INTERVAL; use crate::halt::POLL_INTERVAL;
const SEND_HALT_CHECK_INTERVAL: Duration = POLL_INTERVAL; const SEND_HALT_CHECK_INTERVAL: Duration = POLL_INTERVAL;
-3
View File
@@ -20,9 +20,6 @@
//! 4 MiB buffer for the common local-disk case; `WritebackFile` //! 4 MiB buffer for the common local-disk case; `WritebackFile`
//! (separate module) wraps a `File` with the adaptive-chunk //! (separate module) wraps a `File` with the adaptive-chunk
//! `sync_file_range` machinery for the Linux+NFS case. //! `sync_file_range` machinery for the Linux+NFS case.
//!
//! See `(internal)/memory/project_buffering_architecture.md` for
//! the full design and the source/sink matrix.
use std::io::{Seek, Write}; use std::io::{Seek, Write};
+1 -1
View File
@@ -47,7 +47,7 @@ impl SocketSink {
/// writing. `sndbuf_bytes`, when present, is forwarded to /// writing. `sndbuf_bytes`, when present, is forwarded to
/// `setsockopt(SO_SNDBUF)` as a kernel hint — the OS may clamp it. /// `setsockopt(SO_SNDBUF)` as a kernel hint — the OS may clamp it.
/// ///
/// `addr` accepts anything `ToSocketAddrs` does: `"10.0.0.1:1234"`, /// `addr` accepts anything `ToSocketAddrs` does: `"192.0.2.1:1234"`,
/// `("host", 1234)`, a `SocketAddr`, etc. /// `("host", 1234)`, a `SocketAddr`, etc.
pub fn connect<A: ToSocketAddrs>(addr: A, sndbuf_bytes: Option<usize>) -> io::Result<Self> { pub fn connect<A: ToSocketAddrs>(addr: A, sndbuf_bytes: Option<usize>) -> io::Result<Self> {
let stream = TcpStream::connect(addr)?; let stream = TcpStream::connect(addr)?;
+24 -24
View File
@@ -38,7 +38,7 @@ use std::collections::BTreeMap;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct DiscMetadata { pub struct DiscMetadata {
/// Localized titles, keyed by 3-char ISO 639-2 lang code /// Localized titles, keyed by 3-char ISO 639-2 lang code
/// (e.g. "eng" → "Dune Part Two") /// (e.g. "eng" → "Aurora Drift")
pub titles: BTreeMap<String, String>, pub titles: BTreeMap<String, String>,
/// First-line / short description, per lang /// First-line / short description, per lang
pub descriptions: BTreeMap<String, String>, pub descriptions: BTreeMap<String, String>,
@@ -146,7 +146,7 @@ pub(crate) fn parse_bdmt_xml(_lang_code: &str, xml_text: &str) -> Option<BdmtFie
} }
/// Reject candidate description strings that are themselves XML /// Reject candidate description strings that are themselves XML
/// fragments — observed on disc-04 (Top Gun: Maverick), where /// fragments — observed on a captured disc, where
/// `<di:description>` contained `<di:thumbnail href="…"/>` child /// `<di:description>` contained `<di:thumbnail href="…"/>` child
/// elements and no actual prose. Surfacing that raw to the JSON /// elements and no actual prose. Surfacing that raw to the JSON
/// output is worse than dropping the field entirely. /// output is worse than dropping the field entirely.
@@ -210,10 +210,10 @@ mod tests {
// carrier inside a <discInfo> root. // carrier inside a <discInfo> root.
let xml = r#"<?xml version="1.0" encoding="UTF-8"?> let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta"> <discInfo xmlns:di="urn:BDA:bdmv;disclibmeta">
<di:name>Dune Part Two</di:name> <di:name>Aurora Drift</di:name>
</discInfo>"#; </discInfo>"#;
let (title, desc, set) = parse_bdmt_xml("eng", xml).expect("title should parse"); let (title, desc, set) = parse_bdmt_xml("eng", xml).expect("title should parse");
assert_eq!(title, "Dune Part Two"); assert_eq!(title, "Aurora Drift");
assert_eq!(desc, None); assert_eq!(desc, None);
assert_eq!(set, None); assert_eq!(set, None);
} }
@@ -223,12 +223,12 @@ mod tests {
// <di:title> is the alternate carrier; should be picked up // <di:title> is the alternate carrier; should be picked up
// when <di:name> is absent. // when <di:name> is absent.
let xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta"> let xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta">
<di:title>The Matrix</di:title> <di:title>Echo Chamber</di:title>
<di:description>A film about computers.</di:description> <di:description>A film about machines.</di:description>
</discInfo>"#; </discInfo>"#;
let (title, desc, _) = parse_bdmt_xml("eng", xml).unwrap(); let (title, desc, _) = parse_bdmt_xml("eng", xml).unwrap();
assert_eq!(title, "The Matrix"); assert_eq!(title, "Echo Chamber");
assert_eq!(desc.as_deref(), Some("A film about computers.")); assert_eq!(desc.as_deref(), Some("A film about machines."));
} }
#[test] #[test]
@@ -238,17 +238,17 @@ mod tests {
// titleName inside tableOfContents. // titleName inside tableOfContents.
let xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta"> let xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta">
<di:tableOfContents> <di:tableOfContents>
<di:titleName>Inside Out 2</di:titleName> <di:titleName>Feelings Two</di:titleName>
</di:tableOfContents> </di:tableOfContents>
</discInfo>"#; </discInfo>"#;
let (title, _, _) = parse_bdmt_xml("eng", xml).unwrap(); let (title, _, _) = parse_bdmt_xml("eng", xml).unwrap();
assert_eq!(title, "Inside Out 2"); assert_eq!(title, "Feelings Two");
} }
#[test] #[test]
fn extract_box_set_position() { fn extract_box_set_position() {
let xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta"> let xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta">
<di:name>LOTR Disc 2</di:name> <di:name>Box Set Disc 2</di:name>
<di:discNumber>2</di:discNumber> <di:discNumber>2</di:discNumber>
<di:numSets>5</di:numSets> <di:numSets>5</di:numSets>
</discInfo>"#; </discInfo>"#;
@@ -287,11 +287,11 @@ mod tests {
// would. This exercises the BTreeMap key handling without // would. This exercises the BTreeMap key handling without
// needing a UdfFs. // needing a UdfFs.
let eng_xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta"> let eng_xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta">
<di:name>Dune Part Two</di:name> <di:name>Aurora Drift</di:name>
</discInfo>"#; </discInfo>"#;
let fra_xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta"> let fra_xml = r#"<discInfo xmlns:di="urn:BDA:bdmv;disclibmeta">
<di:name>Dune Deuxième Partie</di:name> <di:name>Aurora Drift (Partie Deux)</di:name>
<di:description>Suite du film de 2021.</di:description> <di:description>Suite du film fictif.</di:description>
</discInfo>"#; </discInfo>"#;
let mut meta = DiscMetadata::default(); let mut meta = DiscMetadata::default();
@@ -310,16 +310,16 @@ mod tests {
assert_eq!( assert_eq!(
meta.titles.get("eng").map(String::as_str), meta.titles.get("eng").map(String::as_str),
Some("Dune Part Two") Some("Aurora Drift")
); );
assert_eq!( assert_eq!(
meta.titles.get("fra").map(String::as_str), meta.titles.get("fra").map(String::as_str),
Some("Dune Deuxième Partie") Some("Aurora Drift (Partie Deux)")
); );
assert!(meta.descriptions.get("eng").is_none()); assert!(meta.descriptions.get("eng").is_none());
assert_eq!( assert_eq!(
meta.descriptions.get("fra").map(String::as_str), meta.descriptions.get("fra").map(String::as_str),
Some("Suite du film de 2021.") Some("Suite du film fictif.")
); );
assert_eq!(meta.disc_number, None); assert_eq!(meta.disc_number, None);
} }
@@ -342,21 +342,21 @@ mod tests {
#[test] #[test]
fn description_with_only_child_xml_is_dropped() { fn description_with_only_child_xml_is_dropped() {
// Real-world bug from disc-04 (Top Gun: Maverick, 2026-05-11 // Real-world bug from a captured disc (2026-05-11
// capture): <di:description> contained only <di:thumbnail/> // capture): <di:description> contained only <di:thumbnail/>
// child elements with no actual prose. The previous parser // child elements with no actual prose. The previous parser
// surfaced the raw XML fragment as the description string. // surfaced the raw XML fragment as the description string.
// Now we reject candidates that begin with `<`. // Now we reject candidates that begin with `<`.
let xml = r#"<discInfo> let xml = r#"<discInfo>
<di:name>Top Gun: Maverick</di:name> <di:name>Skyline Run</di:name>
<di:description> <di:description>
<di:thumbnail href="tgm_meta_sm.jpg" /> <di:thumbnail href="sample_meta_sm.jpg" />
<di:thumbnail href="tgm_meta_lg.jpg" /> <di:thumbnail href="sample_meta_lg.jpg" />
</di:description> </di:description>
</discInfo>"#; </discInfo>"#;
let (title, description, _) = let (title, description, _) =
parse_bdmt_xml("eng", xml).expect("title is present so parse must succeed"); parse_bdmt_xml("eng", xml).expect("title is present so parse must succeed");
assert_eq!(title, "Top Gun: Maverick"); assert_eq!(title, "Skyline Run");
assert!( assert!(
description.is_none(), description.is_none(),
"description containing only XML children must be dropped, got {description:?}" "description containing only XML children must be dropped, got {description:?}"
@@ -381,10 +381,10 @@ mod tests {
#[test] #[test]
fn whitespace_in_title_is_trimmed() { fn whitespace_in_title_is_trimmed() {
let xml = r#"<discInfo><di:name> let xml = r#"<discInfo><di:name>
Dune Part Two Aurora Drift
</di:name></discInfo>"#; </di:name></discInfo>"#;
let (title, _, _) = parse_bdmt_xml("eng", xml).unwrap(); let (title, _, _) = parse_bdmt_xml("eng", xml).unwrap();
assert_eq!(title, "Dune Part Two"); assert_eq!(title, "Aurora Drift");
} }
#[test] #[test]
+1 -2
View File
@@ -7,8 +7,7 @@
//! Stream labels live as plain ASCII strings inside compiled `.class` //! Stream labels live as plain ASCII strings inside compiled `.class`
//! files in the jar — a quirk of the menu-rendering layer encoding //! files in the jar — a quirk of the menu-rendering layer encoding
//! its TextField positions and content as constant strings the //! its TextField positions and content as constant strings the
//! Java compiler retained in the class string pool. Format observed //! Java compiler retained in the class string pool. Observed format:
//! in the corpus (Civil War UHD, 2024):
//! //!
//! ```text //! ```text
//! LTextField,Audio1,English Dolby Atmos,Fontstrip_Composite,... //! LTextField,Audio1,English Dolby Atmos,Fontstrip_Composite,...
+38 -60
View File
@@ -1,83 +1,65 @@
//! Deluxe BD-J framework — `com/bydeluxe/bluray/` package signature. //! Deluxe BD-J framework — `com/bydeluxe/bluray/` package signature.
//! //!
//! Used by major studios (Disney, Warner, others) for their UHD //! Detected on discs whose `/BDMV/JAR/<x>.jar` contains a
//! BD-J authoring. Detected on discs whose `/BDMV/JAR/<x>.jar` //! `com/bydeluxe/` directory entry.
//! contains a `com/bydeluxe/` directory entry.
//! //!
//! ## Why this parser exists //! ## What this parser reads
//! //!
//! Deluxe-authored discs store stream labels as **ordinal references //! Deluxe-authored discs store stream labels as ordinal references into
//! into obfuscated enum classes**. The label text isn't a literal //! enum classes whose names are obfuscated per-disc, so a name-based
//! string in any anchor pattern (unlike dbp's `TextField,...` rows). //! match won't work. The label data is instead recovered by matching on
//! Instead, the binding code is roughly: //! the **shape of each enum's `<clinit>`**, which is framework-stable:
//!
//! ```java
//! streamTable.put(1, new AudioSlot(LanguageEnum.English,
//! CodecEnum.ATMOS_HD_AUDIO,
//! PurposeEnum.Normal));
//! ```
//!
//! The class names `LanguageEnum`, `CodecEnum`, `PurposeEnum`, and
//! `AudioSlot` are obfuscated per-disc (`be.class`, `ma.class`,
//! `lp.class`, etc.) — no name pattern survives the obfuscator. But
//! the **shape of `<clinit>`** is framework-stable:
//! //!
//! | Enum | Signature | //! | Enum | Signature |
//! |---|---| //! |---|---|
//! | Language | 70 `ldc` operations in `<clinit>`, sequence starts `English, French, Spanish, Dutch, ...` | //! | Language | 70 `ldc` operations in `<clinit>`, sequence starts `English, French, Spanish, Dutch, ...` |
//! | Purpose | 8 ldcs starting `Normal, Commentary, PiP, Trivia, ...` | //! | Purpose | 8 ldcs starting `Normal, Commentary, PiP, Trivia, ...` |
//! | VideoFormat | 7 ldcs starting `HD, HDR10 Plus, HD Dolby, ...` | //! | VideoFormat | 7 ldcs starting `HD, HDR10 Plus, HD Dolby, ...` |
//! | Region | 22 ldcs starting `USA_D1, LIC1, LIC2, LIC3, ...` (Disney only) | //! | Region | 22 ldcs starting `USA_D1, LIC1, LIC2, LIC3, ...` |
//! | Studio | 6 ldcs starting `Disney, Marvel, Pixar, ...` (Disney only) | //! | Studio | 6 ldcs starting `Disney, Marvel, Pixar, ...` |
//! | Codec | ~46 `new` instructions, 0 ldcs in `<clinit>` (codec strings live in subclasses) | //! | Codec | many `new` instructions, 0 ldcs in `<clinit>` (codec strings live in subclasses) |
//! //!
//! Match on the SHAPE, not the name, and the parser survives obfuscation. //! Matching on the shape rather than the class name keeps the parser
//! working across obfuscation variants.
//! //!
//! ## Implementation phases //! ## Implementation phases
//! //!
//! - **Phase A** — master enum identification (`identify_master_enums`). //! - **Phase A** — master enum identification (`identify_master_enums`).
//! Walks every `.class`'s `<clinit>` ldc sequence and matches against //! Walks every `.class`'s `<clinit>` ldc sequence and matches against
//! the framework-stable fingerprints. Output: `Vec<(label, MasterEnum)>` //! the framework-stable fingerprints. Output: `Vec<(label, MasterEnum)>`
//! with full ordinal → string-value tables. **Empirically verified** //! with full ordinal → string-value tables.
//! on disc-01 (Disney) + disc-09 (Warner).
//! //!
//! - **Phase B** — codec enum subclass walk (`decode_codec_enum`). //! - **Phase B** — codec enum subclass walk (`decode_codec_enum`).
//! The codec enum's `<clinit>` has ~46 `new` instructions and zero //! The codec enum's `<clinit>` has many `new` instructions and zero
//! string ldcs — codec name strings live in the subclasses each //! string ldcs — codec name strings live in the subclasses each
//! `new` constructs. Walks every referenced subclass's constant //! `new` constructs. Walks every referenced subclass's constant
//! pool, extracts the codec name string. **Structural shape //! pool, extracts the codec name string, following the standard Java
//! verified** on disc-01 (ma.class, 41 `new` ops) + disc-09 //! enum compilation convention (each enum value's `<init>` is called
//! (ea.class, 46 `new` ops); per-subclass string extraction //! with its name string as the first arg).
//! designed against the published Java enum compilation convention
//! (each enum value's `<init>` is called with its name string as
//! the first arg).
//! //!
//! - **Phase C** — binding-class identification (`find_binding_classes`). //! - **Phase C** — binding-class identification (`find_binding_classes`).
//! The per-stream table is built by some class via repeated //! The per-stream table is built by some class via repeated
//! `getstatic` references to the master enums identified in A. //! `getstatic` references to the master enums identified in A.
//! That class has the highest such `getstatic` count in the jar. //! That class has the highest such `getstatic` count in the jar.
//! **Heuristic shape**; precise threshold may need tuning. //! Heuristic shape; precise threshold may need tuning.
//! //!
//! - **Phase D** — binding-class bytecode decoder (`decode_binding`). //! - **Phase D** — binding-class bytecode decoder (`decode_binding`).
//! Walks the binding class's `<clinit>` with a tiny symbolic stack //! Walks the binding class's `<clinit>` with a tiny symbolic stack
//! machine. For each `new X / dup / ... / invokespecial X.<init>` //! machine. For each `new X / dup / ... / invokespecial X.<init>`
//! sequence, collects the int values and enum-reference operands //! sequence, collects the int values and enum-reference operands
//! between the `dup` and the constructor call, then emits a //! between the `dup` and the constructor call, then emits a
//! `DecodedStream`. **Mechanism verified** in unit tests against //! `DecodedStream`. The signal-to-StreamLabel mapping (which arg is
//! synthetic class fixtures; the **signal-to-StreamLabel mapping** //! stream index? which is language? audio vs subtitle?) uses a
//! (which arg is stream index? which is language? audio vs //! heuristic — see `interpret_streams` for the mapping rules.
//! subtitle?) uses a documented heuristic that needs corpus-disc
//! verification — see `interpret_stream` for the mapping rules.
//! //!
//! ## Confidence //! ## Confidence
//! //!
//! [`parse`] returns `Some(ParseResult::medium(labels))` when Phases A //! [`parse`] returns `Some(ParseResult::medium(labels))` when Phases A
//! through D produce at least one stream — `Medium` because the //! through D produce at least one stream — `Medium` because the
//! signal-to-label mapping is heuristic until real disc bytecode //! signal-to-label mapping is heuristic. `None` when the disc isn't
//! confirms the binding pattern. Once verified the parser can promote //! Deluxe-authored or when decoding produces zero streams (a
//! to `High`. `None` when the disc isn't Deluxe-authored or when //! recognized-but-broken state that the analyzer still surfaces via
//! decoding produces zero streams (a recognized-but-broken state that //! `parsers_detected`).
//! the analyzer still surfaces via `parsers_detected`).
use super::class_reader::{ use super::class_reader::{
AASTORE, BIPUSH, ClassFile, CodeAttribute, ConstantPool, CpInfo, GETSTATIC, ICONST_0, ICONST_1, AASTORE, BIPUSH, ClassFile, CodeAttribute, ConstantPool, CpInfo, GETSTATIC, ICONST_0, ICONST_1,
@@ -559,10 +541,9 @@ fn clinit_news_and_ldcs(
/// candidates ordered by descending getstatic count, filtered to a /// candidates ordered by descending getstatic count, filtered to a
/// minimum concentration of master-enum references. /// minimum concentration of master-enum references.
/// ///
/// Empirically (POC v0.3 dumps): on disc-01 the audio binding class /// A disc that splits the table commonly has one audio binding class
/// (`ma.class`) has ~82 getstatic refs, the subtitle binding /// with the most getstatic refs and a subtitle binding class with
/// (`ko.class`) has ~63. Both share the master Language + Purpose /// somewhat fewer; both share the master Language + Purpose enums.
/// enums.
pub(crate) fn find_binding_classes( pub(crate) fn find_binding_classes(
archive: &mut jar::Jar, archive: &mut jar::Jar,
master_enum_classes: &HashSet<&str>, master_enum_classes: &HashSet<&str>,
@@ -641,10 +622,9 @@ pub(crate) enum StackVal {
/// name (e.g. `DOLBY_AC3_AUDIO`, `DOLBY_LOSSLESS_AUDIO`) is the /// name (e.g. `DOLBY_AC3_AUDIO`, `DOLBY_LOSSLESS_AUDIO`) is the
/// codec identifier. Deluxe binding constructors take a /// codec identifier. Deluxe binding constructors take a
/// `LCodingType;` arg directly — codecs are NOT a Deluxe-internal /// `LCodingType;` arg directly — codecs are NOT a Deluxe-internal
/// enum (Phase B's codec-subclass walk was based on a wrong /// enum; the codec source is the standard BD-J API `CodingType`
/// assumption; the actual codec source is the standard BD-J API /// enum, so the binding constructor's codec arg is read straight
/// enum). Discovered via deluxe-poc v0.3 binding-bytecode dump /// from that getstatic operand.
/// against disc-01 (Disney) + disc-09 (Warner) on 2026-05-10.
CodingType(String), CodingType(String),
/// An uninitialized `new` object — popped by the matching /// An uninitialized `new` object — popped by the matching
/// invokespecial. /// invokespecial.
@@ -988,11 +968,10 @@ impl MasterEnumTable {
// ── interpret_streams: Constructions → StreamLabels ───────────────────────── // ── interpret_streams: Constructions → StreamLabels ─────────────────────────
/// Convert the per-construction tuples from Phase D into /// Convert the per-construction tuples from Phase D into
/// [`StreamLabel`]s. Pattern verified against corpus discs via /// [`StreamLabel`]s. Two binding-constructor shapes are handled:
/// deluxe-poc v0.3 binding-bytecode dump (2026-05-10):
/// ///
/// Disney binding (5-arg): `BindingType.<init>(I, Lbe;, Llp;, I, LCodingType;)V` /// 5-arg: `BindingType.<init>(I, Lang;, Lpurpose;, I, LCodingType;)V`
/// Warner binding (4-arg): `BindingType.<init>(I, Law;, Lgp;, LCodingType;)V` /// 4-arg: `BindingType.<init>(I, Lang;, Lpurpose;, LCodingType;)V`
/// ///
/// Args are identified by **TYPE**, not position: /// Args are identified by **TYPE**, not position:
/// - First `EnumRef{kind: "Language"}` → audio/subtitle language /// - First `EnumRef{kind: "Language"}` → audio/subtitle language
@@ -1098,11 +1077,10 @@ fn interpret_streams(constructions: &[Construction], master: &MasterEnumTable) -
/// getstatic operands on Deluxe binding classes) to a human-readable /// getstatic operands on Deluxe binding classes) to a human-readable
/// codec hint string. /// codec hint string.
/// ///
/// CodingType is the standard BD-J API enum; values are documented /// CodingType is the standard BD-J API enum; values are documented in
/// in the BD-J specification and verified empirically against the /// the BD-J specification. Unknown field names pass through unchanged
/// binding-bytecode dumps in `(internal)/research/deluxe-poc/data/`. /// so unfamiliar codecs still surface something rather than going
/// Unknown field names pass through unchanged so unfamiliar codecs /// silent.
/// still surface something rather than going silent.
fn coding_type_to_codec_hint(field: &str) -> &str { fn coding_type_to_codec_hint(field: &str) -> &str {
match field { match field {
// Lossless / hi-res. // Lossless / hi-res.
+1 -1
View File
@@ -954,7 +954,7 @@ mod gap_fill_tests {
#[test] #[test]
fn partial_yield_fills_gaps_keeps_framework() { fn partial_yield_fills_gaps_keeps_framework() {
// Oppenheimer-style: framework matched but only labeled 2 of 6 audios. // Partial-coverage case: framework matched but only labeled 2 of 6 audios.
let mut framework = vec![ let mut framework = vec![
label(StreamLabelType::Audio, 1, "eng", "Atmos"), label(StreamLabelType::Audio, 1, "eng", "Atmos"),
label(StreamLabelType::Audio, 4, "eng", "Commentary"), label(StreamLabelType::Audio, 4, "eng", "Commentary"),
+1 -1
View File
@@ -300,7 +300,7 @@ mod tests {
#[test] #[test]
fn assign_labels_numbers_commentary_behind_placeholders() { fn assign_labels_numbers_commentary_behind_placeholders() {
// Wicked: the FPL_MainFeature playlist lists three unlabelled main // Observed case: the FPL_MainFeature playlist lists three unlabelled main
// audio tracks as `Audio Stream N` placeholders, then a lone // audio tracks as `Audio Stream N` placeholders, then a lone
// `eng_ACOM_` commentary at STN slot 4. The commentary must land on // `eng_ACOM_` commentary at STN slot 4. The commentary must land on
// audio #4, not collapse onto #1 (which would tag the main feature // audio #4, not collapse onto #1 (which would tag the main feature
+1 -2
View File
@@ -10,8 +10,7 @@
//! - English text → [`LabelPurpose`] (Commentary / Descriptive / etc.). //! - English text → [`LabelPurpose`] (Commentary / Descriptive / etc.).
//! - English text → [`LabelQualifier`] (SDH / Forced / Descriptive Service). //! - English text → [`LabelQualifier`] (SDH / Forced / Descriptive Service).
//! //!
//! Rules of engagement (carried over from //! Rules of engagement:
//! `(internal)/memory/feedback_label_data_rules.md`):
//! //!
//! 1. Only map values we are 100% certain about — published codec //! 1. Only map values we are 100% certain about — published codec
//! names, well-known ISO 639-2 mappings, vendor-documented purpose //! names, well-known ISO 639-2 mappings, vendor-documented purpose
+1 -1
View File
@@ -6,7 +6,7 @@
//! ```text //! ```text
//! let mut input = input("iso://Disc.iso", &opts)?; //! let mut input = input("iso://Disc.iso", &opts)?;
//! let title = input.info().clone(); //! let title = input.info().clone();
//! let mut output = output("mkv://Dune.mkv", &title)?; //! let mut output = output("mkv://Movie.mkv", &title)?;
//! while let Ok(Some(frame)) = input.read() { //! while let Ok(Some(frame)) = input.read() {
//! output.write(&frame)?; //! output.write(&frame)?;
//! } //! }
+1 -3
View File
@@ -9,9 +9,7 @@
//! [`ScsiSense::is_*`]. //! [`ScsiSense::is_*`].
//! //!
//! The actual `ioctl(SG_IO, ...)` call is impossible to mock without a //! The actual `ioctl(SG_IO, ...)` call is impossible to mock without a
//! kernel — see //! kernel, so libc shims are deliberately avoided here. These tests
//! `(internal)/docs/audits/2026-04-26-scsi-architecture-research.md`
//! for why the audit recommends against libc shims here. These tests
//! therefore pin the *contract* every backend must satisfy via a mock //! therefore pin the *contract* every backend must satisfy via a mock
//! `ScsiTransport`: //! `ScsiTransport`:
//! //!
+12 -12
View File
@@ -77,21 +77,21 @@ fn parse_url_disc_device() {
#[test] #[test]
fn parse_url_mkv() { fn parse_url_mkv() {
let u = parse_url("mkv://Dune.mkv"); let u = parse_url("mkv://Movie.mkv");
assert_eq!(u.scheme(), "mkv"); assert_eq!(u.scheme(), "mkv");
assert_eq!(u.path_str(), "Dune.mkv"); assert_eq!(u.path_str(), "Movie.mkv");
} }
#[test] #[test]
fn parse_url_network() { fn parse_url_network() {
let u = parse_url("network://10.0.0.1:9000"); let u = parse_url("network://192.0.2.10:9000");
assert_eq!(u.scheme(), "network"); assert_eq!(u.scheme(), "network");
assert_eq!(u.path_str(), "10.0.0.1:9000"); assert_eq!(u.path_str(), "192.0.2.10:9000");
} }
#[test] #[test]
fn parse_url_bare_path_rejected() { fn parse_url_bare_path_rejected() {
let u = parse_url("Dune.mkv"); let u = parse_url("Movie.mkv");
assert_eq!(u.scheme(), "unknown"); assert_eq!(u.scheme(), "unknown");
} }
@@ -104,21 +104,21 @@ fn parse_url_null() {
#[test] #[test]
fn parse_url_m2ts_with_path() { fn parse_url_m2ts_with_path() {
let u = parse_url("m2ts:///tmp/Dune.m2ts"); let u = parse_url("m2ts:///tmp/Movie.m2ts");
assert_eq!(u.scheme(), "m2ts"); assert_eq!(u.scheme(), "m2ts");
assert_eq!(u.path_str(), "/tmp/Dune.m2ts"); assert_eq!(u.path_str(), "/tmp/Movie.m2ts");
} }
#[test] #[test]
fn parse_url_m2ts_relative() { fn parse_url_m2ts_relative() {
let u = parse_url("m2ts://Dune.m2ts"); let u = parse_url("m2ts://Movie.m2ts");
assert_eq!(u.scheme(), "m2ts"); assert_eq!(u.scheme(), "m2ts");
assert_eq!(u.path_str(), "Dune.m2ts"); assert_eq!(u.path_str(), "Movie.m2ts");
} }
#[test] #[test]
fn open_input_bare_path_errors() { fn open_input_bare_path_errors() {
let result = libfreemkv::input("Dune.mkv", &libfreemkv::InputOptions::default()); let result = libfreemkv::input("Movie.mkv", &libfreemkv::InputOptions::default());
assert!(result.is_err()); assert!(result.is_err());
let msg = match result { let msg = match result {
Err(e) => e.to_string(), Err(e) => e.to_string(),
@@ -134,7 +134,7 @@ fn open_input_bare_path_errors() {
#[test] #[test]
fn open_output_bare_path_errors() { fn open_output_bare_path_errors() {
let dt = sample_disc_title(); let dt = sample_disc_title();
let result = libfreemkv::output("Dune.mkv", &dt); let result = libfreemkv::output("Movie.mkv", &dt);
assert!(result.is_err()); assert!(result.is_err());
let msg = match result { let msg = match result {
Err(e) => e.to_string(), Err(e) => e.to_string(),
@@ -195,7 +195,7 @@ fn open_output_disc_errors() {
#[test] #[test]
fn open_input_network_no_port_errors() { fn open_input_network_no_port_errors() {
let result = libfreemkv::input("network://10.0.0.1", &libfreemkv::InputOptions::default()); let result = libfreemkv::input("network://192.0.2.1", &libfreemkv::InputOptions::default());
assert!(result.is_err()); assert!(result.is_err());
let msg = match result { let msg = match result {
Err(e) => e.to_string(), Err(e) => e.to_string(),