use std::collections::HashMap;
use oxideav_core::{Error, Packet, Result, StreamInfo, WriteSeek};
use crate::cenc::{
CencScheme, CencSchemeDecision, IvSupply, SeigEntry, SencSample, SubsampleEntry, TencBox,
};
use crate::cenc_cipher::encrypt_sample_in_place;
use crate::frag::FragmentedMuxer;
use crate::options::{FragmentedOptions, Mp4MuxerOptions};
#[derive(Clone)]
pub struct TrackKey {
pub stream_index: usize,
pub key: [u8; 16],
}
struct TrackCrypto {
scheme: CencScheme,
tenc: TencBox,
keys: HashMap<[u8; 16], [u8; 16]>,
active_override: Option<SeigEntry>,
iv_counter: u64,
}
impl TrackCrypto {
fn effective_tenc(&self) -> TencBox {
match &self.active_override {
None => self.tenc.clone(),
Some(seig) => TencBox {
version: self.tenc.version,
default_is_protected: seig.is_protected,
default_per_sample_iv_size: seig.per_sample_iv_size,
default_kid: seig.kid,
default_crypt_byte_block: seig.crypt_byte_block,
default_skip_byte_block: seig.skip_byte_block,
default_constant_iv: seig.constant_iv.clone(),
},
}
}
fn next_iv(&mut self, size: u8) -> Vec<u8> {
let mut iv = vec![0u8; size as usize];
iv[..8].copy_from_slice(&self.iv_counter.to_be_bytes());
self.iv_counter += 1;
iv
}
}
pub struct CencFragmentPackager {
muxer: FragmentedMuxer,
tracks: Vec<Option<TrackCrypto>>,
}
impl CencFragmentPackager {
pub fn new(
output: Box<dyn WriteSeek>,
streams: &[StreamInfo],
options: Mp4MuxerOptions,
frag_options: FragmentedOptions,
keys: impl IntoIterator<Item = TrackKey>,
) -> Result<CencFragmentPackager> {
let mut tracks: Vec<Option<TrackCrypto>> = Vec::with_capacity(streams.len());
for i in 0..streams.len() {
tracks.push(
options
.track_protection
.iter()
.find(|p| p.stream_index == i)
.map(|p| TrackCrypto {
scheme: CencScheme::from_fourcc(&p.scheme_type),
tenc: p.tenc.clone(),
keys: HashMap::new(),
active_override: None,
iv_counter: 1,
}),
);
}
for tk in keys {
let slot = tracks
.get_mut(tk.stream_index)
.and_then(|t| t.as_mut())
.ok_or_else(|| {
Error::invalid(format!(
"CENC packager: key for stream {} which has no track_protection \
directive",
tk.stream_index
))
})?;
slot.keys.insert(slot.tenc.default_kid, tk.key);
}
for (i, t) in tracks.iter().enumerate() {
if let Some(t) = t {
if t.keys.is_empty() {
return Err(Error::invalid(format!(
"CENC packager: protected stream {i} has no content key"
)));
}
}
}
let muxer = crate::frag::open_fragmented_typed(output, streams, options, frag_options)?;
Ok(CencFragmentPackager { muxer, tracks })
}
pub fn write_header(&mut self) -> Result<()> {
use oxideav_core::Muxer;
self.muxer.write_header()
}
pub fn write_trailer(&mut self) -> Result<()> {
use oxideav_core::Muxer;
self.muxer.write_trailer()
}
pub fn write_packet(&mut self, packet: &Packet) -> Result<()> {
self.write_packet_inner(packet, None)
}
pub fn write_packet_with_subsamples(
&mut self,
packet: &Packet,
subsamples: &[SubsampleEntry],
) -> Result<()> {
self.write_packet_inner(packet, Some(subsamples))
}
fn write_packet_inner(
&mut self,
packet: &Packet,
subsamples: Option<&[SubsampleEntry]>,
) -> Result<()> {
use oxideav_core::Muxer;
let idx = packet.stream_index as usize;
let crypto = match self.tracks.get_mut(idx) {
Some(Some(c)) => c,
Some(None) => {
if subsamples.is_some() {
return Err(Error::invalid(format!(
"CENC packager: subsample map on unprotected stream {idx}"
)));
}
return self.muxer.write_packet(packet);
}
None => {
return Err(Error::invalid(format!(
"CENC packager: unknown stream index {idx}"
)))
}
};
let effective = crypto.effective_tenc();
let decision = CencSchemeDecision::new(crypto.scheme, effective.clone())?;
let kid = effective.default_kid;
let key = *crypto.keys.get(&kid).ok_or_else(|| {
Error::invalid(format!(
"CENC packager: no content key for the active KID on stream {idx}"
))
})?;
let iv: Vec<u8> = match decision.iv_supply() {
IvSupply::PerSample { size } => crypto.next_iv(size),
IvSupply::Constant => Vec::new(),
IvSupply::None => {
return Err(Error::invalid(format!(
"CENC packager: stream {idx} effective parameters are unprotected \
(isProtected == 0) — route clear samples through the muxer directly"
)))
}
};
let mut data = packet.data.clone();
encrypt_sample_in_place(
&decision,
&key,
if iv.is_empty() { None } else { Some(&iv) },
subsamples,
&mut data,
)?;
let senc = SencSample {
initialization_vector: iv,
subsamples: subsamples.map(<[_]>::to_vec).unwrap_or_default(),
};
let seig = crypto.active_override.clone();
let mut out = packet.clone();
out.data = data;
self.muxer.write_protected_packet_grouped(&out, senc, seig)
}
pub fn rotate_key(
&mut self,
stream_index: usize,
kid: [u8; 16],
key: [u8; 16],
constant_iv: Option<Vec<u8>>,
) -> Result<()> {
let crypto = self
.tracks
.get_mut(stream_index)
.and_then(|t| t.as_mut())
.ok_or_else(|| {
Error::invalid(format!(
"CENC packager: rotate_key on stream {stream_index} which has no \
track_protection directive"
))
})?;
let uses_constant_iv =
crypto.tenc.default_is_protected == 1 && crypto.tenc.default_per_sample_iv_size == 0;
if uses_constant_iv != constant_iv.is_some() {
return Err(Error::invalid(if uses_constant_iv {
"CENC packager: rotate_key on a constant-IV track requires a new constant IV"
} else {
"CENC packager: rotate_key constant IV supplied but the track uses \
per-sample IVs"
}));
}
crypto.keys.insert(kid, key);
if kid == crypto.tenc.default_kid
&& constant_iv.as_ref() == crypto.tenc.default_constant_iv.as_ref()
{
crypto.active_override = None;
return Ok(());
}
let seig = SeigEntry {
crypt_byte_block: crypto.tenc.default_crypt_byte_block,
skip_byte_block: crypto.tenc.default_skip_byte_block,
is_protected: 1,
per_sample_iv_size: crypto.tenc.default_per_sample_iv_size,
kid,
constant_iv,
};
crate::cenc::build_seig_entry(&seig)?;
crypto.active_override = Some(seig);
Ok(())
}
pub fn reset_to_default_key(&mut self, stream_index: usize) -> Result<()> {
let crypto = self
.tracks
.get_mut(stream_index)
.and_then(|t| t.as_mut())
.ok_or_else(|| {
Error::invalid(format!(
"CENC packager: reset_to_default_key on stream {stream_index} which has \
no track_protection directive"
))
})?;
crypto.active_override = None;
Ok(())
}
pub fn set_next_segment_pssh(&mut self, pssh: impl IntoIterator<Item = crate::cenc::PsshBox>) {
self.muxer.set_next_segment_pssh(pssh)
}
}