use std::fmt::Write as _;
use crate::boxes::BoxRegistry;
use crate::codec::{
CodecBox, FieldHooks, FieldTable, FieldValue, FieldValueError, FieldValueRead, FieldValueWrite,
ImmutableBox, MutableBox,
};
use crate::{FourCc, codec_field};
pub const MARLIN_BRAND_MGSV: FourCc = FourCc::from_bytes(*b"MGSV");
pub const MARLIN_IPMPS_TYPE_MGSV: u16 = 0xA551;
pub const PROTECTION_SCHEME_TYPE_MARLIN_ACBC: FourCc = FourCc::from_bytes(*b"ACBC");
pub const PROTECTION_SCHEME_TYPE_MARLIN_ACGK: FourCc = FourCc::from_bytes(*b"ACGK");
pub const MARLIN_STYP_AUDIO: &str = "urn:marlin:organization:sne:content-type:audio";
pub const MARLIN_STYP_VIDEO: &str = "urn:marlin:organization:sne:content-type:video";
fn missing_field(field_name: &'static str) -> FieldValueError {
FieldValueError::MissingField { field_name }
}
fn unexpected_field(field_name: &'static str, value: FieldValue) -> FieldValueError {
FieldValueError::UnexpectedType {
field_name,
expected: "matching codec field value",
actual: value.kind_name(),
}
}
fn invalid_value(field_name: &'static str, reason: &'static str) -> FieldValueError {
FieldValueError::InvalidValue { field_name, reason }
}
fn render_hex_bytes(bytes: &[u8]) -> String {
let mut rendered = String::from("[");
let mut first = true;
for byte in bytes {
if !first {
rendered.push_str(", ");
}
first = false;
let _ = write!(&mut rendered, "0x{:x}", byte);
}
rendered.push(']');
rendered
}
macro_rules! impl_leaf_box {
($name:ident, $box_type:expr) => {
impl ImmutableBox for $name {
fn box_type(&self) -> FourCc {
FourCc::from_bytes($box_type)
}
}
impl MutableBox for $name {}
};
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Satr;
impl_leaf_box!(Satr, *b"satr");
impl FieldHooks for Satr {}
impl FieldValueRead for Satr {
fn field_value(&self, field_name: &'static str) -> Result<FieldValue, FieldValueError> {
Err(missing_field(field_name))
}
}
impl FieldValueWrite for Satr {
fn set_field_value(
&mut self,
field_name: &'static str,
value: FieldValue,
) -> Result<(), FieldValueError> {
Err(unexpected_field(field_name, value))
}
}
impl CodecBox for Satr {
const FIELD_TABLE: FieldTable = FieldTable::new(&[]);
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Hmac {
pub data: Vec<u8>,
}
impl_leaf_box!(Hmac, *b"hmac");
impl FieldHooks for Hmac {
fn display_field(&self, name: &'static str) -> Option<String> {
(name == "Data").then(|| render_hex_bytes(&self.data))
}
}
impl FieldValueRead for Hmac {
fn field_value(&self, field_name: &'static str) -> Result<FieldValue, FieldValueError> {
match field_name {
"Data" => Ok(FieldValue::Bytes(self.data.clone())),
_ => Err(missing_field(field_name)),
}
}
}
impl FieldValueWrite for Hmac {
fn set_field_value(
&mut self,
field_name: &'static str,
value: FieldValue,
) -> Result<(), FieldValueError> {
match (field_name, value) {
("Data", FieldValue::Bytes(value)) => {
self.data = value;
Ok(())
}
(field_name, value) => Err(unexpected_field(field_name, value)),
}
}
}
impl CodecBox for Hmac {
const FIELD_TABLE: FieldTable =
FieldTable::new(&[codec_field!("Data", 0, with_bit_width(8), as_bytes())]);
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Gkey {
pub data: Vec<u8>,
}
impl_leaf_box!(Gkey, *b"gkey");
impl FieldHooks for Gkey {
fn display_field(&self, name: &'static str) -> Option<String> {
(name == "Data").then(|| render_hex_bytes(&self.data))
}
}
impl FieldValueRead for Gkey {
fn field_value(&self, field_name: &'static str) -> Result<FieldValue, FieldValueError> {
match field_name {
"Data" => Ok(FieldValue::Bytes(self.data.clone())),
_ => Err(missing_field(field_name)),
}
}
}
impl FieldValueWrite for Gkey {
fn set_field_value(
&mut self,
field_name: &'static str,
value: FieldValue,
) -> Result<(), FieldValueError> {
match (field_name, value) {
("Data", FieldValue::Bytes(value)) => {
self.data = value;
Ok(())
}
(field_name, value) => Err(unexpected_field(field_name, value)),
}
}
}
impl CodecBox for Gkey {
const FIELD_TABLE: FieldTable =
FieldTable::new(&[codec_field!("Data", 0, with_bit_width(8), as_bytes())]);
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MarlinStyp {
pub value: String,
}
impl MarlinStyp {
pub fn parse_payload(bytes: &[u8]) -> Result<Self, FieldValueError> {
if bytes.is_empty() {
return Ok(Self::default());
}
let mut forced_nul = bytes.to_vec();
*forced_nul.last_mut().unwrap() = 0;
let string_end = forced_nul.iter().position(|byte| *byte == 0).unwrap();
let value = String::from_utf8(forced_nul[..string_end].to_vec())
.map_err(|_| invalid_value("Value", "value is not valid UTF-8"))?;
Ok(Self { value })
}
pub fn encode_payload(&self) -> Result<Vec<u8>, FieldValueError> {
if self.value.as_bytes().contains(&0) {
return Err(invalid_value("Value", "string contains an embedded NUL"));
}
let mut payload = self.value.as_bytes().to_vec();
payload.push(0);
Ok(payload)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MarlinShortSchm {
pub scheme_type: FourCc,
pub scheme_version: u16,
}
impl Default for MarlinShortSchm {
fn default() -> Self {
Self {
scheme_type: FourCc::from_bytes(*b"\0\0\0\0"),
scheme_version: 0,
}
}
}
impl MarlinShortSchm {
pub fn parse_payload(bytes: &[u8]) -> Result<Self, FieldValueError> {
if bytes.len() != 6 {
return Err(invalid_value(
"Payload",
"expected a 6-byte Marlin short-form schm payload",
));
}
Ok(Self {
scheme_type: FourCc::from_bytes(bytes[..4].try_into().unwrap()),
scheme_version: u16::from_be_bytes(bytes[4..6].try_into().unwrap()),
})
}
pub fn encode_payload(&self) -> [u8; 6] {
let mut payload = [0_u8; 6];
payload[..4].copy_from_slice(self.scheme_type.as_bytes());
payload[4..6].copy_from_slice(&self.scheme_version.to_be_bytes());
payload
}
pub fn uses_track_key(&self) -> bool {
self.scheme_type == PROTECTION_SCHEME_TYPE_MARLIN_ACBC && self.scheme_version == 0x0100
}
pub fn uses_group_key(&self) -> bool {
self.scheme_type == PROTECTION_SCHEME_TYPE_MARLIN_ACGK && self.scheme_version == 0x0100
}
}
pub fn register_boxes(registry: &mut BoxRegistry) {
registry.register::<Satr>(FourCc::from_bytes(*b"satr"));
registry.register::<Hmac>(FourCc::from_bytes(*b"hmac"));
registry.register::<Gkey>(FourCc::from_bytes(*b"gkey"));
}