Commit Graph
20 Commits
Author SHA1 Message Date
Matthew Jackson 8000bae177 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.
2026-06-07 22:28:29 -07:00
Matthew Jackson 2a55bab3ed tests: read_aacs_inputs Long-AD coverage; scrub title names from comments
Add a precommit fixture proving Disc::read_aacs_inputs reads a Long-AD,
multi-extent /AACS/Unit_Key_RO.inf in full — the exact input the online
key-request path depends on (no disc/deploy needed). Make
read_aacs_inputs_from_reader pub(crate) for the test.

Reword internal comments/doc examples to generic descriptions instead of
specific media titles.
2026-06-07 21:25:29 -07:00
Matthew Jackson 061f68594a 0.31.0: hardening and correctness pass across mux, codec, AACS/CSS, UDF/MPLS/CLPI, recovery, drive/SCSI, labels, and I/O
Library-wide review-and-fix pass: tightened AACS keydb/handshake/variant
handling and trailing-partial-unit policy, corrected MPLS mark offset and
added UDF allocation bounds, hardened the mux/codec framing and M2TS paths,
guarded SCSI READ CAPACITY short transfers and unified error mapping, added
overflow guards on untrusted disc input, and made prefetch shutdown
deterministic. Release profile now builds with thin LTO + single codegen unit.
2026-06-07 17:37:38 -07:00
MattJackson fd543c058b mux: rustfmt (Rust 1.86) on touched codec/meta files 2026-06-06 21:32:04 -07:00
MattJackson 2eee777b8f mux/hevc: real chroma/bit-depth in hvcC + clamp oversized param sets
codec_private() hardcoded 8-bit 4:2:0 in the hvcC fixed header, wrong for
10-bit Main 10 UHD (essentially all UHD). Parse chroma_format_idc and
bit_depth_luma/chroma_minus8 from the SPS RBSP (with emulation-prevention
removal and sub-layer profile_tier_level handling) and emit the real
values; fall back to 8-bit 4:2:0 only if the SPS can't be parsed.

Also guard the 16-bit NAL length fields: a param set larger than 65535
bytes would truncate the length while appending all bytes, mis-framing
the record. Return None instead of emitting a corrupt hvcC.
2026-06-06 21:22:44 -07:00
MattJackson dab6ea4359 mux/hevc: emit redefined param sets in-band at every keyframe
Fight Club redefines PPS id 0 mid-title; the parser froze the first PPS into
codecPrivate and stripped the rest, so the redefined segment decoded against
the wrong PPS (CABAC/cu_qp_delta desync, intact framing). Now any VPS/SPS/PPS
whose body differs from the codecPrivate copy is emitted in-band at every
occurrence, overriding the hvcC copy a player re-applies per keyframe. Proven:
Fight Club re-mux decode errors 320+ -> 0 across all corrupt regions.

Also adds aacs::unit_key_validates (1-block early-reject UK validation) and
ts_sync_count/ts_packet_total helpers.
2026-06-06 21:09:04 -07:00
MattJackson 5b702a76a7 mux: write PTS (presentation), not DTS, as the MKV block timecode
The video codec parsers (HEVC, H.264, VC-1, MPEG-2) used
pes.dts.or(pes.pts) as each frame's timestamp. MKV block timecodes
are presentation timestamps; frames are stored in decode order and
the player reorders for display by timecode. Using DTS makes the
timecode monotonic in storage order, presenting B-frames in decode
order — visible motion judder / wrong frames on playback, and
PTS-based seeking lands on the wrong frame.

The compressed video was always byte-correct (verified by NAL-level
diff against a known-good demux); this was purely a timestamp defect
affecting every B-frame title. Fix: prefer PTS (pes.pts.or(pes.dts)).
Verified on a real UHD iso->mkv: emitted PTS now reorders for
B-frames identically to a reference muxer.

