Skip to main content

st377_1/
preface.rs

1//! Preface — SMPTE ST 377-1:2019 Annex A.2 (`docs/st377-1.md`): the file's
2//! root Header Metadata Set, always the first Set after the Primer Pack
3//! (§9.5.1).
4
5extern crate alloc;
6
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_fixed,
15    get_optional_raw, get_required_fixed, get_required_raw, owned_set_serialized_len,
16    serialize_owned_set,
17};
18use crate::types::{MxfTimestamp, UlBytes, parse_uid_batch, serialize_uid_batch};
19
20/// Local tag: Last Modified Date (A.2).
21pub const TAG_LAST_MODIFIED_DATE: u16 = 0x3B02;
22/// Local tag: Version (A.2).
23pub const TAG_VERSION: u16 = 0x3B05;
24/// Local tag: Object Model Version (A.2).
25pub const TAG_OBJECT_MODEL_VERSION: u16 = 0x3B07;
26/// Local tag: Primary Package (A.2).
27pub const TAG_PRIMARY_PACKAGE: u16 = 0x3B08;
28/// Local tag: Identifications (A.2).
29pub const TAG_IDENTIFICATIONS: u16 = 0x3B06;
30/// Local tag: Content Storage (A.2).
31pub const TAG_CONTENT_STORAGE: u16 = 0x3B03;
32/// Local tag: Operational Pattern (A.2).
33pub const TAG_OPERATIONAL_PATTERN: u16 = 0x3B09;
34/// Local tag: EssenceContainers (A.2).
35pub const TAG_ESSENCE_CONTAINERS: u16 = 0x3B0A;
36/// Local tag: DM Schemes (A.2).
37pub const TAG_DM_SCHEMES: u16 = 0x3B0B;
38
39const KNOWN_TAGS: [u16; 12] = [
40    crate::sets::TAG_INSTANCE_UID,
41    crate::sets::TAG_GENERATION_UID,
42    crate::sets::TAG_OBJECT_CLASS,
43    TAG_LAST_MODIFIED_DATE,
44    TAG_VERSION,
45    TAG_OBJECT_MODEL_VERSION,
46    TAG_PRIMARY_PACKAGE,
47    TAG_IDENTIFICATIONS,
48    TAG_CONTENT_STORAGE,
49    TAG_OPERATIONAL_PATTERN,
50    TAG_ESSENCE_CONTAINERS,
51    TAG_DM_SCHEMES,
52];
53
54/// This revision's fixed `Version` property value (`0x0103` = v1.3, A.2).
55pub const VERSION_1_3: u16 = 0x0103;
56
57/// The Preface Set — SMPTE ST 377-1:2019 Annex A.2: the file's overall
58/// metadata (modification time, Operational Pattern, Essence Container /
59/// Descriptive Metadata scheme inventory) and strong references to the
60/// `Identification` history and the `ContentStorage` Set.
61///
62/// Properties marked "dyn" in Annex A.2 (`ApplicationSchemes`,
63/// `IsRIPPresent` — no fixed static local tag; a real Primer Pack must be
64/// consulted to learn which tag a given file used) are preserved
65/// byte-for-byte in `dark` rather than individually typed in this first
66/// pass — see `docs/st377-1.md`'s Scope section.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct Preface {
69    /// Interchange Object (A.1) base properties.
70    pub interchange: InterchangeObjectFields,
71    /// Last Modified Date (`0x3B02`, Req).
72    pub last_modified_date: MxfTimestamp,
73    /// Version (`0x3B05`, Req) — `major*256 + minor`; [`VERSION_1_3`] for
74    /// this revision.
75    pub version: u16,
76    /// Object Model Version (`0x3B07`, Opt).
77    pub object_model_version: Option<u32>,
78    /// Primary Package (`0x3B08`, Opt) — weak reference (Instance UID) to
79    /// this file's primary Material Package, if any.
80    pub primary_package: Option<UlBytes>,
81    /// Identifications (`0x3B06`, **E/req** — encoder-required, but a
82    /// decoder must not fail if it's absent, per Annex A.2's "Req?" column)
83    /// — strong references to every `Identification` Set recording a
84    /// modification to this file. Empty if the property was absent on
85    /// parse.
86    pub identifications: Vec<UlBytes>,
87    /// Content Storage (`0x3B03`, Req) — strong reference to the
88    /// `ContentStorage` Set.
89    pub content_storage: UlBytes,
90    /// Operational Pattern (`0x3B09`, Req) — copy of the Partition Pack's
91    /// own value.
92    pub operational_pattern: UlBytes,
93    /// EssenceContainers (`0x3B0A`, Req) — Batch of Essence Container ULs
94    /// used in/referenced by this file.
95    pub essence_containers: Vec<UlBytes>,
96    /// DM Schemes (`0x3B0B`, Req) — Batch of Descriptive Metadata Scheme
97    /// ULs used in this file.
98    pub dm_schemes: Vec<UlBytes>,
99    /// Every other property found on parse, including any "dyn"-tagged
100    /// optional property (`ApplicationSchemes`/`IsRIPPresent`) and any
101    /// private/dark extension — preserved byte-for-byte, not decoded.
102    pub dark: Vec<(u16, Vec<u8>)>,
103}
104
105impl<'a> Parse<'a> for Preface {
106    type Error = Error;
107
108    fn parse(bytes: &'a [u8]) -> Result<Self> {
109        let set = LocalSet::parse(bytes)?;
110        if set.kind() != StructuralSetKind::Preface {
111            return Err(Error::KeyPrefixMismatch {
112                what: "Preface (Table 17)",
113            });
114        }
115        let items = &set.items;
116        let interchange = InterchangeObjectFields::decode(items, "Preface")?;
117        let last_modified_date = MxfTimestamp::parse(get_required_raw(
118            items,
119            TAG_LAST_MODIFIED_DATE,
120            "Last Modified Date",
121            "Preface",
122        )?)?;
123        let version = u16::from_be_bytes(get_required_fixed::<2>(
124            items,
125            TAG_VERSION,
126            "Version",
127            "Preface",
128        )?);
129        let object_model_version =
130            get_optional_fixed::<4>(items, TAG_OBJECT_MODEL_VERSION, "Object Model Version")?
131                .map(u32::from_be_bytes);
132        let primary_package =
133            get_optional_fixed::<16>(items, TAG_PRIMARY_PACKAGE, "Primary Package")?;
134        let identifications = match get_optional_raw(items, TAG_IDENTIFICATIONS) {
135            Some(raw) => parse_uid_batch(raw)?,
136            None => Vec::new(),
137        };
138        let content_storage =
139            get_required_fixed::<16>(items, TAG_CONTENT_STORAGE, "Content Storage", "Preface")?;
140        let operational_pattern = get_required_fixed::<16>(
141            items,
142            TAG_OPERATIONAL_PATTERN,
143            "Operational Pattern",
144            "Preface",
145        )?;
146        let essence_containers = parse_uid_batch(get_required_raw(
147            items,
148            TAG_ESSENCE_CONTAINERS,
149            "EssenceContainers",
150            "Preface",
151        )?)?;
152        let dm_schemes = parse_uid_batch(get_required_raw(
153            items,
154            TAG_DM_SCHEMES,
155            "DM Schemes",
156            "Preface",
157        )?)?;
158        let dark = collect_dark(items, &KNOWN_TAGS);
159
160        Ok(Preface {
161            interchange,
162            last_modified_date,
163            version,
164            object_model_version,
165            primary_package,
166            identifications,
167            content_storage,
168            operational_pattern,
169            essence_containers,
170            dm_schemes,
171            dark,
172        })
173    }
174}
175
176impl Preface {
177    fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
178        let mut out = Vec::new();
179        self.interchange.encode_into(&mut out);
180        {
181            let mut buf = [0u8; crate::types::TIMESTAMP_LEN];
182            self.last_modified_date
183                .serialize_into(&mut buf)
184                .expect("fixed-size buffer");
185            out.push(LocalSetOwnedItem::owned(
186                TAG_LAST_MODIFIED_DATE,
187                buf.to_vec(),
188            ));
189        }
190        out.push(LocalSetOwnedItem::fixed(
191            TAG_VERSION,
192            self.version.to_be_bytes(),
193        ));
194        if let Some(v) = self.object_model_version {
195            out.push(LocalSetOwnedItem::fixed(
196                TAG_OBJECT_MODEL_VERSION,
197                v.to_be_bytes(),
198            ));
199        }
200        if let Some(p) = self.primary_package {
201            out.push(LocalSetOwnedItem::fixed(TAG_PRIMARY_PACKAGE, p));
202        }
203        out.push(LocalSetOwnedItem::owned(
204            TAG_IDENTIFICATIONS,
205            serialize_uid_batch(&self.identifications),
206        ));
207        out.push(LocalSetOwnedItem::fixed(
208            TAG_CONTENT_STORAGE,
209            self.content_storage,
210        ));
211        out.push(LocalSetOwnedItem::fixed(
212            TAG_OPERATIONAL_PATTERN,
213            self.operational_pattern,
214        ));
215        out.push(LocalSetOwnedItem::owned(
216            TAG_ESSENCE_CONTAINERS,
217            serialize_uid_batch(&self.essence_containers),
218        ));
219        out.push(LocalSetOwnedItem::owned(
220            TAG_DM_SCHEMES,
221            serialize_uid_batch(&self.dm_schemes),
222        ));
223        out
224    }
225}
226
227impl Serialize for Preface {
228    type Error = Error;
229
230    fn serialized_len(&self) -> usize {
231        let (key, items) =
232            finish_owned_set(StructuralSetKind::Preface, self.owned_items(), &self.dark);
233        owned_set_serialized_len(key, &items)
234    }
235
236    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
237        let (key, items) =
238            finish_owned_set(StructuralSetKind::Preface, self.owned_items(), &self.dark);
239        serialize_owned_set(key, &items, buf)
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    fn sample() -> Preface {
248        Preface {
249            interchange: InterchangeObjectFields {
250                instance_uid: [0x11; 16],
251                generation_uid: Some([0x22; 16]),
252                object_class: None,
253            },
254            last_modified_date: MxfTimestamp {
255                year: 2019,
256                month: 11,
257                day: 28,
258                hour: 10,
259                minute: 0,
260                second: 0,
261                msec_div4: 0,
262            },
263            version: VERSION_1_3,
264            object_model_version: Some(1),
265            primary_package: Some([0x33; 16]),
266            identifications: alloc::vec![[0x44; 16]],
267            content_storage: [0x55; 16],
268            operational_pattern: [0x66; 16],
269            essence_containers: alloc::vec![[0x77; 16]],
270            dm_schemes: Vec::new(),
271            dark: Vec::new(),
272        }
273    }
274
275    #[test]
276    fn construct_serialize_parse_round_trip() {
277        let preface = sample();
278        let mut buf = alloc::vec![0u8; preface.serialized_len()];
279        preface.serialize_into(&mut buf).unwrap();
280        let parsed = Preface::parse(&buf).unwrap();
281        assert_eq!(parsed, preface);
282
283        // Reserialize the parsed value and confirm byte-identical output.
284        let mut buf2 = alloc::vec![0u8; parsed.serialized_len()];
285        parsed.serialize_into(&mut buf2).unwrap();
286        assert_eq!(buf, buf2);
287    }
288
289    #[test]
290    fn mutation_changes_serialized_bytes() {
291        let mut preface = sample();
292        let original = preface.to_bytes();
293        preface.version = 0x0200;
294        let mutated = preface.to_bytes();
295        assert_ne!(original, mutated);
296        assert_eq!(Preface::parse(&mutated).unwrap().version, 0x0200);
297    }
298
299    #[test]
300    fn dark_tags_preserved_round_trip() {
301        let mut preface = sample();
302        preface.dark = alloc::vec![(0x8001, alloc::vec![9, 9, 9])];
303        let bytes = preface.to_bytes();
304        let parsed = Preface::parse(&bytes).unwrap();
305        assert_eq!(parsed.dark, preface.dark);
306    }
307
308    #[test]
309    fn identifications_absent_tolerated_e_req_not_hard_required() {
310        // Annex A.2's "Req?" column marks Identifications "E/req": an
311        // encoder must write it, but a decoder must not fail if it's
312        // missing. Build a Preface local set with every other required
313        // property present but Identifications omitted entirely.
314        let mut preface = sample();
315        preface.identifications = Vec::new();
316        let owned = preface.owned_items();
317        let items: Vec<LocalSetOwnedItem> = owned
318            .into_iter()
319            .filter(|item| item.tag != TAG_IDENTIFICATIONS)
320            .collect();
321        let (key, encoded) = finish_owned_set(StructuralSetKind::Preface, items, &Vec::new());
322        let mut buf = alloc::vec![0u8; owned_set_serialized_len(key, &encoded)];
323        serialize_owned_set(key, &encoded, &mut buf).unwrap();
324
325        let parsed = Preface::parse(&buf).expect("absent Identifications must not error");
326        assert_eq!(parsed.identifications, Vec::<UlBytes>::new());
327    }
328
329    #[test]
330    fn wrong_kind_rejected() {
331        let key = LocalSet::build_key(
332            StructuralSetKind::Identification,
333            crate::local_set::ItemLengthMode::TwoByte,
334        );
335        let set = LocalSet {
336            key,
337            items: Vec::new(),
338        };
339        let bytes = set.to_bytes();
340        assert!(matches!(
341            Preface::parse(&bytes),
342            Err(Error::KeyPrefixMismatch { .. })
343        ));
344    }
345}