Fix the DVD MPEG-audio codec mapping and the fabricated AACS docs
parse_audio_attr mapped DVD audio_coding_mode 2 to Codec::Mpeg1 — the MPEG-1
VIDEO variant. Codec::kind() reports Video for it, so a DVD MPEG-audio stream
was classified and handled as video everywhere downstream. Modes 2 and 3 are
both MPEG audio Layer II (3 adds the MPEG-2 multichannel extension), so both
map to Codec::Mp2. A test now walks every coding mode and asserts each result's
kind() is Audio, so no mode can map to a non-audio codec again.
docs/aacs.md documented an entire keydb-resolving API that does not exist:
ScanOptions::with_keydb, Disc::open_title, reader.read_unit(). None of those
symbols appear anywhere in the crate, and ScanOptions has no keydb field — its
own doc comment says "libfreemkv is lookup-free — it resolves no keys". A
reader following that page would conclude the library reads keydb.cfg, which
inverts the actual design: the caller resolves keys out-of-band through a
KeySource and applies them with Disc::decrypt_with.
The section is rewritten against the real API, and the AacsState table's
`key_source` type corrected from KeySource to KeyOrigin.
Worth recording: the first replacement example I wrote was itself wrong. It
used `input("disc://...")`, which resolve.rs explicitly rejects with
Error::DiscUrlNotDirect — live disc must go through Drive::open + Disc::scan +
DiscStream::new. Every symbol and signature in the committed example was
checked against the source rather than assumed.
This commit is contained in:
+27
-16
@@ -65,27 +65,38 @@ if disc.encrypted {
|
||||
}
|
||||
}
|
||||
|
||||
// Read content -- decryption is automatic
|
||||
let mut reader = disc.open_title(&mut session, 0).unwrap();
|
||||
while let Some(unit) = reader.read_unit().unwrap() {
|
||||
// decrypted content
|
||||
// Read content -- decryption is applied on read by the DiscStream decorator.
|
||||
// Live disc does NOT go through the URL resolver: `input("disc://...")` returns
|
||||
// Error::DiscUrlNotDirect by design.
|
||||
let keys = disc.decrypt_keys();
|
||||
let mut stream = DiscStream::new(
|
||||
Box::new(drive),
|
||||
disc.titles[0].clone(),
|
||||
keys,
|
||||
batch_sectors,
|
||||
disc.titles[0].content_format,
|
||||
false, // raw: false → decrypt on read
|
||||
None, // halt
|
||||
)?;
|
||||
while let Ok(Some(frame)) = stream.read() {
|
||||
// decrypted PES frames
|
||||
}
|
||||
```
|
||||
|
||||
The application never touches keys, never calls decryption functions, and never
|
||||
manages handshakes. All of that is internal to `Disc::scan()` and the content
|
||||
reader.
|
||||
The application never calls decryption functions and never manages the
|
||||
drive-level handshake. It DOES own key resolution — see below.
|
||||
|
||||
### KEYDB Location
|
||||
### Key resolution is the caller's job
|
||||
|
||||
`ScanOptions` controls where the keydb is loaded from. If no explicit path is
|
||||
set, the library checks the standard config locations. To specify an explicit
|
||||
path:
|
||||
`libfreemkv` is **lookup-free: it resolves no keys and reads no keydb.** There is
|
||||
no `ScanOptions::with_keydb`, and `ScanOptions` has no keydb field — its only
|
||||
scan input is the optional drive credentials for the live-drive authenticated
|
||||
handshake.
|
||||
|
||||
```rust
|
||||
let opts = ScanOptions::with_keydb("/path/to/keydb.cfg");
|
||||
let disc = Disc::scan(&mut session, &opts).unwrap();
|
||||
```
|
||||
The caller resolves a key out-of-band through a key source and applies it with
|
||||
[`Disc::decrypt_with`]. `freemkv-keysources` is the crate that implements the
|
||||
keydb and key-server sources; `ScanOptions::key_sources` takes them as
|
||||
`Box<dyn KeySource>`.
|
||||
|
||||
### AacsState
|
||||
|
||||
@@ -97,7 +108,7 @@ After a successful scan, `disc.aacs` contains an `AacsState`:
|
||||
| `bus_encryption` | `bool` | Whether bus encryption is active |
|
||||
| `mkb_version` | `Option<u32>` | MKB version from disc |
|
||||
| `disc_hash` | `String` | Identifier for the disc's key-input files |
|
||||
| `key_source` | `KeySource` | How the disc's key was resolved |
|
||||
| `key_source` | `KeyOrigin` | How the disc's key was resolved |
|
||||
|
||||
## keydb.cfg
|
||||
|
||||
|
||||
+33
-2
@@ -571,10 +571,14 @@ fn parse_audio_attr(data: &[u8], offset: usize) -> Result<DvdAudioAttr> {
|
||||
let b1 = byte_at(data, offset + 1)?;
|
||||
|
||||
let coding_mode = (b0 >> 5) & 0x07;
|
||||
// DVD-Video audio_coding_mode. Modes 2 and 3 are both MPEG audio Layer II
|
||||
// (3 adds the MPEG-2 multichannel extension), so both are Codec::Mp2.
|
||||
// Mode 2 previously mapped to Codec::Mpeg1 — a VIDEO variant, so
|
||||
// Codec::kind() reported Video and the audio stream was classified and
|
||||
// handled as video everywhere downstream.
|
||||
let codec = match coding_mode {
|
||||
0 => Codec::Ac3,
|
||||
2 => Codec::Mpeg1,
|
||||
3 => Codec::Mp2,
|
||||
2 | 3 => Codec::Mp2,
|
||||
4 => Codec::Lpcm,
|
||||
6 => Codec::Dts,
|
||||
_ => Codec::Unknown(coding_mode),
|
||||
@@ -1218,6 +1222,33 @@ mod tests {
|
||||
assert_eq!(crate::mux::ps::dvd_audio_pid(0x89), Some(0xBD89));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_audio_coding_mode_maps_to_an_audio_codec() {
|
||||
// An audio attribute block must never yield a codec whose kind() is Video.
|
||||
// Mode 2 (MPEG-1 audio Layer II) mapped to Codec::Mpeg1 — the MPEG-1 VIDEO
|
||||
// variant — so a DVD MPEG-audio stream was classified as video downstream.
|
||||
for (mode, want) in [
|
||||
(0u8, Codec::Ac3),
|
||||
(2, Codec::Mp2),
|
||||
(3, Codec::Mp2),
|
||||
(4, Codec::Lpcm),
|
||||
(6, Codec::Dts),
|
||||
] {
|
||||
let mut data = vec![0u8; 16];
|
||||
data[0] = mode << 5;
|
||||
data[2] = b'e';
|
||||
data[3] = b'n';
|
||||
let attr = parse_audio_attr(&data, 0).unwrap();
|
||||
assert_eq!(attr.codec, want, "coding_mode {mode} must map to {want:?}");
|
||||
assert_eq!(
|
||||
attr.codec.kind(),
|
||||
crate::disc::CodecKind::Audio,
|
||||
"coding_mode {mode} produced {:?}, whose kind is not Audio",
|
||||
attr.codec
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_attr_dts() {
|
||||
let mut data = vec![0u8; 16];
|
||||
|
||||
Reference in New Issue
Block a user