#[cfg(feature = "std")]
use alloc::string::String;
use alloc::vec::Vec;
use crate::cenc::ProtectionSystemSpecificHeaderBox;
use crate::error::{Error, Result};
use crate::rtp::base64_decode;
#[cfg(feature = "std")]
use crate::rtp::base64_encode;
#[cfg(feature = "std")]
use quick_xml::Writer;
#[cfg(feature = "std")]
use quick_xml::escape::partial_escape;
#[cfg(feature = "std")]
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
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],
]
}
#[cfg(feature = "std")]
const WRMHEADER_NAMESPACE: &str = "http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader";
#[cfg(feature = "std")]
const PLAYREADY_ALGID_AESCTR: &str = "AESCTR";
#[cfg(feature = "std")]
const PRO_RECORD_TYPE_HEADER: u16 = 0x0001;
#[cfg(feature = "std")]
pub fn playready_wrmheader(kids: &[[u8; 16]], la_url: Option<&str>) -> String {
let version = "4.2.0.0";
let mut w = Writer::new(Vec::new());
let mut root = BytesStart::new("WRMHEADER");
root.push_attribute(("xmlns", WRMHEADER_NAMESPACE));
root.push_attribute(("version", version));
let _ = w.write_event(Event::Start(root));
for name in ["DATA", "PROTECTINFO", "KIDS"] {
let _ = w.write_event(Event::Start(BytesStart::new(name)));
}
for kid in kids {
let guid = cenc_kid_to_playready(*kid);
let value = base64_encode(&guid);
let mut kid_tag = BytesStart::new("KID");
kid_tag.push_attribute(("ALGID", PLAYREADY_ALGID_AESCTR));
kid_tag.push_attribute(("VALUE", value.as_str()));
let _ = w.write_event(Event::Start(kid_tag));
let _ = w.write_event(Event::End(BytesEnd::new("KID")));
}
for name in ["KIDS", "PROTECTINFO"] {
let _ = w.write_event(Event::End(BytesEnd::new(name)));
}
if let Some(url) = la_url {
let _ = w.write_event(Event::Start(BytesStart::new("LA_URL")));
let _ = w.write_event(Event::Text(BytesText::from_escaped(partial_escape(url))));
let _ = w.write_event(Event::End(BytesEnd::new("LA_URL")));
}
for name in ["DATA", "WRMHEADER"] {
let _ = w.write_event(Event::End(BytesEnd::new(name)));
}
String::from_utf8_lossy(&w.into_inner()).into_owned()
}
#[cfg(feature = "std")]
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
}
#[cfg(feature = "std")]
pub fn playready_pro(kids: &[[u8; 16]], la_url: Option<&str>) -> Result<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 value_len = broadcast_common::len::fit_u16(value.len(), "PRO record value length")?;
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.to_le_bytes());
out.extend_from_slice(&value);
Ok(out)
}
#[cfg(feature = "std")]
pub fn playready_pssh(
kids: &[[u8; 16]],
la_url: Option<&str>,
) -> Result<ProtectionSystemSpecificHeaderBox> {
Ok(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)
}