use super::types::{DeviceKey, DiscEntry, HostCert};
pub trait KeyProvider: Send + Sync {
fn device_keys(&self) -> Vec<DeviceKey> {
Vec::new()
}
fn processing_keys(&self) -> Vec<[u8; 16]> {
Vec::new()
}
fn media_keys(&self) -> Vec<[u8; 16]> {
Vec::new()
}
fn host_certs(&self) -> Vec<HostCert> {
Vec::new()
}
fn lookup_disc_by_hash(&self, _disc_hash: &[u8; 20]) -> Option<DiscEntry> {
None
}
fn lookup_disc_by_vid(&self, _volume_id: &[u8; 16]) -> Option<DiscEntry> {
None
}
}
pub(crate) struct Providers<'a>(pub &'a [&'a dyn KeyProvider]);
impl Providers<'_> {
pub fn device_keys(&self) -> Vec<DeviceKey> {
let mut v: Vec<DeviceKey> = self.0.iter().flat_map(|p| p.device_keys()).collect();
v.sort_unstable_by_key(|d| (d.key, d.node, d.uv, d.u_mask_shift));
v.dedup_by_key(|d| (d.key, d.node, d.uv, d.u_mask_shift));
v
}
pub fn processing_keys(&self) -> Vec<[u8; 16]> {
let mut v: Vec<[u8; 16]> = self.0.iter().flat_map(|p| p.processing_keys()).collect();
v.sort_unstable();
v.dedup();
v
}
pub fn media_keys(&self) -> Vec<[u8; 16]> {
let mut v: Vec<[u8; 16]> = self.0.iter().flat_map(|p| p.media_keys()).collect();
v.sort_unstable();
v.dedup();
v
}
#[allow(dead_code)]
pub fn host_certs(&self) -> Vec<HostCert> {
self.0.iter().flat_map(|p| p.host_certs()).collect()
}
pub fn lookup_disc_by_hash(&self, disc_hash: &[u8; 20]) -> Option<DiscEntry> {
self.0.iter().find_map(|p| p.lookup_disc_by_hash(disc_hash))
}
pub fn lookup_disc_by_vid(&self, volume_id: &[u8; 16]) -> Option<DiscEntry> {
self.0.iter().find_map(|p| p.lookup_disc_by_vid(volume_id))
}
}
pub(crate) struct SuppliedKey {
pub device_keys: Vec<DeviceKey>,
pub processing_keys: Vec<[u8; 16]>,
pub media_keys: Vec<[u8; 16]>,
pub disc_entry: Option<DiscEntry>,
}
impl KeyProvider for SuppliedKey {
fn device_keys(&self) -> Vec<DeviceKey> {
self.device_keys.clone()
}
fn processing_keys(&self) -> Vec<[u8; 16]> {
self.processing_keys.clone()
}
fn media_keys(&self) -> Vec<[u8; 16]> {
self.media_keys.clone()
}
fn lookup_disc_by_hash(&self, _disc_hash: &[u8; 20]) -> Option<DiscEntry> {
self.disc_entry.clone()
}
fn lookup_disc_by_vid(&self, _volume_id: &[u8; 16]) -> Option<DiscEntry> {
self.disc_entry.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(hash: &str, vuk: u8) -> DiscEntry {
DiscEntry {
disc_hash: hash.to_string(),
title: "t".to_string(),
media_key: None,
disc_id: None,
vuk: Some([vuk; 16]),
unit_keys: Vec::new(),
}
}
fn dk(byte: u8, node: u16) -> DeviceKey {
DeviceKey {
key: [byte; 16],
node,
uv: 1,
u_mask_shift: 0,
}
}
#[derive(Default)]
struct Fixed {
dks: Vec<DeviceKey>,
pks: Vec<[u8; 16]>,
mks: Vec<[u8; 16]>,
hash_hit: Option<DiscEntry>,
vid_hit: Option<DiscEntry>,
}
impl KeyProvider for Fixed {
fn device_keys(&self) -> Vec<DeviceKey> {
self.dks.clone()
}
fn processing_keys(&self) -> Vec<[u8; 16]> {
self.pks.clone()
}
fn media_keys(&self) -> Vec<[u8; 16]> {
self.mks.clone()
}
fn lookup_disc_by_hash(&self, _h: &[u8; 20]) -> Option<DiscEntry> {
self.hash_hit.clone()
}
fn lookup_disc_by_vid(&self, _v: &[u8; 16]) -> Option<DiscEntry> {
self.vid_hit.clone()
}
}
#[test]
fn default_provider_methods_return_empty() {
struct Empty;
impl KeyProvider for Empty {}
let e = Empty;
assert!(e.device_keys().is_empty());
assert!(e.processing_keys().is_empty());
assert!(e.media_keys().is_empty());
assert!(e.host_certs().is_empty());
assert!(e.lookup_disc_by_hash(&[0u8; 20]).is_none());
assert!(e.lookup_disc_by_vid(&[0u8; 16]).is_none());
}
#[test]
fn providers_processing_keys_union_and_dedup() {
let a = Fixed {
pks: vec![[0x01u8; 16], [0x02u8; 16]],
..Default::default()
};
let b = Fixed {
pks: vec![[0x02u8; 16], [0x03u8; 16]],
..Default::default()
};
let arr: &[&dyn KeyProvider] = &[&a, &b];
let mut got = Providers(arr).processing_keys();
got.sort();
assert_eq!(got, vec![[0x01u8; 16], [0x02u8; 16], [0x03u8; 16]]);
}
#[test]
fn providers_media_keys_union_and_dedup() {
let a = Fixed {
mks: vec![[0xAAu8; 16]],
..Default::default()
};
let b = Fixed {
mks: vec![[0xAAu8; 16], [0xBBu8; 16]],
..Default::default()
};
let arr: &[&dyn KeyProvider] = &[&a, &b];
let mut got = Providers(arr).media_keys();
got.sort();
assert_eq!(got, vec![[0xAAu8; 16], [0xBBu8; 16]]);
}
#[test]
fn providers_device_keys_dedup_on_value_tuple() {
let a = Fixed {
dks: vec![dk(0x11, 5), dk(0x11, 5)],
..Default::default()
};
let b = Fixed {
dks: vec![dk(0x11, 5), dk(0x11, 6)],
..Default::default()
};
let arr: &[&dyn KeyProvider] = &[&a, &b];
let got = Providers(arr).device_keys();
assert_eq!(got.len(), 2, "identical DKs dedup; differing node kept");
let nodes: Vec<u16> = got.iter().map(|d| d.node).collect();
assert!(nodes.contains(&5) && nodes.contains(&6));
}
#[test]
fn providers_lookup_by_hash_first_hit_wins() {
let a = Fixed {
hash_hit: Some(entry("first", 0x01)),
..Default::default()
};
let b = Fixed {
hash_hit: Some(entry("second", 0x02)),
..Default::default()
};
let arr: &[&dyn KeyProvider] = &[&a, &b];
let got = Providers(arr).lookup_disc_by_hash(&[0u8; 20]).unwrap();
assert_eq!(got.disc_hash, "first");
assert_eq!(got.vuk, Some([0x01u8; 16]));
}
#[test]
fn providers_lookup_by_hash_falls_through_to_later_provider() {
let a = Fixed::default(); let b = Fixed {
hash_hit: Some(entry("second", 0x02)),
..Default::default()
};
let arr: &[&dyn KeyProvider] = &[&a, &b];
let got = Providers(arr).lookup_disc_by_hash(&[0u8; 20]).unwrap();
assert_eq!(got.disc_hash, "second");
}
#[test]
fn providers_lookup_by_vid_first_hit_wins() {
let a = Fixed {
vid_hit: Some(entry("vid-a", 0x07)),
..Default::default()
};
let b = Fixed {
vid_hit: Some(entry("vid-b", 0x08)),
..Default::default()
};
let arr: &[&dyn KeyProvider] = &[&a, &b];
let got = Providers(arr).lookup_disc_by_vid(&[0u8; 16]).unwrap();
assert_eq!(got.disc_hash, "vid-a");
}
#[test]
fn providers_empty_array_yields_nothing() {
let arr: &[&dyn KeyProvider] = &[];
let p = Providers(arr);
assert!(p.device_keys().is_empty());
assert!(p.processing_keys().is_empty());
assert!(p.media_keys().is_empty());
assert!(p.lookup_disc_by_hash(&[0u8; 20]).is_none());
assert!(p.lookup_disc_by_vid(&[0u8; 16]).is_none());
}
#[test]
fn supplied_key_exposes_only_populated_fields() {
let sk = SuppliedKey {
device_keys: vec![dk(0x33, 9)],
processing_keys: Vec::new(),
media_keys: Vec::new(),
disc_entry: None,
};
assert_eq!(sk.device_keys().len(), 1);
assert!(sk.processing_keys().is_empty());
assert!(sk.media_keys().is_empty());
assert!(sk.lookup_disc_by_hash(&[0u8; 20]).is_none());
assert!(sk.lookup_disc_by_vid(&[0u8; 16]).is_none());
}
#[test]
fn supplied_key_disc_entry_returned_for_any_hash_or_vid() {
let sk = SuppliedKey {
device_keys: Vec::new(),
processing_keys: Vec::new(),
media_keys: Vec::new(),
disc_entry: Some(entry("supplied", 0x44)),
};
let h1 = sk.lookup_disc_by_hash(&[0x01u8; 20]).unwrap();
let h2 = sk.lookup_disc_by_hash(&[0xFFu8; 20]).unwrap();
assert_eq!(h1.disc_hash, "supplied");
assert_eq!(h2.disc_hash, "supplied");
assert!(sk.lookup_disc_by_vid(&[0x00u8; 16]).is_some());
}
}