Commit Graph
100 Commits
Author SHA1 Message Date
Matthew Jackson 1baf9ba061 halt: Stop-aware waits across channels, drive, io, flush and the macOS shim
Waits are halt-aware on a stall timer; every CDB carries the op token;
prefetchers and pipeline joins honour Stop; flush is durable with
measured progress; the macOS shim open is cancellable. Also: CPI is
cleared on every decrypted unit, FMTS phase verification, the HD DVD
declared CPS count, AES work hoisted out of AACS hot paths, and the
last production unsafe removed.
2026-09-28 03:57:56 -07:00
Matthew Jackson 3617ccf733 disc, audio: record unlocatable bus-encrypted files; resync without losing good frames
A bus-encrypted stream file whose File Entry cannot be located is
re-read, then recorded rather than failing the scan; MKV staging ranges
and E6022 for a partial image. Audio framing chains headers while
resyncing, reports every lost slot, keeps good frames beside corrupt
ones and places locks by PTS, with ADTS and MPEG property fuzzing.
2026-09-28 01:11:12 -07:00
Matthew Jackson cbd3db9d0e spec, test_util: specification quote registry, spec guards, test-util fixtures
libfreemkv::spec holds the verbatim specification quotes the code
cites, with guards tying each governed site to its quote. A test-util
feature exposes shared fixtures to downstream tests. M2TS output
derives DTS for reordered video and rounds to whole 90 kHz ticks.
2026-09-28 00:50:09 -07:00
Matthew Jackson 3207856c17 dvd, mux: route DVD audio and subtitles by the PGC tables; MP2 multichannel
DVD subtitles and audio route by PGC SPST_CTL and AST_CTL instead of
stream ordinal, and MPEG audio is fed from its PES id. The DVD MPEG-2
extension stream rides the IR with its channel count verified by CRC.
Audio framing stops false syncs poisoning a track; PGS and DVD subtitle
parser fixes; E9073 and E7034 declared.
2026-09-28 00:31:27 -07:00
Matthew Jackson 18982a7c0e disc, aacs: key files before the handshake; one whole-disc reader
Scan reads UDF and the AACS files before the drive handshake and records
a missing DVD /AACS key file instead of failing. Decryption needs an
explicit AACS unit base and refuses off-grid units; an all-broken
host-cert list is its own error (E7033). Every whole-disc and image to
ISO copy goes through one shared reader that refuses an unkeyable file
up front (E7032). HD DVD XPL tick and timecode fixes; CI action bumps.
2026-09-27 20:17:01 -07:00
Matthew Jackson 503a80884e mux: sink and reader audit fixes; DTS in MP4; halt-aware network receive
Fixes from the mkv, mp4, network and ts sink audit; track timing and
DiscardPadding ride the FMKV wire. MP4 writes DTS per ETSI TS 102 114
(ddts rates and timescales). The MKV PCM depth probe is bounded and
evidence-based. Network receives use keepalive and wait on readiness,
so a Stop or a dead peer ends them.
2026-09-27 12:40:50 -07:00
Matthew Jackson d6396005b8 scsi, lpcm: MMC-6 disc presence and tray handling; LPCM decoded to plain PCM
Disc presence is tri-state per MMC-6, an empty drive fails fast, a
cleaning cartridge reads as no disc, and the tray is unlocked only when
we locked it. BD and DVD LPCM decode to plain PCM, with whole-frame DVD
blocks and the BD layout carried through M2TS.
2026-09-27 11:54:34 -07:00
Matthew Jackson 8c707efbe1 nav, labels: navigation command semantics, BD-J feature detection, CLPI audit
DVD and HDMV navigation commands follow the reference semantics
(SetGPRMMD, set-links, saturating ADD/MUL, unmasked shifts). Label
detection bounds the BD-J menu walk, opens large jars in place and
tightens Paramount, Deluxe and BD-J feature evidence. The CLPI audit
keys streams by clip and PID; channel layouts keep the LFE split and
TrueHD counts for unnamed layouts.
2026-09-27 11:45:48 -07:00
Matthew Jackson 4c5098ce75 keys, disc, io: bus-encryption gates, halt-aware key paths, writeback error latching
Read-time key fetches are validated and source, halt and drive faults
surface. Every BDMV/STREAM file is bus-decrypted per the BEE flag and
unit copy-permission bits; a missing or unparseable cert fails safe.
Extract treats a drive Stop as a halt, not a hole. io latches writeback
errors, fixes the read eviction window and never waits on writeback
during seeks.
2026-09-26 19:53:08 -07:00
Matthew Jackson aa85906a87 udf, mux: post-1.7.7 fixes for UDF chains, AAC headers and codec framing
UDF validates allocation-descriptor chains, maps fragmented metadata
files and tolerates broken subdirectory chains. The muxer holds headers
until every AAC track has its AudioSpecificConfig and backpatches a late
CodecPrivate. HEVC PPS tables are bounded, DTS EXSS aligned and MPEG
free-format frames capped. A no-media drive is released before the next
candidate opens; CI builds freemkv's app as a consumer.
2026-09-26 19:29:26 -07:00
Matthew Jackson ee25d50c11 restore freemkv-unlock path dep for local dev (post-v1.7.7) 2026-09-26 21:16:58 +00:00
Matthew Jackson c8e67f16b4 v1.7.7: bump version (freemkv-unlock git-pinned for the tag) 2026-09-26 21:16:54 +00:00
Matthew Jackson efc9bd705d Link the website FVI specification and simplify PGS comments 2026-09-26 11:58:27 -07:00
Matthew Jackson 68d27be8f8 Clarify send deadline Rustdoc contract 2026-09-26 11:50:01 -07:00
Matthew Jackson a42241eb15 Prepare 1.7.7 with concise comments and shared CI policy 2026-09-26 11:46:32 -07:00
Matthew Jackson 5520890550 Update Scorecard action to use supported GHCR image 2026-09-26 11:23:38 -07:00
Matthew Jackson d0c9df91ef Fix PGS clear events and add FFmpeg QA interoperability gate 2026-09-26 10:47:57 -07:00
Matthew Jackson 1363ba8510 restore freemkv-unlock path dep for local dev (post-v1.7.6) 2026-09-26 04:05:41 +00:00
Matthew Jackson 3f156f1f03 v1.7.6: bump version (freemkv-unlock git-pinned for the tag) 2026-09-26 04:05:38 +00:00
Matthew Jackson df43373f41 docs: neutral wording in changelog [skip-media] 2026-09-25 17:10:05 -07:00
Matthew Jackson a797c544f1 docs: say test corpus, not hoard [skip-media] 2026-09-25 16:48:38 -07:00
Matthew Jackson 584cc6030b tests(macos): hardware drive checks (INQUIRY, reopen, UHD AACS2 state, rescan stability, probe/scan agreement) + non-hardware WritebackFile/preallocate tests 2026-09-25 13:44:22 -07:00
Matthew Jackson 7257559153 chore: absorb qa into dev for lockstep promote [skip-media] 2026-09-25 12:25:45 -07:00
Matthew Jackson 8949b81dfc macos(drive): discover empty-tray drives via IOKit registry-id selectors
Empty-tray optical drives couldn't be opened because the macOS backend
resolved drives by media-child `BSD Name`, which doesn't exist without a
disc loaded. Result: `list_drives()` returned nothing for a drive with no
media, and any attempt to `open("disc://")` on that state failed.

