libfreemkv 0.31.2: comprehensive spec-grounded test suite (~950 tests)

Test-hardening release, no runtime changes. Adds spec-grounded unit tests
across the silent-corruption surfaces — UDF/MPLS/CLPI/IFO parsing, BD/DVD
title + extent assembly, AACS/CSS key handling, TS/PS demux + codec parsers,
MKV/EBML container output, the mux pipeline, sector prefetch + decrypt
decorator, drive/SCSI sense decoding, label extraction, and core I/O. Each
test is grounded in the format spec or real on-disc behavior and verified to
fail under a targeted source mutation. No behavior changed.
This commit is contained in:
Matthew Jackson
2026-06-07 22:28:29 -07:00
parent 2a55bab3ed
commit 8000bae177
85 changed files with 22998 additions and 1 deletions
+165
View File
@@ -284,4 +284,169 @@ mod tests {
let result = NetworkStream::connect("127.0.0.1");
assert!(result.is_err());
}
/// Spawn an accepting reader and return (its address, join handle that
/// yields all frames read after the FMKV header).
fn spawn_reader() -> (
std::net::SocketAddr,
std::thread::JoinHandle<(DiscTitle, Vec<crate::pes::PesFrame>)>,
) {
use crate::pes;
// Bind BEFORE spawning so the port is live when connect() runs — no
// channel handshake needed (the listener already owns the socket).
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let handle = std::thread::spawn(move || {
let mut ns = NetworkStream::accept_from(listener).unwrap();
let info = pes::Stream::info(&ns).clone();
let mut frames = Vec::new();
while let Ok(Some(f)) = pes::Stream::read(&mut ns) {
frames.push(f);
}
(info, frames)
});
(addr, handle)
}
/// write() on a listen()/accept-constructed (READ) stream must return
/// StreamReadOnly — the read side has no writer. (Returning Ok would let
/// a caller silently lose frames written into a receive-only socket.)
#[test]
fn write_on_read_side_is_read_only_error() {
use crate::pes;
let (addr, handle) = spawn_reader();
// Sender connects, sends header (zero frames), finishes — so the
// reader's accept_from() returns. We test the reader's write guard.
let dt = sample_title();
let mut writer = NetworkStream::connect(&addr.to_string()).unwrap().meta(&dt);
pes::Stream::finish(&mut writer).unwrap();
let (_info, _frames) = handle.join().unwrap();
// Now build a fresh read-side stream and confirm its write() errors.
// (Re-bind, accept once, then immediately try to write to it.)
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr2 = listener.local_addr().unwrap();
let h = std::thread::spawn(move || {
let mut ns = NetworkStream::accept_from(listener).unwrap();
let frame = pes::PesFrame {
track: 0,
pts: 0,
keyframe: true,
data: vec![0u8; 8],
duration_ns: None,
};
// Read side: writing must be a typed StreamReadOnly error.
let err = pes::Stream::write(&mut ns, &frame).expect_err("read side write must error");
err.kind()
});
// Drive the accept: connect + send header so accept_from completes.
let mut w2 = NetworkStream::connect(&addr2.to_string())
.unwrap()
.meta(&dt);
pes::Stream::finish(&mut w2).unwrap();
let kind = h.join().unwrap();
// E_STREAM_READ_ONLY (9000) maps to Unsupported.
assert_eq!(kind, io::ErrorKind::Unsupported);
}
/// read() on a connect()-constructed (WRITE) stream must return
/// StreamWriteOnly — never Ok(None), which a caller would read as a
/// legitimately empty stream.
#[test]
fn read_on_write_side_is_write_only_error() {
use crate::pes;
let (addr, handle) = spawn_reader();
let dt = sample_title();
let mut writer = NetworkStream::connect(&addr.to_string()).unwrap().meta(&dt);
let err = pes::Stream::read(&mut writer).expect_err("write side read must error");
// E_STREAM_WRITE_ONLY (9001) maps to Unsupported.
assert_eq!(err.kind(), io::ErrorKind::Unsupported);
pes::Stream::finish(&mut writer).unwrap();
let _ = handle.join().unwrap();
}
/// The FMKV header must be written exactly once, before the first frame,
/// even across many frames. The receiver must therefore reconstruct the
/// title exactly once and read every frame after it — a header re-emitted
/// between frames would desync PesFrame::deserialize and corrupt frame N.
#[test]
fn header_written_once_then_all_frames_roundtrip() {
use crate::pes;
let (addr, handle) = spawn_reader();
let dt = sample_title();
let mut writer = NetworkStream::connect(&addr.to_string()).unwrap().meta(&dt);
for i in 0..5u8 {
let frame = pes::PesFrame {
track: (i % 2) as usize,
pts: i as i64 * 90_000,
keyframe: i == 0,
data: vec![i; 100 + i as usize],
duration_ns: None,
};
pes::Stream::write(&mut writer, &frame).unwrap();
}
pes::Stream::finish(&mut writer).unwrap();
let (info, frames) = handle.join().unwrap();
// Title parsed once and intact.
assert_eq!(info.streams.len(), 2);
// Every frame survived in order with exact payloads — no desync from
// a duplicated header.
assert_eq!(frames.len(), 5);
for (i, f) in frames.iter().enumerate() {
assert_eq!(f.pts, i as i64 * 90_000, "frame {i} pts");
assert_eq!(f.data.len(), 100 + i, "frame {i} payload length");
assert!(
f.data.iter().all(|&b| b == i as u8),
"frame {i} payload bytes"
);
}
}
/// The receiver's title comes strictly from the SENDER's FMKV header:
/// the sender's meta() title is what accept_from() reconstructs, proving
/// the metadata flows sender→receiver over the header (not from the
/// receiver's empty default). Distinct sender title confirms the source.
#[test]
fn receiver_title_comes_from_sender_header() {
use crate::pes;
let (addr, handle) = spawn_reader();
let mut dt = sample_title();
dt.playlist = "SenderControlled".into();
dt.playlist_id = 42;
let mut writer = NetworkStream::connect(&addr.to_string()).unwrap().meta(&dt);
pes::Stream::finish(&mut writer).unwrap();
let (info, _frames) = handle.join().unwrap();
// The receiver default title is empty (playlist ""); it must have
// been replaced by the sender's header-carried title.
assert_eq!(info.playlist, "SenderControlled");
assert_eq!(
info.streams.len(),
2,
"stream descriptors round-trip via header"
);
}
/// accept_from() must reject a connection whose first bytes are NOT the
/// FMKV magic — there is no metadata to drive muxing, so it surfaces
/// NoMetadata rather than proceeding with an empty/garbage title.
#[test]
fn accept_from_rejects_stream_without_fmkv_header() {
use std::io::Write as _;
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let handle = std::thread::spawn(move || {
// Raw non-FMKV bytes (not starting with 'F') then close.
let mut s = TcpStream::connect(addr).unwrap();
s.write_all(&[0x47u8; 64]).unwrap(); // TS sync bytes, no FMKV magic
s.shutdown(std::net::Shutdown::Both).unwrap();
});
let err = match NetworkStream::accept_from(listener) {
Ok(_) => panic!("missing FMKV header must error, not silently accept"),
Err(e) => e,
};
// E_NO_METADATA (9008) maps to InvalidInput.
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
handle.join().unwrap();
}
}