Commit Graph
213 Commits
Author SHA1 Message Date
matthew 9b65cb8fa1 v0.11.18: DiscStream halt flag — Stop works in dense bad-sector regions
DiscStream::fill_extents loops internally while the demuxer waits for
enough clean data to emit a PES frame. In a dense bad zone that loop
can run for minutes without returning to the outer read() call, so
the caller's Stop signal never gets serviced until a frame is finally
emitted — which may be very far away.

Add DiscStream::set_halt(Arc<AtomicBool>) — typically wired to
Drive::halt_flag() for unified Stop across drive recovery phases and
stream sector processing. fill_extents checks the flag at the top of
every retry iteration; raising it returns Err(Error::Halted) within
one SCSI round-trip.

No behavior change for callers that don't call set_halt. Unblocks the
architectural fix for the "Stop doesn't stop" bug observed on a
damaged UHD disc.
2026-04-24 07:41:13 -07:00
matthew 4a8913be22 v0.11.17: adaptive batch sizer — no per-sector descent
Replace read_with_binary_search + 3×5s light recovery with an adaptive
sizer that shrinks on failure (halve, 3-aligned ≥6) and probes back up
after 100 MiB (51,200 sectors) of clean reads. Descent cost is paid
once per bad region, not once per bad sector.

Emit BatchSizeChanged { new_size, reason } on shrink and probe-up.
Remove BinarySearch event — no longer produced.

Side fix: scsi/macos.rs one-liner for manual_c_str_literals clippy
lint that surfaced on a newer toolchain.
2026-04-23 20:24:40 -07:00
Matt Jackson 63b68a01a5 v0.11.16: API cleanup — one method per action 2026-04-21 19:17:50 +00:00
Matt Jackson d4818ad88e v0.11.15: lint cleanup — fmt + clippy clean 2026-04-21 18:52:55 +00:00
Matt Jackson c0a96dfa97 v0.11.14: audit fixes — trailing sectors, verify stop, SCSI sense, O_CLOEXEC
Fix trailing sectors dropped at extent boundaries when sector_count % 3 != 0.
Add verify_title stop support via progress callback returning bool.
Add O_CLOEXEC on all SCSI fd opens to prevent leak to child processes.
Fix SCSI sense descriptor format detection (0x72/0x73 vs 0x70/0x71).
2026-04-21 18:40:04 +00:00
Matt Jackson 61edb63a6f Async SG_IO: enforceable timeouts via write/poll/read
Replace blocking ioctl(SG_IO) with the sg driver's async interface.
Commands are submitted via write(), waited on via poll() with a hard
wall-clock timeout, and completed via read(). If poll() times out,
the fd is abandoned and a fresh one opened — the kernel can no longer
hold us hostage during USB error recovery.

- write() submits command, returns immediately
- poll() enforces exact timeout (EINTR-safe with deadline tracking)
- read() retrieves result + copies data to caller's buffer
- On timeout: old fd closed in background thread, new fd opened
- No SG_FLAG_DIRECT_IO — kernel buffers for safe timeout abandonment
- Store device_path for fd reopen after timeout
- Drop guards fd=-1 (abandoned fd)
2026-04-21 17:57:58 +00:00
Matt Jackson a529a8fcb6 Clean API: merge read() and read_fast() into read(recovery: bool) 2026-04-21 03:27:17 +00:00
Matt Jackson 93d619bb5c Fix: initial batch read uses fast read, not full recovery 2026-04-21 02:02:13 +00:00
Matt Jackson 08be717dc5 Drive halt flag, sector events, binary search light recovery (3x5s) 2026-04-21 00:18:46 +00:00
Matt Jackson 92c1729b69 v0.11.11: binary search error recovery in fill_extents 2026-04-20 21:51:46 +00:00
Matt Jackson e9f0c323b7 DiscStream skip_errors, read_sectors_recover(recovery) API 2026-04-20 03:38:32 +00:00
Matt Jackson c78e83e916 Clean API: read_sectors_recover(recovery: bool) replaces read_sectors_fast 2026-04-20 01:12:59 +00:00
Matt Jackson 8a256cc620 v0.11.9: fast verify reads — 5s timeout, no recovery loop 2026-04-20 01:01:44 +00:00
Matt Jackson 1208d67df0 Add verify module: sector-by-sector disc health check 2026-04-20 00:01:02 +00:00
Matt Jackson ec0688c0e0 Improve label generator: proper video labels, all resolutions, no false Atmos claims 2026-04-19 17:50:17 +00:00
Matt Jackson 547871fbe5 Move label generation to labels system — fill_defaults() for all stream types 2026-04-19 17:23:50 +00:00
Matt Jackson 2f4caf8fe1 Generate audio track labels when disc doesn't provide them 2026-04-19 16:34:35 +00:00
Matt Jackson 48f862709b v0.11.7: TrueHD parser rewrite — 12-bit length, AC-3 skip, cross-PES buffering 2026-04-19 01:36:56 +00:00
Matt Jackson fcac5b742e Fix TrueHD: strip BD-TS access unit header before muxing to MKV 2026-04-18 19:26:14 +00:00
Matt Jackson 66e474c338 v0.11.5: MKV container fixes — timestamps, frame rate, HDR, chapters, disposition 2026-04-18 16:29:21 +00:00
Matt Jackson 23990de768 Fix truncated rips on dual-layer discs, propagate read errors
Use UDF file_extents() to read actual allocation descriptors instead
of assuming m2ts files are contiguous from file_start_lba. Dual-layer
UHD discs split large files across 70+ extents (~1 GB each) — the old
code created one extent from packet count which only covered the first
chunk, causing silent truncation at ~37%.

