extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
use crate::error::{Error, Result};
use crate::local_set::{LocalSet, StructuralSetKind};
use crate::sets::{
InterchangeObjectFields, LocalSetOwnedItem, collect_dark, finish_owned_set, get_optional_raw,
get_required_fixed, get_required_raw, owned_set_serialized_len, serialize_owned_set,
};
use crate::types::{MxfTimestamp, ProductVersion, UlBytes, decode_utf16_be, encode_utf16_be};
pub const TAG_THIS_GENERATION_UID: u16 = 0x3C09;
pub const TAG_COMPANY_NAME: u16 = 0x3C01;
pub const TAG_PRODUCT_NAME: u16 = 0x3C02;
pub const TAG_PRODUCT_VERSION: u16 = 0x3C03;
pub const TAG_VERSION_STRING: u16 = 0x3C04;
pub const TAG_PRODUCT_UID: u16 = 0x3C05;
pub const TAG_MODIFICATION_DATE: u16 = 0x3C06;
pub const TAG_TOOLKIT_VERSION: u16 = 0x3C07;
pub const TAG_PLATFORM: u16 = 0x3C08;
const KNOWN_TAGS: [u16; 12] = [
crate::sets::TAG_INSTANCE_UID,
crate::sets::TAG_GENERATION_UID,
crate::sets::TAG_OBJECT_CLASS,
TAG_THIS_GENERATION_UID,
TAG_COMPANY_NAME,
TAG_PRODUCT_NAME,
TAG_PRODUCT_VERSION,
TAG_VERSION_STRING,
TAG_PRODUCT_UID,
TAG_MODIFICATION_DATE,
TAG_TOOLKIT_VERSION,
TAG_PLATFORM,
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Identification {
pub interchange: InterchangeObjectFields,
pub this_generation_uid: UlBytes,
pub company_name: String,
pub product_name: String,
pub product_version: Option<ProductVersion>,
pub version_string: String,
pub product_uid: UlBytes,
pub modification_date: MxfTimestamp,
pub toolkit_version: Option<ProductVersion>,
pub platform: Option<String>,
pub dark: Vec<(u16, Vec<u8>)>,
}
impl<'a> Parse<'a> for Identification {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let set = LocalSet::parse(bytes)?;
if set.kind() != StructuralSetKind::Identification {
return Err(Error::KeyPrefixMismatch {
what: "Identification (Table 17)",
});
}
let items = &set.items;
let interchange = InterchangeObjectFields::decode(items, "Identification")?;
let this_generation_uid = get_required_fixed::<16>(
items,
TAG_THIS_GENERATION_UID,
"This Generation UID",
"Identification",
)?;
let company_name = decode_utf16_be(get_required_raw(
items,
TAG_COMPANY_NAME,
"Company Name",
"Identification",
)?)
.map_err(|_| Error::InvalidUtf16 {
tag: TAG_COMPANY_NAME,
name: "Company Name",
})?;
let product_name = decode_utf16_be(get_required_raw(
items,
TAG_PRODUCT_NAME,
"Product Name",
"Identification",
)?)
.map_err(|_| Error::InvalidUtf16 {
tag: TAG_PRODUCT_NAME,
name: "Product Name",
})?;
let product_version = get_optional_raw(items, TAG_PRODUCT_VERSION)
.map(ProductVersion::parse)
.transpose()?;
let version_string = decode_utf16_be(get_required_raw(
items,
TAG_VERSION_STRING,
"Version String",
"Identification",
)?)
.map_err(|_| Error::InvalidUtf16 {
tag: TAG_VERSION_STRING,
name: "Version String",
})?;
let product_uid =
get_required_fixed::<16>(items, TAG_PRODUCT_UID, "Product UID", "Identification")?;
let modification_date = MxfTimestamp::parse(get_required_raw(
items,
TAG_MODIFICATION_DATE,
"Modification Date",
"Identification",
)?)?;
let toolkit_version = get_optional_raw(items, TAG_TOOLKIT_VERSION)
.map(ProductVersion::parse)
.transpose()?;
let platform = get_optional_raw(items, TAG_PLATFORM)
.map(decode_utf16_be)
.transpose()
.map_err(|_| Error::InvalidUtf16 {
tag: TAG_PLATFORM,
name: "Platform",
})?;
let dark = collect_dark(items, &KNOWN_TAGS);
Ok(Identification {
interchange,
this_generation_uid,
company_name,
product_name,
product_version,
version_string,
product_uid,
modification_date,
toolkit_version,
platform,
dark,
})
}
}
impl Identification {
fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
let mut out = Vec::new();
self.interchange.encode_into(&mut out);
out.push(LocalSetOwnedItem::fixed(
TAG_THIS_GENERATION_UID,
self.this_generation_uid,
));
out.push(LocalSetOwnedItem::owned(
TAG_COMPANY_NAME,
encode_utf16_be(&self.company_name),
));
out.push(LocalSetOwnedItem::owned(
TAG_PRODUCT_NAME,
encode_utf16_be(&self.product_name),
));
if let Some(pv) = self.product_version {
let mut buf = [0u8; crate::types::PRODUCT_VERSION_LEN];
pv.serialize_into(&mut buf).expect("fixed-size buffer");
out.push(LocalSetOwnedItem::owned(TAG_PRODUCT_VERSION, buf.to_vec()));
}
out.push(LocalSetOwnedItem::owned(
TAG_VERSION_STRING,
encode_utf16_be(&self.version_string),
));
out.push(LocalSetOwnedItem::fixed(TAG_PRODUCT_UID, self.product_uid));
{
let mut buf = [0u8; crate::types::TIMESTAMP_LEN];
self.modification_date
.serialize_into(&mut buf)
.expect("fixed-size buffer");
out.push(LocalSetOwnedItem::owned(
TAG_MODIFICATION_DATE,
buf.to_vec(),
));
}
if let Some(tv) = self.toolkit_version {
let mut buf = [0u8; crate::types::PRODUCT_VERSION_LEN];
tv.serialize_into(&mut buf).expect("fixed-size buffer");
out.push(LocalSetOwnedItem::owned(TAG_TOOLKIT_VERSION, buf.to_vec()));
}
if let Some(platform) = &self.platform {
out.push(LocalSetOwnedItem::owned(
TAG_PLATFORM,
encode_utf16_be(platform),
));
}
out
}
}
impl Serialize for Identification {
type Error = Error;
fn serialized_len(&self) -> usize {
let (key, items) = finish_owned_set(
StructuralSetKind::Identification,
self.owned_items(),
&self.dark,
);
owned_set_serialized_len(key, &items)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let (key, items) = finish_owned_set(
StructuralSetKind::Identification,
self.owned_items(),
&self.dark,
);
serialize_owned_set(key, &items, buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::ReleaseType;
fn sample() -> Identification {
Identification {
interchange: InterchangeObjectFields {
instance_uid: [0x11; 16],
generation_uid: None,
object_class: None,
},
this_generation_uid: [0x22; 16],
company_name: String::from("Acme Broadcast"),
product_name: String::from("st377-1"),
product_version: Some(ProductVersion {
major: 0,
minor: 1,
tertiary: 0,
patch: 0,
release: ReleaseType::Development,
}),
version_string: String::from("0.1.0-dev"),
product_uid: [0x33; 16],
modification_date: MxfTimestamp {
year: 2019,
month: 11,
day: 28,
hour: 9,
minute: 30,
second: 0,
msec_div4: 0,
},
toolkit_version: None,
platform: Some(String::from("rustc (linux)")),
dark: Vec::new(),
}
}
#[test]
fn construct_serialize_parse_round_trip() {
let id = sample();
let bytes = id.to_bytes();
let parsed = Identification::parse(&bytes).unwrap();
assert_eq!(parsed, id);
assert_eq!(parsed.to_bytes(), bytes);
}
#[test]
fn mutation_changes_serialized_bytes() {
let mut id = sample();
let before = id.to_bytes();
id.company_name = String::from("Changed Inc.");
let after = id.to_bytes();
assert_ne!(before, after);
assert_eq!(
Identification::parse(&after).unwrap().company_name,
"Changed Inc."
);
}
}