mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
Security - Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged. CI / tooling - clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny dependency-audit gate with first-party git sources allowed (#15); audience-based comment-guard (#17). Docs - README (keydb ownership, BD-J parser count), drive-access transport/reset notes, mux timeline module-doc links.
240 lines
12 KiB
Markdown
240 lines
12 KiB
Markdown
# src/disc/bluray.rs — extended notes
|
|
|
|
Overflow prose for comments that were trimmed to the internal 3-line cap in
|
|
`src/disc/bluray.rs`. Each section is pointed to by a `// See docs/bluray.md
|
|
— <topic>` comment at the corresponding site.
|
|
|
|
## CLIP_STREAM_EXTS
|
|
|
|
Stream-file extensions probed for a BD-family playlist clip, in priority
|
|
order. A clip is normally `.m2ts`; AACS 2.1 (FMTS) discs name the main
|
|
feature `.fmts` (an M2TS transport stream plus forensic variant segments)
|
|
and 3D discs use `.ssif`. `.m2ts` is tried first, so a normal clip is
|
|
unaffected — the fallback only runs when `.m2ts` is absent (exactly when
|
|
`file_extents` errors).
|
|
|
|
Scope: these are all variants that live in `BDMV/STREAM/` and are reached
|
|
through an MPLS playlist. HD-DVD's `.evo` does NOT belong here — HD-DVD is
|
|
a different tree (`HVDVD_TS/`) with `.XPL` playlists and needs its own
|
|
enumerator (a peer to `parse_playlist`), not another extension in this
|
|
list.
|
|
|
|
## scan_bluray_titles
|
|
|
|
Cancellation: `halt` is polled between playlists and once more after the
|
|
loop, and a read that fails with `Error::Halted` — how a live drive
|
|
reports a Stop, since `Drive::checked_exec` fails every command once its
|
|
flag is set and `Drive::read` preserves the variant — is propagated rather
|
|
than swallowed. `Halted` is the only error this returns from the
|
|
enumeration itself; an unreadable or unparseable playlist keeps its
|
|
best-effort skip.
|
|
|
|
It has to be an error and not a short title list, for the same reason
|
|
spelled out on `Disc::scan_hddvd_titles`: a cancelled enumeration that
|
|
returned `Ok` would be indistinguishable from a disc that genuinely holds
|
|
fewer titles, and the caller would cache and act on it. Before this, a Stop
|
|
pressed mid-scan failed every remaining `.mpls` read in turn, each one
|
|
silently skipped, and the scan returned `Ok(truncated)` at rc=0.
|
|
|
|
## parse_playlist
|
|
|
|
Sums PlayItem durations; returns `Ok(None)` if the playlist is under 30
|
|
seconds (skips menu / clip-info stub playlists), fails to parse, or names a
|
|
clip that cannot be resolved. Physical sector extents are pulled from the
|
|
UDF allocation descriptors of each referenced `.m2ts` (deduplicated by
|
|
clip_id).
|
|
|
|
`Ok(None)` keeps its two benign meanings (unparseable MPLS, sub-30s
|
|
playlist) plus the deliberate "drop this title" outcomes below. `Err` means
|
|
the scan is over: today that is only `Error::Halted`, the operator's Stop,
|
|
which must not be reported as a disc that simply holds fewer titles.
|
|
|
|
## Test: parse_playlist_keeps_exactly_30_seconds
|
|
|
|
At exactly 30s the playlist is kept (`< 30.0` is strict). The fixture is a
|
|
fully wired BDMV (`make_bdmv_fs`), not the bare `make_min_fs` this used to
|
|
use. `make_min_fs` lays no STREAM/CLIPINF, so the play item's `.clpi` read
|
|
now fails and the title drops — which is correct behaviour for an
|
|
unresolvable clip but has nothing to do with the 30-second boundary this
|
|
test exists to pin. Wiring the clip keeps the test measuring exactly one
|
|
thing.
|
|
|
|
## make_bdmv_fs_ssif
|
|
|
|
Full BDMV with a real Blu-ray 3D layout: `.ssif` files under
|
|
`BDMV/STREAM/SSIF/<clip>.ssif` (note the SSIF subdirectory, unlike
|
|
`make_bdmv_fs_ext`) plus a matching `.clpi` in CLIPINF. Resolving the SSIF
|
|
is what latches `is_3d = true` in `parse_playlist`.
|
|
|
|
## Test: parse_playlist_fmts_clip_resolves_extent
|
|
|
|
AACS 2.1: the feature clip is `00001.fmts`, NOT `.m2ts`. The
|
|
`CLIP_STREAM_EXTS` fallback in `parse_playlist` must still resolve the
|
|
physical extent — before the fix the hard-coded `.m2ts` path errored,
|
|
yielding empty extents (a silent empty rip and 0 encrypted samples for key
|
|
resolution). Size still comes from the `.clpi`, which parses regardless.
|
|
|
|
## Test: parse_playlist_dedups_repeated_clip_extents_and_size
|
|
|
|
THE 0.31.0 DEDUP PATH. A playlist that references the SAME clip_id from
|
|
multiple PlayItems (seamless split / looped segment) must count the
|
|
physical extents and packet bytes exactly once — mux reads extents in
|
|
order, so a duplicate would mux the A/V twice and inflate `size_bytes`
|
|
(bluray.rs: `first_ref = seen_clips.insert(...)` gates both `total_size +=`
|
|
and the `extents.push`). Per-PlayItem Clip entries are still recorded for
|
|
both.
|
|
|
|
## Test: parse_playlist_missing_clpi_yields_no_title
|
|
|
|
A clip whose `.clpi` is missing must yield no title.
|
|
|
|
This test used to assert the opposite — a title with `size_bytes == 0` and
|
|
empty extents — and its docstring quoted the buggy control flow ("bluray.rs
|
|
only fetches extents inside the `if let Ok(clpi_data)`") as if it were the
|
|
specification. It was blessing the defect: the title's `duration_secs` is
|
|
summed from the PlayItems before the clip is resolved, so the returned
|
|
title advertised the movie's full runtime while carrying none of its
|
|
bytes, and the discarded error meant not one log line said so. That is the
|
|
flagship failure class of this crate — a failure that looks like success —
|
|
reached through an ordinary missing or scratched CLIPINF file.
|
|
|
|
The correct behaviour is the same as for a clip whose extents cannot be
|
|
resolved (see `parse_playlist_unreadable_clip_icb_yields_no_title`): drop
|
|
the title and log the read's own error code.
|
|
|
|
That last clause is now asserted, not merely asked for. The site's comment
|
|
insists on `e.code()` because a missing `.clpi` (E6003), a scratched one
|
|
(E6000) and a malformed one (E6002) are different populations, but nothing
|
|
checked it: putting a literal back compiled and passed. Mutation: `"E6017"`
|
|
(or any fixed code) in place of `"E{}", e.code()` fails here, and so does
|
|
dropping the warn entirely — a silent drop is the same invisible title
|
|
loss this test was written for, one step later.
|
|
|
|
## Test: parse_playlist_unrecorded_extent_yields_no_title
|
|
|
|
A clip stream whose ICB declares an UNRECORDED (ECMA-167 4/14.14.1.1
|
|
type-1) extent must not yield a title at all.
|
|
|
|
The extent is allocated to the file but was never written, so the file's
|
|
content there is zeros while the media holds whatever was left at those
|
|
sectors. Neither answer a `(lba, sector_count)` read plan can give is true
|
|
— reading it splices undefined sectors into the rip as content, dropping
|
|
it slides every later extent's byte space — so the title is refused rather
|
|
than mis-ripped. This fixture is the shape a crafted disc uses to get such
|
|
a range into a title's extent list.
|
|
|
|
(The `sectors > 0 && lba > 0` filter below the resolver stays as defence in
|
|
depth; a zero-length AD is only reachable as an unrecorded descriptor,
|
|
since a zero-length TYPE 0 one terminates the AD list.)
|
|
|
|
## Test: parse_playlist_unreadable_clip_icb_yields_no_title
|
|
|
|
An unrecorded extent was never the only way a clip fails to resolve.
|
|
|
|
RED BEFORE GREEN: this fixture gives the .m2ts an ICB whose descriptor tag
|
|
is neither 261 nor 266, so `file_extents` returns `DiscRead` — the same
|
|
variant a scratched sector under a real clip's ICB produces, which is the
|
|
ordinary way this happens on real media. Before the fix only
|
|
`UdfUnrecordedExtent` set the drop flag, so this fell through to the "file
|
|
absent" path and `parse_playlist` returned a title: full declared duration
|
|
from the play item, `total_size` already counted from the .clpi, and zero
|
|
extents — a movie advertising its runtime with the content missing, and
|
|
not one log line. The clip must drop the title exactly as an unrecorded
|
|
extent does.
|
|
|
|
## Test: parse_playlist_logs_a_non_absence_ssif_failure_the_m2ts_fallback_hid
|
|
|
|
A non-absence SSIF failure that the `.m2ts` fallback then papers over must
|
|
be logged, with its own code.
|
|
|
|
`unresolved` had exactly one reader — `if let (None, Some(code))` — so a
|
|
code recorded for `/BDMV/STREAM/SSIF/<clip>.ssif` was thrown away whenever
|
|
the fallback succeeded. The disc here IS a 3D disc: the SSIF is present and
|
|
carries both eyes, and only its ICB is unreadable (a scratched sector, the
|
|
ordinary way this happens). The title shipped base-view 2D at rc=0 with
|
|
`is_3d` false and not one log line, so the operator's rip is silently
|
|
missing the dependent view and the journal cannot tell this disc apart
|
|
from one that was only ever 2D.
|
|
|
|
The title is deliberately still returned — the base view resolved, so
|
|
refusing would trade a degraded rip for no rip. The defect being fixed is
|
|
the silence, not the fallback.
|
|
|
|
Mutations this catches: deleting the new `(Some(_), Some(code))` arm, or
|
|
restoring the `if let (None, Some(code))` shape, leaves no event to find;
|
|
logging a fixed code instead of `e.code()` fails the code assertion;
|
|
making the arm `return Ok(None)` fails the title assertion.
|
|
|
|
## Test: parse_playlist_id_falls_back_to_zero_when_suffix_is_not_mpls
|
|
|
|
A filename that is long enough (>= 5 bytes) but does NOT end in ".mpls"
|
|
must NOT have its last 5 bytes stripped — the whole string is handed to
|
|
the numeric parse instead, which fails and falls back to playlist_id 0
|
|
(bluray.rs `filename.len() >= 5 && filename[len-5..].eq_ignore_ascii_case(".mpls")`).
|
|
|
|
## Test: scan_bluray_titles_skips_non_mpls_extension_file
|
|
|
|
A non-directory PLAYLIST entry whose name does NOT end in ".mpls" must be
|
|
skipped even though its content parses as a perfectly good (long) MPLS
|
|
playlist — extension gating, not content sniffing, decides eligibility
|
|
(bluray.rs `!entry.is_dir && entry.name...ends_with(".mpls")`).
|
|
|
|
## HaltingReader
|
|
|
|
A `SectorSource` that fails every read in `halt_range` with
|
|
`Error::Halted` — exactly how a live drive behaves once the operator
|
|
presses Stop: `Drive::checked_exec` fails every SCSI command with `Halted`
|
|
from then on, and `Drive::read` deliberately preserves the variant. Reads
|
|
outside the range still succeed, so a test can aim the cancel at one
|
|
structure and leave the scan far enough along to have something to
|
|
truncate.
|
|
|
|
## Test: halted_playlist_read_is_not_reported_as_a_shorter_disc
|
|
|
|
A Stop on a live drive never touches `ScanOptions::halt`: `Drive` has its
|
|
own flag and `checked_exec` fails every SCSI command with `Error::Halted`
|
|
once it is set. The Blu-ray enumerator must not swallow that into a
|
|
successful scan.
|
|
|
|
RED BEFORE GREEN: with the propagation reverted this returned
|
|
`Ok([00800])` — the `if let Ok(mpls_data)` skipped the cancelled read of
|
|
00801.mpls, the loop ended, and a half-enumerated disc came back at
|
|
success. One title from a two-title disc is indistinguishable from a disc
|
|
that genuinely holds one title, and the caller caches and rips from it.
|
|
|
|
The halt lands on the second playlist deliberately: it is the last
|
|
iteration, so nothing after it would poll a flag — only propagating the
|
|
read's own error catches it.
|
|
|
|
## Test: halted_clpi_read_is_not_accounted_as_an_unresolvable_clip
|
|
|
|
The same cancel landing on a `.clpi` read must not be classified as an
|
|
unresolvable clip either.
|
|
|
|
RED BEFORE GREEN: with the `Err(Error::Halted)` arm removed from the
|
|
CLIPINF match, the cancel fell into the generic "clip could not be
|
|
resolved" arm, which logs a disc-defect code and drops the title —
|
|
accounting an operator Stop as a scratched disc, and (in the scan loop)
|
|
dropping every remaining playlist in turn for a truncated `Ok`. With the
|
|
fix the cancel is propagated with its own variant intact.
|
|
|
|
## Test: halted_extent_resolve_is_not_a_title_missing_its_clip
|
|
|
|
And the same cancel landing on `file_extents` — the clip's ICB, not its
|
|
CLIPINF — must propagate too.
|
|
|
|
RED BEFORE GREEN: `note` used to exempt `Halted` from the unresolved
|
|
classification (correctly — a cancel is not an authoring hole) but had no
|
|
way to propagate it, so the resolver simply produced no extents and
|
|
`parse_playlist` returned a title with the clip's runtime counted, its
|
|
`size_bytes` counted from the .clpi, and zero bytes behind it. Measured
|
|
with the propagation reverted: `Ok(Some((768000, [])))` — the flagship
|
|
defect shape, wearing a cancel.
|
|
|
|
## Test: read_meta_title_ignores_non_xml_file_regardless_of_content
|
|
|
|
A non-.xml file must be ignored even if its content looks like a valid
|
|
meta XML (contains a `<di:name>`) — extension gating, not content
|
|
sniffing, decides eligibility (bluray.rs `!e.is_dir &&
|
|
e.name...ends_with(".xml")`).
|