Fix: enumerate `IOBDServices` directly. With media present, the returned
selector is the familiar `/dev/diskN` path (unchanged). With no media,
the selector is an opaque `ioreg:<registry-id>` string that resolves to
the same IOBDServices entry — Disk Arbitration and unmount are skipped
because there's no media node.

- `src/drive/macos.rs`, `src/drive/mod.rs`, `src/scsi/mod.rs`: threads
  the selector through the transport so empty-tray drives are openable
  without punning it as a filesystem path.
- `src/scsi/macos.rs`, `src/scsi/macos_shim.c`: the C shim now accepts
  both `/dev/diskN` and `ioreg:<id>` in `shim_open_exclusive`, and
  `shim_list_drives` reports the correct selector for each drive.
- `tests/macos_drive_tests.rs` (new): covers empty-tray discovery.
- `docs/drive-access.md`, `docs/scsi-macos.md`: document the two-path
  selector model.

[skip-media]
2026-09-25 12:03:02 -07:00
Matthew Jackson 7bcfdeda32 restore freemkv-unlock path dep for local dev (post-v1.7.6) 2026-09-25 05:32:00 +00:00
Matthew Jackson 89be65a3d4 v1.7.6: bump version (freemkv-unlock git-pinned for the tag) 2026-09-25 05:31:57 +00:00
Matthew Jackson f21ab37920 chore(qa): gate 1.7.6 promote — media matrix not needed [skip-media]
The 1.7.6 unified release ships no rip-path change (Linux desktop shell,
notifications, menu_layout refactor, XDG paths). Real-media QA on EC2
adds ~2h + real money to the release cycle to prove code that never
enters the rip pipeline. This empty commit puts `[skip-media]` in qa's
HEAD so the media matrix in qa.yml auto-skips per the documented bypass;
the full-platform builds/tests still run.
2026-09-24 21:37:26 -07:00
Matthew Jackson 0c28e569a8 chore(qa-sync): absorb qa's post-1.7.5 release commits into dev
Same fix as the other 7 repos: qa held two release-orchestrate commits
(v1.7.5 bump + post-release freemkv-unlock path restore) that never
propagated back to dev, so promote's fast-forward gate refused. Ours-merge
records qa as an ancestor without applying its content; dev has already
moved past 1.7.5 to 1.7.6.
2026-09-24 21:27:58 -07:00
Matthew Jackson 0d9565eac9 ci(consumers): install libgtk-4-dev so the freemkv consumer build resolves
The freemkv 1.7.6 Linux target compiles a GTK4 + libadwaita desktop shell,
so this job's downstream `cargo test`/`cargo check` on freemkv hits
`gtk4-sys`/`glib-sys` pkg-config probes that fail on a stock ubuntu
runner. Adds the apt-get step alongside the existing sibling checkout,
mirroring the fix in freemkv's own ci.yml `cli-linux` job.

Kept local (not in the shared reusable checkout action) — only `freemkv`
links against GTK; adding the deps to every downstream consumer would tax
the six that don't.
2026-09-24 20:37:35 -07:00
Matthew Jackson b52e3c8292 chore(release): bump to 1.7.6, follow the CI-required release-notes shape
Bumps Cargo.toml + Cargo.lock to 1.7.6. Also fixes a CI-shape defect the
previous release left behind: dev's CHANGELOG still said
`## [1.7.5] — UNRELEASED` after 1.7.5 shipped (release-orchestrate dated
main but did not push a fresh UNRELEASED heading back to dev). Corrects
that here so the changelog job accepts 1.7.6 on the way to qa:

  ## [1.7.6] — UNRELEASED
    - Version aligned; unified release driven by freemkv 1.7.6.
  ## [1.7.5] — 2026-09-23
    - Version aligned (or actual content, per the shipped main entry).

