Skip to main content

st377_1/
identification.rs

1//! Identification — SMPTE ST 377-1:2019 Annex A.3 (`docs/st377-1.md`):
2//! records the application/device that created or last modified the file.
3
4extern crate alloc;
5
6use alloc::string::String;
7use alloc::vec::Vec;
8
9use broadcast_common::{Parse, Serialize};
10
11use crate::error::{Error, Result};
12use crate::local_set::{LocalSet, StructuralSetKind};
13use crate::sets::{
14    InterchangeObjectFields, LocalSetOwnedItem, collect_dark, finish_owned_set, get_optional_raw,
15    get_required_fixed, get_required_raw, owned_set_serialized_len, serialize_owned_set,
16};
17use crate::types::{MxfTimestamp, ProductVersion, UlBytes, decode_utf16_be, encode_utf16_be};
18
19/// Local tag: This Generation UID (A.3).
20pub const TAG_THIS_GENERATION_UID: u16 = 0x3C09;
21/// Local tag: Company Name (A.3).
22pub const TAG_COMPANY_NAME: u16 = 0x3C01;
23/// Local tag: Product Name (A.3).
24pub const TAG_PRODUCT_NAME: u16 = 0x3C02;
25/// Local tag: Product Version (A.3).
26pub const TAG_PRODUCT_VERSION: u16 = 0x3C03;
27/// Local tag: Version String (A.3).
28pub const TAG_VERSION_STRING: u16 = 0x3C04;
29/// Local tag: Product UID (A.3).
30pub const TAG_PRODUCT_UID: u16 = 0x3C05;
31/// Local tag: Modification Date (A.3).
32pub const TAG_MODIFICATION_DATE: u16 = 0x3C06;
33/// Local tag: Toolkit Version (A.3).
34pub const TAG_TOOLKIT_VERSION: u16 = 0x3C07;
35/// Local tag: Platform (A.3).
36pub const TAG_PLATFORM: u16 = 0x3C08;
37
38const KNOWN_TAGS: [u16; 12] = [
39    crate::sets::TAG_INSTANCE_UID,
40    crate::sets::TAG_GENERATION_UID,
41    crate::sets::TAG_OBJECT_CLASS,
42    TAG_THIS_GENERATION_UID,
43    TAG_COMPANY_NAME,
44    TAG_PRODUCT_NAME,
45    TAG_PRODUCT_VERSION,
46    TAG_VERSION_STRING,
47    TAG_PRODUCT_UID,
48    TAG_MODIFICATION_DATE,
49    TAG_TOOLKIT_VERSION,
50    TAG_PLATFORM,
51];
52
53/// The Identification Set — SMPTE ST 377-1:2019 Annex A.3: one instance per
54/// application/device that has created or modified the file (§7.5.2), each
55/// referenced from the Preface's `Identifications` array.
56///
57/// Per A.3's closing note, the Interchange Object's optional Generation UID
58/// property "shall not be encoded in Identification Set instances" — this
59/// crate does not enforce that at parse time (a non-conformant file is
60/// still identified, not rejected); callers constructing a fresh
61/// `Identification` should simply leave `interchange.generation_uid` as
62/// `None`.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct Identification {
65    /// Interchange Object (A.1) base properties.
66    pub interchange: InterchangeObjectFields,
67    /// This Generation UID (`0x3C09`, Req) — referenced by other Sets'
68    /// optional `Generation UID` property (§7.5.2).
69    pub this_generation_uid: UlBytes,
70    /// Company Name (`0x3C01`, Req).
71    pub company_name: String,
72    /// Product Name (`0x3C02`, Req).
73    pub product_name: String,
74    /// Product Version (`0x3C03`, Opt).
75    pub product_version: Option<ProductVersion>,
76    /// Version String (`0x3C04`, Req).
77    pub version_string: String,
78    /// Product UID (`0x3C05`, Req).
79    pub product_uid: UlBytes,
80    /// Modification Date (`0x3C06`, Req).
81    pub modification_date: MxfTimestamp,
82    /// Toolkit Version (`0x3C07`, Opt).
83    pub toolkit_version: Option<ProductVersion>,
84    /// Platform (`0x3C08`, Opt).
85    pub platform: Option<String>,
86    /// Every other property found on parse (private/dark extension).
87    pub dark: Vec<(u16, Vec<u8>)>,
88}
89
90impl<'a> Parse<'a> for Identification {
91    type Error = Error;
92
93    fn parse(bytes: &'a [u8]) -> Result<Self> {
94        let set = LocalSet::parse(bytes)?;
95        if set.kind() != StructuralSetKind::Identification {
96            return Err(Error::KeyPrefixMismatch {
97                what: "Identification (Table 17)",
98            });
99        }
100        let items = &set.items;
101        let interchange = InterchangeObjectFields::decode(items, "Identification")?;
102        let this_generation_uid = get_required_fixed::<16>(
103            items,
104            TAG_THIS_GENERATION_UID,
105            "This Generation UID",
106            "Identification",
107        )?;
108        let company_name = decode_utf16_be(get_required_raw(
109            items,
110            TAG_COMPANY_NAME,
111            "Company Name",
112            "Identification",
113        )?)
114        .map_err(|_| Error::InvalidUtf16 {
115            tag: TAG_COMPANY_NAME,
116            name: "Company Name",
117        })?;
118        let product_name = decode_utf16_be(get_required_raw(
119            items,
120            TAG_PRODUCT_NAME,
121            "Product Name",
122            "Identification",
123        )?)
124        .map_err(|_| Error::InvalidUtf16 {
125            tag: TAG_PRODUCT_NAME,
126            name: "Product Name",
127        })?;
128        let product_version = get_optional_raw(items, TAG_PRODUCT_VERSION)
129            .map(ProductVersion::parse)
130            .transpose()?;
131        let version_string = decode_utf16_be(get_required_raw(
132            items,
133            TAG_VERSION_STRING,
134            "Version String",
135            "Identification",
136        )?)
137        .map_err(|_| Error::InvalidUtf16 {
138            tag: TAG_VERSION_STRING,
139            name: "Version String",
140        })?;
141        let product_uid =
142            get_required_fixed::<16>(items, TAG_PRODUCT_UID, "Product UID", "Identification")?;
143        let modification_date = MxfTimestamp::parse(get_required_raw(
144            items,
145            TAG_MODIFICATION_DATE,
146            "Modification Date",
147            "Identification",
148        )?)?;
149        let toolkit_version = get_optional_raw(items, TAG_TOOLKIT_VERSION)
150            .map(ProductVersion::parse)
151            .transpose()?;
152        let platform = get_optional_raw(items, TAG_PLATFORM)
153            .map(decode_utf16_be)
154            .transpose()
155            .map_err(|_| Error::InvalidUtf16 {
156                tag: TAG_PLATFORM,
157                name: "Platform",
158            })?;
159        let dark = collect_dark(items, &KNOWN_TAGS);
160
161        Ok(Identification {
162            interchange,
163            this_generation_uid,
164            company_name,
165            product_name,
166            product_version,
167            version_string,
168            product_uid,
169            modification_date,
170            toolkit_version,
171            platform,
172            dark,
173        })
174    }
175}
176
177impl Identification {
178    fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
179        let mut out = Vec::new();
180        self.interchange.encode_into(&mut out);
181        out.push(LocalSetOwnedItem::fixed(
182            TAG_THIS_GENERATION_UID,
183            self.this_generation_uid,
184        ));
185        out.push(LocalSetOwnedItem::owned(
186            TAG_COMPANY_NAME,
187            encode_utf16_be(&self.company_name),
188        ));
189        out.push(LocalSetOwnedItem::owned(
190            TAG_PRODUCT_NAME,
191            encode_utf16_be(&self.product_name),
192        ));
193        if let Some(pv) = self.product_version {
194            let mut buf = [0u8; crate::types::PRODUCT_VERSION_LEN];
195            pv.serialize_into(&mut buf).expect("fixed-size buffer");
196            out.push(LocalSetOwnedItem::owned(TAG_PRODUCT_VERSION, buf.to_vec()));
197        }
198        out.push(LocalSetOwnedItem::owned(
199            TAG_VERSION_STRING,
200            encode_utf16_be(&self.version_string),
201        ));
202        out.push(LocalSetOwnedItem::fixed(TAG_PRODUCT_UID, self.product_uid));
203        {
204            let mut buf = [0u8; crate::types::TIMESTAMP_LEN];
205            self.modification_date
206                .serialize_into(&mut buf)
207                .expect("fixed-size buffer");
208            out.push(LocalSetOwnedItem::owned(
209                TAG_MODIFICATION_DATE,
210                buf.to_vec(),
211            ));
212        }
213        if let Some(tv) = self.toolkit_version {
214            let mut buf = [0u8; crate::types::PRODUCT_VERSION_LEN];
215            tv.serialize_into(&mut buf).expect("fixed-size buffer");
216            out.push(LocalSetOwnedItem::owned(TAG_TOOLKIT_VERSION, buf.to_vec()));
217        }
218        if let Some(platform) = &self.platform {
219            out.push(LocalSetOwnedItem::owned(
220                TAG_PLATFORM,
221                encode_utf16_be(platform),
222            ));
223        }
224        out
225    }
226}
227
228impl Serialize for Identification {
229    type Error = Error;
230
231    fn serialized_len(&self) -> usize {
232        let (key, items) = finish_owned_set(
233            StructuralSetKind::Identification,
234            self.owned_items(),
235            &self.dark,
236        );
237        owned_set_serialized_len(key, &items)
238    }
239
240    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
241        let (key, items) = finish_owned_set(
242            StructuralSetKind::Identification,
243            self.owned_items(),
244            &self.dark,
245        );
246        serialize_owned_set(key, &items, buf)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::types::ReleaseType;
254
255    fn sample() -> Identification {
256        Identification {
257            interchange: InterchangeObjectFields {
258                instance_uid: [0x11; 16],
259                generation_uid: None,
260                object_class: None,
261            },
262            this_generation_uid: [0x22; 16],
263            company_name: String::from("Acme Broadcast"),
264            product_name: String::from("st377-1"),
265            product_version: Some(ProductVersion {
266                major: 0,
267                minor: 1,
268                tertiary: 0,
269                patch: 0,
270                release: ReleaseType::Development,
271            }),
272            version_string: String::from("0.1.0-dev"),
273            product_uid: [0x33; 16],
274            modification_date: MxfTimestamp {
275                year: 2019,
276                month: 11,
277                day: 28,
278                hour: 9,
279                minute: 30,
280                second: 0,
281                msec_div4: 0,
282            },
283            toolkit_version: None,
284            platform: Some(String::from("rustc (linux)")),
285            dark: Vec::new(),
286        }
287    }
288
289    #[test]
290    fn construct_serialize_parse_round_trip() {
291        let id = sample();
292        let bytes = id.to_bytes();
293        let parsed = Identification::parse(&bytes).unwrap();
294        assert_eq!(parsed, id);
295        assert_eq!(parsed.to_bytes(), bytes);
296    }
297
298    #[test]
299    fn mutation_changes_serialized_bytes() {
300        let mut id = sample();
301        let before = id.to_bytes();
302        id.company_name = String::from("Changed Inc.");
303        let after = id.to_bytes();
304        assert_ne!(before, after);
305        assert_eq!(
306            Identification::parse(&after).unwrap().company_name,
307            "Changed Inc."
308        );
309    }
310}