Update the two tests that asserted the old DTS-preferred behavior and
add an HEVC regression test pinning PTS as the block timecode.
2026-06-05 20:01:13 -07:00
MattJackson 6be5198886 libfreemkv: v1.0 hardening — codec/EBML/TS robustness + DTS parser fixes
Audit-driven fixes (rounds 1–3):
- hevc: correct hvcC profile/level SPS offsets (HEVC has a 2-byte NAL header)
- mkv: map all DTS variants to the registered A_DTS codec id; force a new
  cluster before the i16 cluster-relative timestamp can overflow
- ebml/mkvstream: bound untrusted EBML sizes (no multi-GB allocs); reject
  uint>8 (was an OOB panic) and non-{0,4,8} float widths (were a desync)
- ts: skip PES-header bytes that span a TS packet boundary; add the PMT
  section_len/prog_info_len bounds the PAT parser already had
- ac3: preserve a 0x0B77 syncword split across a PES boundary; cap buffer
- dts: validate each next-core boundary by decoded core size (a 0x7FFE8001
  pattern inside XLL payload no longer false-splits/drops the lossless
  extension); reject sub-minimum core frames; fix forced-emit PTS base
- lpcm: DVD program-stream PCM no longer double-strips the BD LPCM header
- vc1/mpeg2: do not emit a parameter-set-only PES as a standalone frame
- pgs/truehd: cap the pending reassembly buffer (parity with ac3/dts)
- aacs: ts_syncs_intact uses the exact packet count
- prefetched: capacity-guard the recycled-buffer set_len
- Cargo.toml: exclude project docs from the published crate

Convergence: a third independent audit pass found no remaining material
(CRITICAL/HIGH/MEDIUM) issues. Full precommit (fmt + clippy -D + tests,
Rust 1.86) green.
2026-06-05 16:23:39 -07:00
MattJackson 1b95193517 v0.25.2: DTS-HD codec ID + PGS BlockDuration
- MkvTrack::audio emits A_DTS/MA, A_DTS/HR, A_DTS per the DTS family
  instead of mislabelling everything as A_DTS. Plex transcoder and
  strict hardware decoders reject DTS-HD MA payload under a plain
  A_DTS track.
- PgsParser is now stateful: pairs display PCS with the following
  empty PCS to compute a duration. Frame::duration_ns + PesFrame::duration_ns
  carry it through; MkvMuxer::write_frame gains a final Option<u64>
  parameter that emits BlockGroup + BlockDuration when set. Fixes
  subtitle bitmaps lingering past their intended end-time.
2026-05-19 16:11:54 -07:00
MattJackson c51b3181f2 mux: pipelined PES highway — read+decrypt → demux → parse on 3 threads
Introduces the freemkv mux throughput highway: a three-stage thread
pipeline that replaces the inline single-thread read path for any
file-backed source (ISO and m2ts file URLs both route through it).

  Thread A: read + decrypt  (PrefetchedSectorSource / BytePrefetcher)
  Thread B: M2TS demux      (DemuxThread)
  Thread C: codec parse     (PipelinedPesStream, on caller thread)

Each handoff uses a bounded crossbeam channel with a recycled buffer
pool — no allocations or memcpys in the steady-state hot loop.

Component map:

* io/byte_prefetcher.rs (new) — std::io::Read producer thread with
  recycled Vec<u8> pool. Pairs with PrefetchedSectorSource (sector
  side) so demux_thread::spawn_zero_copy can wire either upstream.
* sector/prefetched.rs — recycled buffer pool added; into_channels()
  peels off the rx/recycle_tx/shell triple for zero-copy demux.
* mux/demux_thread.rs (new) — owns the TsDemuxer/PsDemuxer, runs
  feed() on its thread, ships Vec<PesPacket> batches.
* mux/pipelined_stream.rs (new) — the read-side Stream impl. Pulls
  packets from the demux thread and runs codec parse on the caller.
* mux/resolve.rs — build_iso_pipeline (public) / build_m2ts_pipeline
  (private) assemble the three stages; iso:// and m2ts:// both
  return PipelinedPesStream.
* mux/m2ts.rs — collapsed to a write-only sink (Mode::Read deleted;
  the read direction lives on the highway now).
