use alloc::string::String;
use alloc::vec::Vec;
use crate::cenc::ProtectionSystemSpecificHeaderBox;
use crate::error::{Error, Result};
use crate::rtp::{base64_decode, base64_encode};
pub const WIDEVINE_SYSTEM_ID: [u8; 16] = [
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
];
pub const PLAYREADY_SYSTEM_ID: [u8; 16] = [
0x9A, 0x04, 0xF0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xAB, 0x92, 0xE6, 0x5B, 0xE0, 0x88, 0x5F, 0x95,
];
pub const FAIRPLAY_SYSTEM_ID: [u8; 16] = [
0x94, 0xCE, 0x86, 0xFB, 0x07, 0xFF, 0x4F, 0x43, 0xAD, 0xB8, 0x93, 0xD2, 0xFA, 0x96, 0x8C, 0xA2,
];
pub const COMMON_SYSTEM_ID: [u8; 16] = [
0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
];
pub const fn cenc_kid_to_playready(uuid: [u8; 16]) -> [u8; 16] {
[
uuid[3], uuid[2], uuid[1], uuid[0], uuid[5], uuid[4], uuid[7], uuid[6], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], ]
}
pub const fn playready_kid_to_cenc(guid: [u8; 16]) -> [u8; 16] {
[
guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6], guid[8], guid[9],
guid[10], guid[11], guid[12], guid[13], guid[14], guid[15],
]
}
const WRMHEADER_NAMESPACE: &str = "http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader";
const PLAYREADY_ALGID_AESCTR: &str = "AESCTR";
const PRO_RECORD_TYPE_HEADER: u16 = 0x0001;
pub fn playready_wrmheader(kids: &[[u8; 16]], la_url: Option<&str>) -> String {
let version = "4.2.0.0";
let mut xml = String::new();
xml.push_str("<WRMHEADER xmlns=\"");
xml.push_str(WRMHEADER_NAMESPACE);
xml.push_str("\" version=\"");
xml.push_str(version);
xml.push_str("\"><DATA><PROTECTINFO><KIDS>");
for kid in kids {
let guid = cenc_kid_to_playready(*kid);
let value = base64_encode(&guid);
xml.push_str("<KID ALGID=\"");
xml.push_str(PLAYREADY_ALGID_AESCTR);
xml.push_str("\" VALUE=\"");
xml.push_str(&value);
xml.push_str("\"></KID>");
}
xml.push_str("</KIDS></PROTECTINFO>");
if let Some(url) = la_url {
xml.push_str("<LA_URL>");
xml.push_str(&xml_escape(url));
xml.push_str("</LA_URL>");
}
xml.push_str("</DATA></WRMHEADER>");
xml
}
fn xml_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
_ => out.push(c),
}
}
out
}
fn utf16le_bytes(s: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(s.len() * 2);
for unit in s.encode_utf16() {
out.extend_from_slice(&unit.to_le_bytes());
}
out
}
pub fn playready_pro(kids: &[[u8; 16]], la_url: Option<&str>) -> Vec<u8> {
let wrm = playready_wrmheader(kids, la_url);
let value = utf16le_bytes(&wrm);
let record_count: u16 = 1;
let total_len = 4 + 2 + 2 + 2 + value.len();
let mut out = Vec::with_capacity(total_len);
out.extend_from_slice(&(total_len as u32).to_le_bytes());
out.extend_from_slice(&record_count.to_le_bytes());
out.extend_from_slice(&PRO_RECORD_TYPE_HEADER.to_le_bytes());
out.extend_from_slice(&(value.len() as u16).to_le_bytes());
out.extend_from_slice(&value);
out
}
pub fn playready_pssh(
kids: &[[u8; 16]],
la_url: Option<&str>,
) -> ProtectionSystemSpecificHeaderBox {
ProtectionSystemSpecificHeaderBox {
version: 0,
system_id: PLAYREADY_SYSTEM_ID,
kids: Vec::new(),
data: playready_pro(kids, la_url),
}
}
const PROTOBUF_WIRETYPE_LEN: u8 = 2;
const PROTOBUF_WIRETYPE_VARINT: u8 = 0;
const WV_FIELD_KEY_ID: u8 = 2;
const WV_FIELD_PROVIDER: u8 = 3;
const WV_FIELD_CONTENT_ID: u8 = 4;
const WV_FIELD_PROTECTION_SCHEME: u8 = 9;
const fn pb_tag(field: u8, wire_type: u8) -> u8 {
(field << 3) | wire_type
}
fn pb_put_varint(out: &mut Vec<u8>, mut value: u64) {
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
out.push(byte);
if value == 0 {
break;
}
}
}
fn pb_put_len_delimited(out: &mut Vec<u8>, field: u8, bytes: &[u8]) {
out.push(pb_tag(field, PROTOBUF_WIRETYPE_LEN));
pb_put_varint(out, bytes.len() as u64);
out.extend_from_slice(bytes);
}
pub fn widevine_pssh_data(
key_ids: &[[u8; 16]],
provider: Option<&str>,
protection_scheme: Option<[u8; 4]>,
) -> Vec<u8> {
let mut out = Vec::new();
for kid in key_ids {
pb_put_len_delimited(&mut out, WV_FIELD_KEY_ID, kid);
}
if let Some(p) = provider {
pb_put_len_delimited(&mut out, WV_FIELD_PROVIDER, p.as_bytes());
}
let _ = WV_FIELD_CONTENT_ID;
if let Some(scheme) = protection_scheme {
out.push(pb_tag(WV_FIELD_PROTECTION_SCHEME, PROTOBUF_WIRETYPE_VARINT));
pb_put_varint(&mut out, u32::from_be_bytes(scheme) as u64);
}
out
}
pub fn widevine_pssh(
kids: &[[u8; 16]],
provider: Option<&str>,
) -> ProtectionSystemSpecificHeaderBox {
ProtectionSystemSpecificHeaderBox {
version: 0,
system_id: WIDEVINE_SYSTEM_ID,
kids: Vec::new(),
data: widevine_pssh_data(kids, provider, None),
}
}
pub fn fairplay_pssh_data(skd_uri: &str) -> Vec<u8> {
skd_uri.as_bytes().to_vec()
}
pub fn fairplay_pssh(skd_uri: &str) -> ProtectionSystemSpecificHeaderBox {
ProtectionSystemSpecificHeaderBox {
version: 0,
system_id: FAIRPLAY_SYSTEM_ID,
kids: Vec::new(),
data: fairplay_pssh_data(skd_uri),
}
}
pub fn playready_kid_value_decode(value: &str) -> Result<[u8; 16]> {
let bytes = base64_decode(value)?;
if bytes.len() != 16 {
return Err(Error::InvalidValue {
field: "playready KID VALUE",
value: bytes.len() as u64,
reason: "expected 16 decoded bytes",
});
}
let mut out = [0u8; 16];
out.copy_from_slice(&bytes);
Ok(out)
}