use broadcast_common::xml_chars::{XmlBudget, namespace_of};
use quick_xml::NsReader;
use quick_xml::events::Event;
use crate::box_types::{box_iter, parse_box};
use crate::error::{Error, Result};
use crate::movie_fragment::MovieFragmentBox;
use crate::smooth::{TFXD_UUID, TfxdBox};
use crate::smooth_parse::default_limits;
const PROTECTION_ELEMENT: &str = "Protection";
const BOX_MOOF: [u8; 4] = *b"moof";
const BOX_TRAF: [u8; 4] = *b"traf";
const BOX_UUID: [u8; 4] = *b"uuid";
const BOX_SENC: [u8; 4] = *b"senc";
const BOX_SAIZ: [u8; 4] = *b"saiz";
const BOX_SAIO: [u8; 4] = *b"saio";
const BOX_HEADER_LEN: usize = 8;
const BOX_SIZE_LEN: usize = 4;
pub const PIFF_SAMPLE_ENCRYPTION_UUID: [u8; 16] = [
0xA2, 0x39, 0x4F, 0x52, 0x5A, 0x9B, 0x4F, 0x14, 0xA2, 0x44, 0x6C, 0x42, 0x7C, 0x64, 0x8D, 0xF4,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FragmentState {
Clear,
Encrypted,
Undetermined,
}
impl FragmentState {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Clear => "clear",
Self::Encrypted => "encrypted",
Self::Undetermined => "undetermined",
}
}
}
broadcast_common::impl_spec_display!(FragmentState);
#[must_use]
pub fn manifest_declares_protection(xml: &str) -> bool {
let mut budget = XmlBudget::new(default_limits());
if budget.check_input(xml.len()).is_err() {
return false;
}
let mut reader = NsReader::from_str(xml);
loop {
match reader.read_resolved_event() {
Ok((ns, Event::Start(e))) => {
if budget.open_element().is_err() {
return false;
}
if matches!(namespace_of(ns), Ok(None))
&& e.local_name().as_ref() == PROTECTION_ELEMENT
{
return true;
}
}
Ok((ns, Event::Empty(e))) => {
if budget.open_element().is_err() {
return false;
}
budget.close_element();
if matches!(namespace_of(ns), Ok(None))
&& e.local_name().as_ref() == PROTECTION_ELEMENT
{
return true;
}
}
Ok((_, Event::End(_))) => budget.close_element(),
Ok((_, Event::Eof)) | Err(_) => return false,
Ok(_) => {}
}
}
}
#[must_use]
pub fn fragment_state(bytes: &[u8]) -> FragmentState {
let mut offset = 0usize;
let mut saw_moof = false;
while offset + BOX_HEADER_LEN <= bytes.len() {
let size = u32::from_be_bytes(
bytes[offset..offset + BOX_SIZE_LEN]
.try_into()
.unwrap_or([0; BOX_SIZE_LEN]),
);
let box_type = &bytes[offset + BOX_SIZE_LEN..offset + BOX_HEADER_LEN];
let is_moof = box_type == BOX_MOOF;
if is_moof {
saw_moof = true;
}
let size = size as usize;
if size < BOX_HEADER_LEN || offset + size > bytes.len() {
return if saw_moof {
FragmentState::Undetermined
} else {
FragmentState::Clear
};
}
if is_moof {
match moof_is_encrypted(&bytes[offset + BOX_HEADER_LEN..offset + size]) {
Some(true) => return FragmentState::Encrypted,
None => return FragmentState::Undetermined,
Some(false) => {}
}
}
offset += size;
}
if saw_moof && offset < bytes.len() {
FragmentState::Undetermined
} else {
FragmentState::Clear
}
}
fn moof_is_encrypted(moof_body: &[u8]) -> Option<bool> {
for result in box_iter(moof_body) {
let Ok((child, _)) = result else {
return None;
};
if !child.header.box_type.is(&BOX_TRAF) {
continue;
}
for result in box_iter(child.body) {
let Ok((grandchild, _)) = result else {
return None;
};
let t = grandchild.header.box_type;
if t.is(&BOX_SENC) || t.is(&BOX_SAIZ) || t.is(&BOX_SAIO) {
return Some(true);
}
if t.is(&BOX_UUID)
&& grandchild.header.usertype.as_ref() == Some(&PIFF_SAMPLE_ENCRYPTION_UUID)
{
return Some(true);
}
}
}
Some(false)
}
pub fn fragment_track_id(fragment: &[u8]) -> Result<u32> {
let mut offset = 0usize;
while offset + BOX_HEADER_LEN <= fragment.len() {
let (bx, consumed) = parse_box(&fragment[offset..])?;
if bx.header.box_type.is(&BOX_MOOF) {
let moof = MovieFragmentBox::parse_body(bx.body)?;
let traf = moof
.traf
.first()
.ok_or(Error::UnexpectedBox { expected: "traf" })?;
return Ok(traf.tfhd.track_id.value());
}
if consumed == 0 {
break;
}
offset += consumed;
}
Err(Error::UnexpectedBox { expected: "moof" })
}
#[must_use]
pub fn fragment_tfxd(fragment: &[u8]) -> Option<TfxdBox> {
for result in box_iter(fragment) {
let (bx, _) = result.ok()?;
if !bx.header.box_type.is(&BOX_MOOF) {
continue;
}
for child in box_iter(bx.body) {
let (traf, _) = child.ok()?;
if !traf.header.box_type.is(&BOX_TRAF) {
continue;
}
for grandchild in box_iter(traf.body) {
let (g, _) = grandchild.ok()?;
if g.header.box_type.is(&BOX_UUID) && g.header.usertype.as_ref() == Some(&TFXD_UUID)
{
return TfxdBox::parse_body(g.body).ok();
}
}
return None;
}
return None;
}
None
}