* mux/codec/h264.rs — find_start_code uses memchr SIMD memmem::find.
* mux/codec/hevc.rs — tightened frame_data initial capacity.
* mux/ts.rs — boundary-packet handling avoids the per-batch 16 MiB
  remainder copy; PesAssembler starts at 16 KiB to dodge the 64-page
  first-touch fault tax that the previous 256 KiB pre-alloc paid on
  every PES boundary.
* mux/disc.rs — gains DiscStream::new_pipeline + read_pipeline as
  the legacy autorip ingress (drive + multipass paths still need
  on_event / skip_errors before they migrate to the highway).
* io/file_sector_source/* — per-OS prefetch() syscall hook
  (Linux readahead, macOS F_RDADVISE, Windows/other no-op).
* decrypt.rs — FREEMKV_DECRYPT_THREADS renamed to FREEMKV_THREADS;
  pool sized to all cores by default.

Measured on rip1 testbed (Civil War UHD, 62 GiB ISO → null://):

  60 → 322 MB/s warm cache (old new_pipeline path)
  60 → 660 MB/s warm cache (highway path, this commit)
  60 → 126 MB/s sustained disk-bound

The IsoSectorReader baseline reader was deleted in favour of
FileSectorSource so the freemkv CLI and autorip exercise the same
read path.
2026-05-19 13:35:32 -07:00
MattJackson 3dea679dac style: cargo fmt 2026-04-24 12:23:42 -07:00
MattJackson e0f40583c4 Fix cargo fmt formatting 2026-04-15 22:32:53 +00:00
MattJackson 8bbf630d82 Fix all PES pipeline audit findings (20 issues)
Critical:
- C1: PES serialize validates track < 256 and data < 4GB
- C2: PES deserialize caps frame size at 256MB (OOM protection)
- C3: TsMuxer stuffing uses static buffer, no per-packet alloc
- C4: PES length uses unbounded (0x0000) for audio >65535 bytes
- C6: TsDemuxer validates AF length <= 183

Warning:
- W1: parse_timestamp validates marker bits, returns Option
- W2: PES header data_start clamped to data.len()
- W3: TsMuxer PTS conversion uses saturating_mul, rejects negative
- W4: AC3/DTS replace debug_assert with runtime bounds check
- W6: MKV block_vint handles 3-4 byte VINTs
- W7: meta.rs to_title() uses unwrap_or fallbacks instead of panic
- W8: MKV reader skips frames for non-existent tracks
- W9: DVD PTS uses higher-precision conversion (1e9/90000)
- FMKV read_header caps JSON at 10MB
- PAT section_len underflow guard

Suggestion:
- S2: TsMuxer uses static STUFF_FF buffer
- S3: HEVC parser single-pass NAL scan (was duplicated)
- S4: TsDemuxer caps remainder at one packet
- S5: PTS 90kHz→ns uses round-to-nearest
2026-04-15 16:22:28 +00:00
MattJackson 75dfd06a02 Fix all v4 audit findings (22 items)
HIGH: ISO writer multi-extent for >4GB, end-to-end MKV mux test
MEDIUM: AACS cvalue bounds, UV offset, macOS discovery, VC-1 resolution
  from sequence header, HEVC profile flags from SPS, ISO CRC + reserve AVDP
LOW: PS AC3 sub-header, CSS crack first-match break, TrackUID unique,
  AC3 no-sync empty return, --all for iso://, --min warning, dead code removed

320 tests, all passing.
2026-04-11 20:29:00 +00:00
MattJackson ffa0eaba4d cargo fmt + clippy --fix: 104 format violations fixed, 8 clippy auto-fixes 2026-04-11 19:10:20 +00:00
MattJackson 515f2f6bc2 100% codec coverage + disc/ and aacs/ module refactors
Codec coverage (DVD + BD + UHD):
- E-AC-3 (Dolby Digital Plus): bsid detection, frame size calc — 8 tests
- DTS-HD MA/HR: extension substream (0x64582025) detection — 8 tests
- LPCM: BD header skip, raw PCM extraction — 6 tests
- DVD VobSub subtitles: passthrough parser — 5 tests
- Dolby Vision: verified RPU NAL type 62 preserved in HEVC — 1 test

Module refactors:
- disc.rs → disc/mod.rs + bluray.rs + dvd.rs + encrypt.rs
- aacs/mod.rs (1661 lines) → mod.rs (21) + keydb.rs + keys.rs + decrypt.rs
- All public APIs preserved, all tests pass

270 tests total, 0 failures.
2026-04-11 17:22:18 +00:00
MattJackson ff5547363b Audit fixes + DVD support foundation (IFO, PS demux, MPEG-2, CSS crack)
Audit fixes (14 critical, 22 warnings):
- UDF: bounds checks on all ICB/FID parsing from disc data
- SCSI Linux: saturating_sub on residual, CDB length guard, buffer size guard
- SCSI macOS: SCSITaskStatus u32 (was u8 — stack corruption)
- AACS: EC mod_inv returns infinity instead of panic, key reduced mod n
- AACS: do_handshake tries all host certs (was returning on first failure)
- H.264: bounds check on SPS < 4 bytes
- ContentReader: error on missing unit key (was zero-fill)
- KEYDB: flat redirect loop (was recursive), 100MB response limit, Windows HOME fallback
- ISO writer: AVDP extent order, partition length, allocation cap
- Network: removed TCP_NODELAY on bulk stream
- MKV: guard on u64::MAX seek
- disc.rs: saturating_sub on extent offset, simplified dead region code
- cargo fmt (610 violations), cargo clippy --fix (55 auto-fixes)

DVD support (new files):
- src/ifo.rs — IFO parser (VIDEO_TS.IFO, VTS_XX_0.IFO, PGC chains, cells, streams) — 13 tests
- src/mux/ps.rs — MPEG-2 Program Stream demuxer (pack headers, PES, private stream 1) — 12 tests
- src/mux/codec/mpeg2.rs — MPEG-2 video parser (sequence headers, I-frame detection) — 15 tests
- src/css/crack.rs — split-attack algorithm (LFSR cipher needs verification — test ignored)

226 tests total (was 186), 1 ignored (CSS crack needs cipher verification).
2026-04-11 16:52:22 +00:00
MattJackson 995525d3ff Add 113 tests, update CI to checkout@v5, add FEATURES.md
Test suite: 64 → 177 tests
- MPLS parser: 6 tests (synthetic binary, streams, errors)
- CLPI parser: 6 tests (EP map, PTS/SPN math, errors)
- H.264: 12 tests (NAL parsing, SPS/PPS, keyframes)
- HEVC: 13 tests (VPS/SPS/PPS, IRAP range, codec private)
- AC3: 12 tests (syncword, frame extraction)
- VC1: 15 tests (BITMAPINFOHEADER, start codes)
- DTS: 5, TrueHD: 4, PGS: 4 tests
- EBML: 6 tests (size/ID/string/float roundtrips)
- UDF: 10 tests (MockSectorReader, filesystem parsing, error paths)
- Disc: 8 tests (scan_image, DiscTitle helpers)
- Streams: 5 new (meta roundtrip, MkvStream)
- NullStream: 4, StdioStream: 2, IsoSectorReader: 2

CI: actions/checkout@v4 → v5 (all workflows)
FEATURES.md: created for v0.7.1
2026-04-11 16:02:49 +00:00
MattJackson 8c2f3898b8 Add IOStream trait and stream-based I/O architecture
Introduce IOStream trait for uniform read/write across disc, file,
network, and null streams. Rename Title→DiscTitle, add stream URL
resolver, split old stream.rs into focused modules (m2ts, mkvstream,
network, disc, null, resolve, meta).
2026-04-10 19:13:53 -07:00
MattJackson 48b212c9d2 Add MKV muxer and event system
- Add event.rs: structured event system for progress reporting
- Add mux/: MKV muxer pipeline (EBML writer, TS demuxer, stream assembly)
  - Codec parsers: H.264, HEVC, AC-3, DTS, TrueHD, PGS, VC-1
  - Lookahead buffer for codec private data extraction
  - Direct m2ts-to-MKV streaming without intermediate files
2026-04-10 08:19:40 -07:00