Also changed fill_extents() to return io::Result<bool> so read errors
propagate instead of being silently treated as EOF.
2026-04-18 02:08:34 +00:00
Matt Jackson 2e5a7af244 Add freemkv keydb path to KEYDB search, fail on encrypted disc without keys 2026-04-17 22:10:56 +00:00
Matt Jackson a3de3d2ed9 v0.10.8: prefetch all metadata file sectors — scan 2min to 18s on USB 2026-04-17 19:51:32 +00:00
Matt Jackson ab431e022c Remove debug timing from scan pipeline 2026-04-17 18:26:04 +00:00
Matt Jackson c6b08381ce Buffer UDF sector reads — eliminates scan hang on USB drives 2026-04-17 18:24:29 +00:00
Matt Jackson bc62cb35f4 DiscStream::new() replaces open_drive/open_iso/from_reader
A stream is a stream. DiscStream::new() takes reader + title + keys +
batch + format — same pattern as every other stream constructor.

Deleted: open_drive(), open_iso(), from_reader() — these were helper
functions that chained multiple operations. Library provides primitives,
callers decide the sequence.

Removed disc:// case from input() — callers use Drive::open() +
Disc::scan() + DiscStream::new() directly for disc sources.
2026-04-17 15:32:48 +00:00
Matt Jackson 2674612d87 Fix drive discovery in Docker — remove sysfs check 2026-04-16 22:05:25 +00:00
MattJackson 521ff7b528 Buffer audio parsers across PES boundaries
- DTS: buffer with core sync detection + frame size from header
- TrueHD: buffer with unit length field parsing
- Same pattern as AC3 fix: incomplete frames held for next PES
- When PES boundaries align (normal case), buffering is a no-op
2026-04-16 19:02:23 +00:00
MattJackson 8d3962ec3f Fix AC3 parser: buffer across PES boundaries, proper frame sizing
- Add state to Ac3Parser (was stateless, split frames at PES boundaries)
- Buffer leftover bytes from incomplete frames for next PES packet
- Calculate exact AC3 frame size from fscod/frmsizecod table
- Calculate EAC3 frame size from frmsiz field
- Skip invalid frame sizes (0 or >8192)
- Eliminates all AC3 decode errors on BD and UHD output
2026-04-16 18:49:30 +00:00
MattJackson 58566c8910 Fix cargo fmt formatting 2026-04-16 17:51:45 +00:00
MattJackson 2bb7376880 Expand CSS Stevenson crack patterns, scan 50K sectors
- Add padding stream (0xBE) with 0xFF payload patterns
- Add video/audio PES with multiple flag/header combinations
- Add navigation pack system header pattern
- Scan up to 50K consecutive scrambled sectors (was 500 sampled)
2026-04-16 15:15:17 +00:00
MattJackson 3557379991 Fix MPEG-2 PS demuxer: codec parsers, sequence header extraction, descramble
- Route DVD PS packets through codec parsers (was bypassing them)
- Extract MPEG-2 sequence header with quantizer matrices for MKV codec_private
- Calculate exact sequence header size from intra/non-intra matrix flags
- Capture sequence extension (B5) from subsequent PES packets
- Revert TAB1 permutation in descramble (XOR-only is correct)
- Fix CSS roundtrip tests for new descramble behavior
2026-04-16 15:06:35 +00:00
MattJackson a1a30bf3c0 Route DVD PS demuxer through codec parsers, fix CSS test expectations
- DVD PS path now calls parser.parse() like BD-TS path does
- MPEG-2 sequence headers extracted for codec_private
- Keyframe detection from parser instead of always-true
- Fix CSS roundtrip tests: descramble uses TAB1 permutation, not pure XOR
2026-04-16 04:49:46 +00:00
MattJackson 65c6c8cfe0 Fix CSS decryption: full key hierarchy, correct cipher tables
- Implement complete CSS key chain: bus auth → disc key → title key
- Add 31 player keys for disc key decryption
- Read disc key via READ DVD STRUCTURE format 0x02
- Read title key via REPORT KEY format 0x04
- Fix CryptKey round 1: use original scratch for term, not modified tmp1
- Fix decrypt_key: use TAB5 for LFSR1 output, TAB4 for LFSR0^invert
- Fix descramble_sector: use TAB5 for LFSR1, TAB4 for LFSR0 (no invert),
  and apply TAB1 permutation to ciphertext before XOR