The driver for 1.7.6 is the freemkv Linux desktop shell (GTK4 +
libadwaita) + rip-finished desktop notification (issue #56).
2026-09-24 20:23:08 -07:00
Matthew Jackson d4510392f6 restore freemkv-unlock path dep for local dev (post-v1.7.5) 2026-09-23 04:40:54 +00:00
Matthew Jackson 49992a5c2d v1.7.5: bump version (freemkv-unlock git-pinned for the tag) 2026-09-23 04:40:51 +00:00
Matthew Jackson c1caac3a7e docs: 1.7.5 changelog entry for the unified release [skip-media]
Version aligned to 1.7.5. No functional change to this crate — the release
is driven by freemkv-unlock mirroring the freemkv-firmware 0.9.0 ABI, where
the drive's separate Ake (0x06) and Bus (0x07) levers are retired into a
single Encryption (0x06) lever.
2026-09-22 21:04:54 -07:00
Matthew Jackson b69002694f ci: add OpenSSF Scorecard workflow (publish_results on default branch) 2026-09-21 16:39:26 -07:00
Matthew Jackson 11c869f8e9 restore freemkv-unlock path dep for local dev (post-v1.7.4) 2026-09-21 23:03:36 +00:00
Matthew Jackson 8ce40edba3 v1.7.4: bump version (freemkv-unlock git-pinned for the tag) 2026-09-21 23:03:33 +00:00
Matthew Jackson bccc8eb9a7 docs: mark 1.7.4 changelog heading UNRELEASED (release workflow dates it) 2026-09-21 15:07:08 -07:00
Matthew Jackson 0edbdfbb21 docs: 1.7.4 changelog + consumer release notes 2026-09-21 11:47:20 -07:00
Matthew Jackson 2499f1600d docs: OpenSSF Best Practices badge (project 14745, 198%) + REUSE.toml/badge — parity with freemkv 2026-09-21 09:22:49 -07:00
Matthew Jackson bb3b1920a9 docs: add OpenSSF Scorecard + REUSE + MSRV badges and a 'what this is not' honesty section 2026-09-21 09:11:52 -07:00
Matthew Jackson 551173fa46 docs/ci: drop stale local-precommit/release.sh references — CI is the only gate 2026-09-21 09:11:34 -07:00
Matthew Jackson 26a9cb9e62 ci(qa): use central checkout-siblings composite + concurrency
Convert qa.yml to the central pattern already applied to ci.yml (86d66d3):
- add top-level concurrency (cancel-in-progress) group
- replace per-job freemkv-unlock checkout (ref qa) with the
  freemkv/.github checkout-siblings@v1 composite (checkout: freemkv-unlock)
  in release-tests, cross-lint and windows-build
- standardize actions/checkout to @v5

No heredoc patching existed in any job, so no patch inputs. The composite
resolves siblings 3-way, so qa runs now pin siblings at qa automatically.
2026-09-21 08:46:53 -07:00
Matthew Jackson 86d66d3113 ci: use central checkout-siblings composite + concurrency; fixes main->main sibling ref (was main->dev)
Replaces the 8x-repeated sibling-checkout block with freemkv/.github checkout-siblings@v1.
Adds concurrency cancel-in-progress. All jobs (incl. consumers running dependent suites) preserved.
2026-09-21 08:37:48 -07:00
Matthew Jackson 6d20c854c0 comment: trim two 5-line inline blocks to 3 (surfaced by the more-accurate central comment-guard lexer) 2026-09-21 08:35:27 -07:00
Matthew Jackson 24f29d83f0 ci: guards → central reusable workflows (comment/leak/identity/doc-links)
Thin callers to freemkv/.github@v1; delete the now-duplicated local ci/ scripts.
Adds concurrency cancel-in-progress and drops the push+PR double-fire (fork PRs
still run). doc-links now resolves toolchain from rust-version (was hardcoded 1.98).
2026-09-21 08:33:46 -07:00
Matthew Jackson 471a441799 doc: backtick the NAL [len][body] notation so rustdoc doesn't parse it as an intra-doc link (fixes doc-links CI) 2026-09-21 08:05:21 -07:00
Matthew Jackson aeb6bf5cc1 disc: scope the #45 lowest-id tiebreak behind stream-richness (recover Dolby Vision / subtitle on BlacKkKlansman + WickedForGood) 2026-09-20 21:50:21 -07:00
Matthew Jackson 0062d79d2c bdnav/bdjo: cite the BDJO format, not libbluray's GPL source (scan-secrets REF-IMPL-CITATION) 2026-09-20 20:45:55 -07:00
Matthew Jackson b0e297bd41 Merge codeaudit-1.7.4: chapter-span + hevc-guard + winner-hint tests, doc-drift, hoist MIN_FEATURE_SECS 2026-09-20 20:45:24 -07:00
Matthew Jackson 0cd967a5ac fix: close code-audit test gaps + doc drift (1.7.4 delta, #45/#52/#46)
Test gaps (guarded code was untested):
- disc: cover is_complete_feature_presentation's CHAPTER_SPAN_MIN_FRAC (0.5)
  boundary — clustered marks (span < 0.5*dur) are not a feature; marks at the
  0.5 threshold are. Adds bd_title_chaptered_span helper (issue #45).
- hevc: exercise the length-prefix self-check WIRING in HevcParser::parse
  end-to-end — a desynced access unit is dropped, a well-formed one emitted.
  Adds a test-only FORCE_FRAMING_DESYNC seam (frame_data is length-prefixed by
  construction, so no real input desyncs it) (issue #52).
- labels: cover resolve_feature_hint's winner_hint early-return — a non-empty
  winner_hint wins over the Tier-2 bdj_feature menu-walk result (issue #46).

Doc drift:
- keysource.md: resolve_unit_keys + UnitKeyResolution (matched/miss_path/
  matched_entry/store_entries de-conflation).
- aacs-trace.md: MatchedEntry, KeyStep.matched_entry/store_entries, and the
  KeyNode::NoDerivableKey node.
- hevc.md: add the "length-prefix self-check (issue #52)" section the hevc.rs
  comment points to.

Craft:
- labels: hoist the duplicated MIN_FEATURE_SECS (60) from fox.rs and
  paramount.rs to a single shared const in labels/mod.rs.
2026-09-20 20:44:28 -07:00
Matthew Jackson d7b6aca6e8 fix(#45): add bdj_feature.rs (unstaged in 0109db9 menu-walk commit) 2026-09-20 20:09:35 -07:00
Matthew Jackson 3a41e0eec7 Merge #45 menu-walk: BD-J feature resolution (BDJO AMT + jar/manifest + Tier-2 locator) above the chapter failsafe 2026-09-20 20:08:39 -07:00
Matthew Jackson 0109db984f fix(#45): BD-J menu-walk — authoritative feature-resolution layer
Adds a generic BD-J feature-playlist resolver that runs when no framework
parser surfaced a manifest hint, plus a refinement to Sony/Fox feature
selection. The hint now rides an independent pass so it survives even when
the winning parser (or no parser) produced any labels.

- labels/mod.rs: resolve_feature_hint() independent hint pass wired into
  apply(); register mod bdj_feature.
- labels/paramount.rs (+ fox.rs mirror): refine feature selection —
  exact name="Feature" → /feature/i by longest duration → most-audio by
  duration; hard-reject sub-minute playlists (kills the _Start_Angle 2s
  decoy). Fixes Sony SM3 UHD. feature_hint_from_xml() exposed for Tier-1.
- labels/bdj_feature.rs (NEW): Tier-1 jar-space resource sweep
  (dcx.xml/playlists.xml/*.properties) reusing fox/paramount hints; Tier-2
  autostart-Xlet locator scan (BDJO AMT → autostart jar → NNNNN.mpls /
  PLAYLIST:N String constants → intersect real PLAYLIST set →
  longest-dominant multi-audio wins; integer-only never emits).
- bdnav/bdjo.rs (NEW): minimal bounds-checked /BDMV/BDJO/*.bdjo
  Application Management Table reader (autostart initial_class + jar ids),
  panic-free like bdnav/index.rs.
- labels/jar.rs: try_each_resource() helper for the Tier-1 sweep.

No changes to disc/mod.rs selection or bdnav/vm.rs.
2026-09-20 20:07:45 -07:00
Matthew Jackson 841328af0f Merge #45 failsafe: chapter-gated composite bumper limb + lowest-id tiebreak 2026-09-20 19:36:33 -07:00
Matthew Jackson a35f336c52 Merge #52: PGS BlockDuration + HEVC NAL guard 2026-09-20 19:36:33 -07:00
Matthew Jackson 00f590f634 mux: guarantee a BlockDuration on every subtitle block (#52)
Fix (B) — the reproducible bug behind ffmpeg "Timestamps are unset in a
packet for stream N" on PGS subtitle tracks (worst on the sparse forced
track). Subtitle tracks carry no DefaultDuration, so a frame emitted with
duration_ns: None became a bare SimpleBlock with nothing to bound the cue.

- codec/pgs.rs: synthesize a fallback dwell (DEFAULT_PGS_DURATION_NS, 5 s)
  for every emission that was duration_ns: None — the EOS trailing set and
  the no-PTS clear/display, truncated, and lone-pass-through arms. emit_pending
  keeps its real computed span but clamps a pathologically large one
  (missing intermediate PCS) past MAX_PGS_DURATION_NS back to the fallback.
- mkv.rs: track_is_subtitle (track_type == 17), mirroring track_is_video. In
  write_frame_at a subtitle frame with no duration now substitutes a minimum
  fallback (MIN_SUBTITLE_BLOCK_DURATION_NS, 1 s) so it is emitted as a
  BlockGroup WITH BlockDuration, never a bare SimpleBlock (defense in depth).

Fix (A) — defensive HEVC guard (the "Invalid NAL unit size" error is not
reproducible in our clean disc->MKV path; this makes our output structurally
incapable of originating it):

- codec/hevc.rs: length_prefix_tiles() self-consistency check — the
  [u32-BE len][body] records must EXACTLY tile the frame; on mismatch the
  desynced access unit is dropped (logging src position) rather than emitted.
- LENGTH_PREFIX_SIZE constant + regression test that hvcC byte-21
  lengthSizeMinusOne+1 equals the prefix width push_length_prefixed writes.

Tests: pgs.rs None-arm duration assertions updated to the fallback + a
clamp/no-clamp pair; mkv.rs subtitle-BlockGroup and two-track (dense+sparse)
"every block has a BlockDuration" cases; hevc.rs tiling accept/reject,
byte-21 regression, and a positive parsed-AU-tiles-exactly test.
2026-09-20 19:33:25 -07:00
Matthew Jackson 827eaf0452 disc: gate composite bumper limb on a complete chapter table (#45)
A BD-J disc (Alita) has no authoritative static playlist chain, so main-
feature selection is a structural failsafe. Its chaptered feature 00800 is
a SUPERSET of an un-chaptered body subset 00703 that is 0.97 of its size,
so the size-only seamless bumper limb fired and demoted the real feature,
ripping the body instead.

Gate ONLY the bumper limb on is_complete_feature_presentation(q): the
wrapped/subset title must carry a real chapter table (>= MIN_FEATURE_CHAPTERS
marks spanning >= CHAPTER_SPAN_MIN_FRAC of its runtime). The concat limb
(much-shorter cut) is untouched. This is the ABSOLUTE-threshold predicate;
a one-line swap to the RELATIVE form (p.chapters.len() <= q.chapters.len())
is documented at the call site and in the predicate doc, pending a hoard
sweep of SM3's real chapter counts.

Also add lowest playlist_id as the final canonical_title_order tie-break so
equal-size seamless siblings (00800-00808) pick 00800 deterministically.

Fixtures re-based on the real chapter discriminator (not a size coincidence)
and new tests cover both topologies (Alita superset-feature, SM3 subset-
feature) plus the un-chaptered-subset keep-superset case.
2026-09-20 19:32:39 -07:00
Matthew Jackson f4ea6ea129 aacs/keysource: de-conflate "no entry" vs matched-but-underivable (#46)
The keydb lookup returned an empty Vec for two very different outcomes:
a true hash miss, and a MATCHED disc that derived no key (e.g. an MK-only
entry with no VID). Both rendered as `keydb > no entry`, misleading
reporters whose disc IS present in the keydb.

Add KeySource::resolve_unit_keys -> UnitKeyResolution { keys, matched,
miss_path, matched_entry, store_entries }; the trait default delegates to
get_unit_keys (matched=false) so every existing source is unchanged.
resolve_and_apply_traced now renders MatchedDisc + NoVid/NoDerivableKey
for a matched-but-no-key disc and reserves NoEntry for a genuine miss.

New KeyNode::NoDerivableKey, a MatchedEntry shape (booleans + lengths, no
key material), and KeyStep.{matched_entry, store_entries} carry the
diagnostics to the app-layer renderer. Tests cover both verdict paths.
2026-09-20 19:16:06 -07:00
Matthew Jackson b47bccce7b libfreemkv 1.7.4: codeaudit fixes (v1.7.0..HEAD sweep)
Bus content-range de-bus:
- F1 disc/mod.rs: always install content ranges (extracted wire_bus_removal);
  empty ranges => Some([]) => de-bus NOTHING (fail-safe), not None => de-bus
  everything — closes silent clear-sector corruption on the AACS cert route.
- F4 aacs/content.rs, F5 decrypt.rs: content gate is now a partition_point
  binary search (O(log n)/sector) over the sorted, merged-disjoint ranges,
  replacing the per-sector O(ranges) linear scan on the de-bus hot path.

- F2 scsi/linux.rs: RECOVERY_THREADS cap uses atomic reserve-then-check
  (fetch_add first, fall back on overshoot) — removes the TOCTOU that let
  concurrent multi-drive rips exceed MAX_RECOVERY_THREADS.
- F3 pes.rs: PesFrame::deserialize reads each chunk straight into data's tail;
  drops the scratch Vec + double-copy, keeps the incremental-growth DoS guard.
- F7 disc/mod.rs: six inline rank_titles thresholds -> named consts.

Tests: bus fail-safe + scan wiring (both routes), per-sector gate straddle,
read_directory type-3 continuation/AED-skip/cycle-guard, strengthened
without_bus_removal no-op guard. F6 (pub decrypt_bus) left by-design.
2026-09-19 16:19:48 -07:00
Matthew Jackson 13b93f803b libfreemkv: fix broken intra-doc links to BusStage::Passthrough in drive/mod.rs (doc-links gate) 2026-09-19 14:25:13 -07:00
Matthew Jackson 1894fa1a49 libfreemkv 1.7.4: single drive-owned AACS bus-removal point
Relocate AACS bus decryption to one spot at the bottom of the sector chain:
the Drive's SectorSource impl applies decrypt_bus_in_content (gated to
encrypted-content ranges), with the stage decided once from the unlock/
handshake result (BusStage::from_read_data_key). Remove read_data_key from
DecryptKeys::Aacs and AacsState and the two downstream decrypt_bus call sites;
every reader (key sampler, mux, unit-key validation) now sees already-de-bussed
content and never reasons about bus encryption. Add BusRemovalSectorSource
stream + tests; keep the AacsBusKeyUnavailable fail-loud gate.
2026-09-19 13:59:52 -07:00
Matthew Jackson 5696175520 restore freemkv-unlock path dep for local dev (post-v1.7.3) 2026-09-19 05:31:13 +00:00
Matthew Jackson e99cb95bd4 v1.7.3: bump version (freemkv-unlock git-pinned for the tag) 2026-09-19 05:31:10 +00:00
Matthew Jackson 1794f5c406 merge: record v1.7.2 release into dev (-s ours; keep path deps + dev's next-cycle work) 2026-09-18 21:39:15 -07:00
Matthew Jackson 9379507c15 docs: backtick item[10]/item[0..5] in mpls module doc so rustdoc doesn't read them as intra-doc links (doc-links CI) 2026-09-18 21:30:25 -07:00
Matthew Jackson 8457efb5da changelog: 1.7.3 — multi-angle MPLS STN fix (#45) + read_structure_files for --share 2026-09-18 21:11:59 -07:00
Matthew Jackson dbc135bee0 disc: add read_structure_files() to capture BDMV/DVD index metadata for --share diagnostics 2026-09-18 21:08:28 -07:00
Matthew Jackson 48fb9b2cf6 mpls: locate STN table past the multi-angle angle block (issue #45)
The PlayItem STN (stream-number) table offset was hard-coded to 32, which
is only correct for a single-angle PlayItem. A multi-angle PlayItem inserts
number_of_angles(1) + flags(1) + (number_of_angles-1) angle references (10
bytes each) right after still_time, so its STN begins at 32 + 2 +
(number_of_angles-1)*10. The fixed offset lands inside that angle block and
misreads the stream counts as garbage (e.g. n_video=51 on Spider-Man 3 UHD
00245/00246.mpls, number_of_angles=2 => real offset 44), dropping every
stream including the primary video. That makes DiscTitle::has_video() return
false, which drops a multi-angle/seamless-branch feature from the disc's nav
candidate set and loses it in main-title selection.

Compute the STN offset from is_multi_angle (bit 4 of item[10]) + number_of
_angles. Add a regression test building a 2-angle PlayItem and asserting the
video stream is found past the angle block.

Validated on a 92-disc real UHD/BD corpus (offline scan): 0 main-feature
pick changes vs baseline, 3 multi-angle titles (Spider-Man 3 244/245/246)
recover has_video. Adds examples/corpus_titledump.rs as the regression tool.
2026-09-18 16:38:45 -07:00
Matthew Jackson 013f27eede fix(disc): SAFETY — spare seamless-branch feature from wrapper-composite demotion (#45)
The fallback composite gate in rank_titles flagged title P as a "play-all
wrapper" whenever some other title Q's clip set was a proper subset of P's,
Q had video, and Q.size >= 0.5 * P.size. main_feature_order then ranks
standalone-before-composite, demoting P below the decoy Q.

On a seamless-branch feature this inverts the pick: the real 00800 (2h03,
4 clips = body + intro/credits/seamless segments) is a SUPERSET of the
single-clip body decoy 00700 (1h50), and 700 >= 50% of 800's size, so 800
was mis-flagged composite and lost to the shorter 700 — the wrong title
gets ripped.

Add a duration-first wrapper-shape gate: P is only a composite when the
subset Q is EITHER a much shorter cut (Q.duration < 0.85 * P.duration, a
concat/play-all padding Q's runtime) OR nearly size-equal (Q.size >= 0.90 *
P.size, a bumper/outro wrapper). A near-full-length but markedly smaller
body subset (Q runs ~0.89 of P's duration yet holds far fewer bytes,
because P's branch segments add bytes not runtime) is a real seamless
branch, so P is kept. Uses per-title duration_secs/size_bytes, stays
capacity-independent and feature-gate-safe.

Preserves the real play-all avoidance: the 00245 bumper-wrapper decoy
(Q ~0.96 of both duration and size) stays demoted via the size limb, and
a concat play-all (subset < half the runtime) via the duration limb.

Add regression tests: seamless_branch_feature_not_demoted_issue_45 and
true_play_all_concat_wrapper_still_demoted. All existing selection tests
pass.
2026-09-18 13:34:50 -07:00
Matthew Jackson 9f0e10a524 restore freemkv-unlock path dep for local dev (post-v1.7.2) 2026-09-18 18:37:24 +00:00
Matthew Jackson b65124728c v1.7.2: bump version (freemkv-unlock git-pinned for the tag) 2026-09-18 18:37:21 +00:00
Matthew Jackson 10d073286c changelog: 1.7.2 — record the SET data-phase unlock fix + READ CAPACITY/empty-ISO hardening 2026-09-18 06:55:00 -07:00
Matthew Jackson 89fd1a6e4d merge: record v1.7.1 main into dev (dev already at 1.7.1; keep path deps) 2026-09-17 23:08:09 -07:00
Matthew Jackson 5c9e2fefe5 comment-guard: trim over-cap comment blocks to caps (comments only, no code change)
Bring every flagged comment block within its cap (inline // <=3 lines,
/// item-doc prose <=8): tighten wording, or convert an over-long inline
block that documents the item below it into a /// doc, preserving all
technical facts. No code, string literals, or logic changed.
2026-09-17 22:47:32 -07:00
Matthew Jackson 041ac5876f test: make case-collision test host-FS-independent
Probe dir_is_case_insensitive against the real target volume by creating
the dir first. Previously the test probed a not-yet-created dir, which
returns a conservative true; that disagreed with extract_tree's own
post-mkdir probe on a case-sensitive host (Linux CI), panicking the test.
Both branches (collision on insensitive, clean coexist on sensitive) still
verified.
2026-09-17 22:37:17 -07:00
Matthew Jackson e0875b630c docs: fix broken intra-doc links
- decrypt.rs: three refs to the #[cfg(test)]-only decrypt_sectors_mapped
  wrapper can't resolve in a non-test doc build; de-link to inline code
  (keeps the name, no broken link).
- Fix DecryptingSectorSource::with_content_ranges link to its full
  crate::sector path so it resolves.
2026-09-17 22:37:17 -07:00
Matthew Jackson d5902ff9f9 deps: sha1 0.10→0.11 (collapse RustCrypto digest/block-buffer/crypto-common dup); drop unused rand 2026-09-17 22:18:07 -07:00
Matthew Jackson 01cd85f514 changelog: open 1.7.2 — UNRELEASED (unified release; firmware ABI v2 in freemkv-unlock) 2026-09-17 14:35:17 -07:00
Matthew Jackson 6de61484a3 docs: point the freemkv-unlock path-dep note at release-orchestrate.yml (release.sh is gone) 2026-09-17 14:24:49 -07:00
Matthew Jackson 764519de43 aacs/disc: test coverage for VID key-derivation + handshake route selection; diag: print unknown mkb_version 2026-09-15 20:37:18 -07:00
Matthew Jackson 2fb7d78b29 libfreemkv: retry READ CAPACITY for ISO sizing; hard-error if unavailable
READ CAPACITY is the only authoritative whole-disc size for an image
(the UDF partition is a subset — measured 288 sectors / ~576 KB short on
a real BD, which would truncate the backup anchor). Its field failures
are sporadic (5/5 reliable on the target drive), so retry it a few times
rather than substituting a short value. On persistent failure the shared
scan/identify path stays lenient (capacity 0; MKV reads by extent) and
image_read_sectors() returns EmptyImage so an ISO fails cleanly instead
of writing a truncated or empty file.
2026-09-15 16:45:48 -07:00
Matthew Jackson 18024ea5f5 scsi/macos: fix io_service_t leak, honor bsd_name in fallback, fix FFI doc
shim_list_drives: reorder the drain loop condition so IOIteratorNext is
not called on the exit iteration, releasing every obtained io_service_t.

shim_open_exclusive: the fallback matcher ignored the requested bsd_name
and could open a different drive; iterate and accept only the candidate
whose IOMedia BSD name matches, else fail. Correct the misleading da_hold
TOCTOU comment.
2026-09-15 14:40:54 -07:00
Matthew Jackson 2287bb4449 labels/pes/disc: overflow + robustness fixes
class_reader: checked pc + size add (guards 32-bit overflow) and remove
the dead BadInstruction variant.

criterion: dedup duplicate stream numbers instead of emitting a number
twice. deluxe: continue past a bad studio attribute instead of aborting
the whole parse. bdmt: treat mixed-content XML (embedded markup) as XML
so it is dropped, without over-rejecting a bare '<' in prose.

pes: grow the frame buffer incrementally instead of an eager
MAX_FRAME_SIZE allocation. extract: give the case-probe a unique random
suffix so concurrent probes cannot collide (TOCTOU-safe).
2026-09-15 14:40:47 -07:00
Matthew Jackson 013cc3ce8d net/error/hex: NAT64 SSRF arm, Dir Display arms, reject leading +
network: add the NAT64 (64:ff9b::/96) arm to the IP classifier -
extract the embedded IPv4 from the last 32 bits and re-check it, matching
the keysources canonical copy, so a NAT64-wrapped private/special
address is blocked like the bare IPv4.

error: add Display arms for DirInsufficientSpace (E9027), DirNameCollision
(E9028), DirWriteFailed (E9029) so their fields render (codes-only, like
the sibling Dir variants).

hex: reject a leading + in the hex int parser.
2026-09-15 14:40:40 -07:00
Matthew Jackson ab2416d07a aacs: propagate MKB walk errors, add bounds/truncation guards, fix FMTS carry
inf: read_mkb_from_drive used .is_ok() after the first pack, silently
truncating the MKB on a mid-walk transport error; propagate the error
instead. Add checked offset guards (94/117) and reject a truncated title
table (161) like the key list already does.

segment: the FMTS key-range carry assumed 2048-aligned segment starts;
use the floor-difference of the actual LBAs so a non-2048-aligned
segment carries correctly.

Annotate css::keyless::recover_title_key as a test-only helper and
correct the clpi_audit dedup-by-PID doc.
2026-09-15 14:40:33 -07:00
Matthew Jackson b735fb3730 mux/codec: PTS carry, buffer cap, EOS core drop, PPS-by-id, dead code
lpcm: carry last PTS + DTS fallback (add last_pts_ns) and propagate
pes.discontinuity instead of hardcoding false, matching adts/flac/
mpegaudio.

pesbuf: add an internal MAX_BUFFERED_BYTES cap (defense-in-depth like
reorder.rs's MAX_GOP) so a misbehaving caller can't grow it unbounded.

dts: drop a truncated trailing core at end-of-stream instead of emitting
it, while keeping genuine trailing extensions.

hevc: resolve num_extra_slice_header_bits from the PPS the slice
references (slice_pic_parameter_set_id), not the last-active PPS.

crc: remove the dead, misnamed crc8_atm (impl was CRC-8/SMBUS, no
callers). Add the missing drop-gate mismatch test; fix the startcode
read_bits contract doc and the h264 SEI comment.
2026-09-15 14:40:26 -07:00
Matthew Jackson f1b7a73b93 mux: harden MKV child loops, read real mp4 sample rate, fix pre-video PTS origin
mkvstream: add the consumed > remaining => MkvSourceInvalid guard (as
BLOCK_GROUP already had) to the INFO/TRACKS/TrackEntry/Video/Audio child
loops, and route the DURATION/SAMPLING_FREQUENCY float reads through the
checked_size cap instead of truncating cs as usize.

mp4/read: audio sample_rate was taken from the mdhd timescale, ignoring
the AudioSampleEntry 16.16 samplerate at the stsd body; read the real
field and fall back to timescale only when it is 0. Drop the unreachable
MAX_TRACKS break (the box scan is already capped) for a debug_assert.

tsmux + m2ts_mux: PTS origin was seeded on video only, so audio
preceding the first video (or an audio-only stream) rebased onto its own
PTS and lost A/V offset; seed the origin from the first frame of any
kind.
2026-09-15 14:40:18 -07:00
Matthew Jackson fbe91895c4 mux: fix filtered-export drop gate, log skipped key-map, restore json fields
demux_sink: dropped_total() folded drops across ALL tracks while
frames_mapped now counts only persisted frames, so a filtered
(sub://audio://) multi-clip export could false-trip SeamPlanDroppedMost /
SinkWroteNothing. Compare drops and mapped frames over the same
persisted-track set via TimelineContinuity::dropped_for; update the
frames_mapped doc to the persisted-only semantics.

resolve: resolve_mux_key_map(...).ok() swallowed the error silently
(TrueHD channel correction skipped with no trace); log it like the
sibling reopen() fix before skipping.

meta_sink: TitleProfile::from_title dropped json:// stream fields
(pid, color/aspect/dimensions/interlaced/mvc, audio secondary/
sample_rate/purpose, subtitle pid/descriptive_service); restore them at
the json document level.
2026-09-15 14:40:09 -07:00
Matthew Jackson 679894e5ea aacs/disc/drive: fix bus-key gate credit, shift guard, tray-unlock log
disc/encrypt: on the disc-keyed cert route, drive_unlocked was derived
from is_drive_unlocker(matched), which is provably always false -
run_bus only ever dispatches the disc unlockers (AACS/DVD), never a
firmware/drive unlocker. Hardcode false with an honest comment; the
genuine drive-unlock case (drive_unlocked, no read_data_key) is the
OEM-VID short-circuit and the bus-key gate already credits it. Gate the
now test-only is_drive_unlocker behind cfg(test) and add a regression
test through resolve_vid_only.

aacs/derive: resolve_dk_node looped 0..u_mask_shift shifting 1u32 << b,
which panics/wraps when the disc/keydb-controlled u_mask_shift is >= 32.
Cap the search at the 32 real bit positions.

drive: unlock_tray dropped its SCSI result with let _; warn! on failure
so a stuck medium-removal PREVENT lock is visible. Fix the stale
probe_disc doc (now a no-op; calibration moved into the unlocker).
2026-09-15 14:39:53 -07:00
Matthew Jackson b7914c5995 decrypt: honor with_content_ranges in the mapped AACS read path
DecryptingSectorSource::with_content_ranges was a silent no-op: the
proactive AACS key_map path early-returned before consulting the content
extents, and decrypt_span ignored its _content argument. A whole-disc
reader (sweep/patch) that installed both a key map and content ranges
therefore decrypted/verified clear UDF/BDMV-nav units, and its
misaligned filesystem reads tripped the AACS unit-alignment gate.

Thread the content extents through decrypt_sectors_mapped_in_content ->
decrypt_span -> apply_aacs_map, skipping any unit whose LBA falls outside
every content range (never decrypted, verified, or counted as loss), and
gate the alignment check on span_touches_content so out-of-content
filesystem reads pass through untouched.

Also drop the redundant is_multiple_of clause in the prefetched
unit-tail guard (remaining > 0 is already guaranteed).
2026-09-15 14:39:31 -07:00
Matthew Jackson 747aeba4a4 error: drop premature E7031 KeyServiceConfig variant
Reverts the cross-repo E7031 addition: it forced enumerating the code across
freemkv test_support + freemkv-i18n's 29 locales for a LOW config-fault nicety.
keysources config faults keep borrowing the transient key-service code until a
coordinated i18n release; decrypt_bus-pub and the SSRF-parity additions stay.
2026-09-15 13:46:06 -07:00
Matthew Jackson 034a8b145b fix: cross-repo integration — widen decrypt_bus, SSRF parity, KeyServiceConfig
- aacs/content: widen decrypt_bus to pub so freemkv-drivetest can invert
  AACS 2.0 bus encryption out-of-tree (was pub(crate), broke that build).
- mux/network: port the 6to4 (2002::/16) and Teredo (2001:0000::/32)
  tunnel-decode arms and the 198.18.0.0/15 + 192.0.0.0/24 blocks from the
  canonical keysources SSRF classifier so both cover the same set.
- error: add distinct KeyServiceConfig variant (E_KEY_SERVICE_CONFIG 7031)
  for the standing key-service misconfiguration fault, separate from the
  transient KeyServiceUnavailable; keysources' online.rs adopts it later.
2026-09-15 13:36:47 -07:00
Matthew Jackson 6720ccdf8b style: cargo fmt the audit changes 2026-09-15 11:02:53 -07:00
Matthew Jackson 24ab408559 test/labels: cover dirimage nesting cap, long_ad dir, paramount feature hint
- dirimage: add a MAX_DEPTH nesting-cap test that walks a real
  deeper-than-cap directory chain and expects DirImageTooLarge.
- udf: add a long_ad (ICB flags = 1) directory read test — the LBA sits
  at AD offset 4, not 12 like extended_ad.
- labels/paramount: derive the feature hint's id and filename from one
  parsed number (canonical 5-digit NNNNN.mpls) so they can't disagree,
  the same fix already applied to fox, and add a hint-parse test.
2026-09-15 11:02:04 -07:00
Matthew Jackson 6e427e2eac test(scsi/linux): cover the fd-reopen path helpers
Add unit tests for to_c_path (single trailing NUL, path bytes preserved
for libc::open) and resolve_to_sg (sg passthrough plus the no-filename
and non-sr/sg fallbacks that need no sysfs) — the helpers the open /
drive_has_disc / recovery reopen path relies on. Verified compiling for
x86_64-unknown-linux-gnu (this module is Linux-only).
2026-09-15 10:58:38 -07:00
Matthew Jackson e130528fea test: cover drive tray/speed rejection, bdnav Call ops, mkv interlace patch
- drive: SET CD SPEED and PREVENT/ALLOW MEDIUM REMOVAL are best-effort; a
  drive rejection must warn, not fail the rip. New test drives a failing
  transport through set_speed/lock_tray/unlock_tray.
- bdnav: CallObject/CallTitle follow the same play path as JumpObject/
  JumpTitle for feature resolution; add tests for both.
- mkv: FlagInterlaced is corrected at finish() to the whole-stream scan
  majority; add a test that a progressive leader on an interlaced feature
  is patched from progressive to interlaced.
2026-09-15 10:56:26 -07:00
Matthew Jackson 0de2e598ef dvdnav(test): cover Vm set-ops (incl divide/mod by zero) and First-Play SetSystem
Add a direct Vm::set test exercising mov/add/sub/mul/div/mod/and/or/xor
and the divide- and modulo-by-zero guards (register left intact, no
panic) plus an unmodelled-op no-op, and a First-Play test that a
SetSystem instruction keeps resolution running so a following JumpTT
still resolves.
2026-09-15 10:51:23 -07:00
Matthew Jackson d0a5a90261 cleanup: remove dead MOBJ cmd cap, add early-break and doc clarifications
- bdnav/mobj: drop the unreachable MAX_CMDS cap (num_cmds is a u16 and the
  span is already bounded against the input) and its flagged NOTE.
- drive/wait_ready: break out immediately on a dead bus (transport
  failure) instead of polling a phantom for the full ~30 s; add a test.
- css/is_scrambled_pack: document that the 0x11 stream_id offset assumes
  zero pack stuffing (true for DVD-Video VOB packs) and why a stuffed pack
  degrades safely.
- scsi/macos_shim: document why releasing g_handle_lock before da_hold is
  TOCTOU-safe (da_hold re-validates the handle under the lock).
2026-09-15 10:49:16 -07:00
Matthew Jackson be296b3901 mux(timeline): tag provisionals with a monotonic epoch counter
passive_offset tagged a provisional offset with epoch_offsets.len() as
the epoch identity, but that length is capped at MAX_EPOCHS. Past the cap
the ring's remove+push leaves len() constant, so a provisional captured
then compared equal to the current identity forever and never retired,
drifting that track off every other one for the rest of the title. Add a
monotonic epoch_seq bumped once per open_epoch and tag provisionals with
it. Add a past-the-cap retirement test.
2026-09-15 10:45:35 -07:00
Matthew Jackson b30b06e04e mpls/ifo: fix STN gate off-by-one and pin the PGC palette chroma order
mpls: the STN table gate required item.len() > STN_OFFSET + 16, one byte
more than the 16-byte header actually spans, skipping a first play item
whose STN header ends exactly at the item boundary. Use >=, with a
boundary test.

ifo: the subtitle palette layout [padding, Y, Cr, Cb] (Cr BEFORE Cb) was
correct but under-documented; harden the comment against a future
byte-swap and add a test that cross-checks the parse against the DvdSub
ycbcr_to_rgb consumer so the two read Cr/Cb the same way.
2026-09-15 10:42:37 -07:00