Reject a short READ CAPACITY reply, and count only entry marks as chapters

Two cases of the same shape: one policy implemented twice, with only one
copy hardened.

Disc::read_capacity decoded buf[0..4] from READ CAPACITY (10) without
checking that the transport actually delivered four bytes, even though its
comment claims to mirror decode_read_capacity — which has exactly that
check, and documents why. A drive answering GOOD with an empty data phase
leaves the buffer zeroed, so last_lba decodes to 0 and the probe reports a
one-sector disc instead of an error. It now calls the shared decoder rather
than re-deriving it.

collect_chapter_summary filtered chapters on mark_type <= 1, counting the
reserved type 0. PlaylistMark's own doc says filters must test == 1, and
disc/bluray.rs did; the labels path did not, inflating the public
chapter_count and letting a playlist whose only marks are reserved pass the
chapter_count == 0 skip. Both sites now share PlaylistMark::is_chapter_mark
so the copies cannot drift again.

Both fixes were confirmed red before green.
This commit is contained in:
Matthew Jackson
2026-08-01 11:00:49 -07:00
parent e0ff0cfeb4
commit fb321f51eb
5 changed files with 81 additions and 12 deletions
+32
View File
@@ -39,6 +39,18 @@ pub(crate) struct PlaylistMark {
pub timestamp: u32,
}
impl PlaylistMark {
/// Is this mark a chapter entry point?
///
/// Only `mark_type == 1` counts. Type 0 is reserved and type 2 is a link
/// point, and neither is a chapter. Every chapter filter in the crate goes
/// through here: two hand-rolled copies had already drifted, one testing
/// `<= 1` and silently counting reserved marks as chapters.
pub(crate) fn is_chapter_mark(&self) -> bool {
self.mark_type == 1
}
}
/// A play item — one clip reference with in/out times.
#[derive(Debug)]
pub(crate) struct PlayItem {
@@ -1698,4 +1710,24 @@ mod tests {
assert_eq!(entry.coding_type, 0x90);
assert_eq!(entry.language, "");
}
/// Type 0 is reserved and type 2 is a link point; neither is a chapter.
/// `labels::collect_chapter_summary` used to filter on `mark_type <= 1`,
/// which counted reserved marks and inflated the public `chapter_count`
/// (and let a playlist whose only marks are reserved pass the
/// `chapter_count == 0` skip). Both call sites now share this predicate.
#[test]
fn only_entry_marks_count_as_chapters() {
let mk = |mark_type| PlaylistMark {
mark_type,
play_item_ref: 0,
timestamp: 0,
};
assert!(
!mk(0).is_chapter_mark(),
"type 0 is reserved, not a chapter"
);
assert!(mk(1).is_chapter_mark());
assert!(!mk(2).is_chapter_mark(), "type 2 is a link point");
}
}