- Fix title key bus XOR: forward order (bus_key[i]), not reversed
- Two-session auth: disc key and title key need separate AGID sessions
- Fix crack_key: scan across extents for scrambled sectors
- Fix TsDemuxer: dynamic PID table size for DVD PIDs
- Set max read speed after scan for DVD riplock removal
2026-04-16 04:42:42 +00:00
MattJackson 3422cbcd22 v0.10.3: CSS drive authentication for DVD ripping 2026-04-16 00:01:48 +00:00
MattJackson 32adeb4af2 Fix cargo fmt formatting 2026-04-15 22:32:53 +00:00
MattJackson c11d2bea31 Fix batch overflow in Disc::copy(), DVD PGC parsing, demuxer flush at EOF
- Disc::copy() hardcoded batch=64 sectors, exceeding BU40N's 60-sector
  hw limit. Now accepts batch_sectors param, defaults to 60.
- IFO PGC: playback time at offset 0x04 not 0x02, cell time at cell+4
- DiscStream: set demuxer from content_format (TS for BD, PS for DVD)
- Flush TS/PS demuxers at EOF to avoid losing last PES frame
- M2tsStream: flush demuxer at EOF
- StdioStream: FMKV metadata header for roundtrip compatibility
2026-04-15 22:26:07 +00:00
MattJackson ed43ced710 v0.10.1: Streams are PES, Disc::copy() for sector dumps, zero English
Architecture:
- One stream per format, bidirectional PES (read/write on same type)
- IsoStream merged into DiscStream (one type, any SectorReader)
- Disc::copy() for disc→ISO raw sector dump
- IOStream trait deleted, all byte-level Read/Write removed
- ContentReader/OpenDisc/open_title/open_input/open_output deleted
- CountingStream wrapper for progress tracking

Error codes:
- All io::Error English strings replaced with Error enum variants
- From<Error> for io::Error conversion
- Unused variants removed, new stream/mux variants added

Deleted: mkvout.rs, pesout.rs, isowriter.rs, mkv-muxer-plan.md
Updated: all docs, README stream table, CHANGELOG

238 tests, 0 clippy warnings.
2026-04-15 19:46:01 +00:00
MattJackson e6d5fb72a1 v0.10.0: PES pipeline audit, codec_privates on DiscTitle, streams not files 2026-04-15 17:22:52 +00:00
MattJackson c40c718113 Remove Seek/File dependencies from stream readers
Streams are streams — they take impl Read, not Read+Seek or File.

- MkvStream::open takes impl Read (was Read+Seek)
  - EBML element skipping uses skip_bytes() instead of seek(Current)
  - Byte position tracking uses remaining-bytes counter, not stream_position()
  - Removed file_size from open (progress is CLI concern)
- M2tsStream::open takes impl Read (was Read+Seek)
  - Buffers first 1MB for FMKV header / PMT scan
  - Uses chain reader (buffered head + rest) for sequential reading
  - Duration unknown without seeking (0.0) — CLI can set from metadata
- Removed ReadSeek trait (no longer needed)
- WriteSeek kept (MKV muxer container format requires seeking internally)
2026-04-15 17:05:50 +00:00
MattJackson 75066744d1 Move codec_privates onto DiscTitle, eliminate duplicate methods
Design fix: codec_privates are now a field on DiscTitle, not a separate
parameter passed through the pipeline. This eliminates the root cause of
the network codec_private bug (forgot to pass the separate param).

API changes:
- output() takes (url, &DiscTitle) — no separate codec_privates param
- MkvOutputStream::create, M2tsOutputStream::create, NetworkOutputStream::connect
  all read codec_privates from title.codec_privates
