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
|
// Read content -- decryption is applied on read by the DiscStream decorator.
|
||||||
let mut reader = disc.open_title(&mut session, 0).unwrap();
|
// Live disc does NOT go through the URL resolver: `input("disc://...")` returns
|
||||||
while let Some(unit) = reader.read_unit().unwrap() {
|
// Error::DiscUrlNotDirect by design.
|
||||||
// decrypted content
|
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
|
The application never calls decryption functions and never manages the
|
||||||
manages handshakes. All of that is internal to `Disc::scan()` and the content
|
drive-level handshake. It DOES own key resolution — see below.
|
||||||
reader.
|
|
||||||
|
|
||||||
### KEYDB Location
|
### Key resolution is the caller's job
|
||||||
|
|
||||||
`ScanOptions` controls where the keydb is loaded from. If no explicit path is
|
`libfreemkv` is **lookup-free: it resolves no keys and reads no keydb.** There is
|
||||||
set, the library checks the standard config locations. To specify an explicit
|
no `ScanOptions::with_keydb`, and `ScanOptions` has no keydb field — its only
|
||||||
path:
|
scan input is the optional drive credentials for the live-drive authenticated
|
||||||
|
handshake.
|
||||||
|
|
||||||
```rust
|
The caller resolves a key out-of-band through a key source and applies it with
|
||||||
let opts = ScanOptions::with_keydb("/path/to/keydb.cfg");
|
[`Disc::decrypt_with`]. `freemkv-keysources` is the crate that implements the
|
||||||
let disc = Disc::scan(&mut session, &opts).unwrap();
|
keydb and key-server sources; `ScanOptions::key_sources` takes them as
|
||||||
```
|
`Box<dyn KeySource>`.
|
||||||
|
|
||||||
### AacsState
|
### AacsState
|
||||||
|
|
||||||
@@ -97,7 +108,7 @@ After a successful scan, `disc.aacs` contains an `AacsState`:
|
|||||||
| `bus_encryption` | `bool` | Whether bus encryption is active |
|
| `bus_encryption` | `bool` | Whether bus encryption is active |
|
||||||
| `mkb_version` | `Option<u32>` | MKB version from disc |
|
| `mkb_version` | `Option<u32>` | MKB version from disc |
|
||||||
| `disc_hash` | `String` | Identifier for the disc's key-input files |
|
| `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
|
## 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 b1 = byte_at(data, offset + 1)?;
|
||||||
|
|
||||||
let coding_mode = (b0 >> 5) & 0x07;
|
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 {
|
let codec = match coding_mode {
|
||||||
0 => Codec::Ac3,
|
0 => Codec::Ac3,
|
||||||
2 => Codec::Mpeg1,
|
2 | 3 => Codec::Mp2,
|
||||||
3 => Codec::Mp2,
|
|
||||||
4 => Codec::Lpcm,
|
4 => Codec::Lpcm,
|
||||||
6 => Codec::Dts,
|
6 => Codec::Dts,
|
||||||
_ => Codec::Unknown(coding_mode),
|
_ => Codec::Unknown(coding_mode),
|
||||||
@@ -1218,6 +1222,33 @@ mod tests {
|
|||||||
assert_eq!(crate::mux::ps::dvd_audio_pid(0x89), Some(0xBD89));
|
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]
|
#[test]
|
||||||
fn audio_attr_dts() {
|
fn audio_attr_dts() {
|
||||||
let mut data = vec![0u8; 16];
|
let mut data = vec![0u8; 16];
|
||||||
|
|||||||
Reference in New Issue
Block a user