- M2tsMeta::from_title() takes only &DiscTitle — reads privates from title
- Deleted from_title_with_privates (was the wrong-name duplicate)
- Merged read_header + read_header_from_stream into one read_header(impl Read)
- Deleted finish(self) from TsMuxer, keep only finish(&mut self)

Rule: ONE public method per action. No _with_X, _from_Y, _ref variants.
2026-04-15 16:52:06 +00:00
MattJackson 5287dd65ce 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 55d1aff2b3 Fix M2TS/MKV roundtrip: codec_private in FMKV header, Annex B conversion
Bug 1: M2TS roundtrip dropped frames — TsMuxer converts length-prefixed
NALs to Annex B, prepends VPS/SPS/PPS from HEVCDecoderConfigurationRecord.

Bug 2: MKV remux lost codec_private — MkvStream.codec_private() now returns
data from EBML header.

FMKV header carries codec_private (base64) per video stream for lossless
M2TS roundtrip.
2026-04-15 16:09:34 +00:00
MattJackson e1c8e6cea6 Fix M2TS/Network output: write FMKV header for roundtrip compatibility
- M2tsOutputStream writes FMKV metadata header before TS data
- NetworkOutputStream sends FMKV header on connect
- Enables M2TS→MKV and network roundtrip to work correctly
2026-04-15 04:50:19 +00:00
MattJackson 8542163a7e NetworkStream PES-only: remove old IOStream/Read/Write interface
- Remove IOStream, Read, Write impls from NetworkStream
- PES write sends FMKV header before first frame (protocol fix)
- PES finish sends TCP shutdown for clean EOF
- Update tests to use PES roundtrip instead of byte-level
- Remove NetworkStream from open_input/open_output (use input/output)
2026-04-15 04:36:47 +00:00
MattJackson 6341e9c430 Fix audit findings: SCSI constants, sg_io_hdr assert, handshake cap, sector overflow check
- Replace magic SCSI opcodes with named constants (S2)
- Add compile-time sg_io_hdr size assertion — 88 bytes on 64-bit (W2)
- Cap handshake cert attempts at 16 (W8)
- Validate IsoSectorReader/FileSectorReader against u32 overflow for >8TB (S8)
- encrypt.rs: limit host cert loop iterations
2026-04-15 04:29:45 +00:00
MattJackson 1a9956cea0 Fix all clippy warnings: dead code, match patterns, type complexity, docs
- Remove unused pes_buf field from M2tsStream and unused TS_PACKET/BD_TS_PACKET constants
- Replace match-with-single-pattern with if let (3 instances in drive/mod.rs)
- Replace match-can-be-? with ? operator for scsi::open call
- Add type aliases PesSetup and MkvHeaderResult to reduce type complexity
- Collapse identical if/else branches in tsmux.rs build_pes_header
- Use RangeInclusive::contains instead of manual range checks
- Make WriteSeek trait pub (was pub(crate) but leaked through pub fn)
- Remove empty line after doc comment in disc.rs
- Fix doc list item indentation in scsi/linux.rs (12 instances)
2026-04-15 04:09:56 +00:00
MattJackson 8ae05bc388 All streams complete — DVD PS demux, network/stdio PES, MKV input
- DiscStream: BD (TsDemuxer) or DVD (PsDemuxer) auto-detected
- NetworkStream: Stream impl with PES serialize/deserialize
- StdioStream: Stream impl with PES serialize/deserialize
- MkvStream: Stream read returns PesFrame from EBML blocks
- M2tsStream: Stream read via TsDemuxReader
- PesFrame: serialize/deserialize for wire format
- TsDemuxReader: shared BD-TS demux helper
- All inputs and outputs support PES
2026-04-15 04:03:56 +00:00
MattJackson 394ce226f9 Complete PES pipeline — all streams, clean API
- Unified Stream trait: read() and write() on one type
- PesFrame serialize/deserialize for wire format
- TsDemuxReader: shared BD-TS demux for any Read source
- MkvStream: PES read from MKV (EBML → PesFrame)
- M2tsStream: PES read via TsDemuxReader
- Network/Stdio output: PES serialization directly (no BD-TS wrap)
- Network/Stdio input: deferred (needs PES deserialization protocol)
- TsMuxer for M2TS output from PES frames
- input() and output() functions return Box<dyn Stream>
2026-04-15 03:56:15 +00:00
MattJackson 29dcbf55e0 M2TS implements Stream (read PES frames via TsDemux)
M2tsStream::read() → TsDemux → CodecParser → PesFrame.
M2TS input now works through PES pipeline.
MKV input deferred (needs EBML → PES extraction).
2026-04-15 03:41:03 +00:00