Skip to main content

st377_1/
essence_container_data.rs

1//! Essence Container Data — SMPTE ST 377-1:2019 Annex A.5
2//! (`docs/st377-1.md`): links a Package to the BodySID/IndexSID pair
3//! identifying its internal Essence Container / Index Table in the file's
4//! Partitions.
5
6extern crate alloc;
7
8use alloc::vec::Vec;
9
10use broadcast_common::{Parse, Serialize};
11
12use crate::error::{Error, Result};
13use crate::local_set::{LocalSet, StructuralSetKind};
14use crate::sets::{
15    InterchangeObjectFields, LocalSetOwnedItem, collect_dark, finish_owned_set, get_optional_fixed,
16    get_required_fixed, owned_set_serialized_len, serialize_owned_set,
17};
18use crate::types::PackageId;
19
20/// Local tag: Linked Package UID (A.5).
21pub const TAG_LINKED_PACKAGE_UID: u16 = 0x2701;
22/// Local tag: IndexSID (A.5).
23pub const TAG_INDEX_SID: u16 = 0x3F06;
24/// Local tag: BodySID (A.5).
25pub const TAG_BODY_SID: u16 = 0x3F07;
26
27const KNOWN_TAGS: [u16; 6] = [
28    crate::sets::TAG_INSTANCE_UID,
29    crate::sets::TAG_GENERATION_UID,
30    crate::sets::TAG_OBJECT_CLASS,
31    TAG_LINKED_PACKAGE_UID,
32    TAG_INDEX_SID,
33    TAG_BODY_SID,
34];
35
36/// The Essence Container Data Set — SMPTE ST 377-1:2019 Annex A.5: links a
37/// `LinkedPackageUID` to the `BodySID`/`IndexSID` pair identifying its
38/// Essence Container / Index Table Segments among the file's Partitions.
39///
40/// The four Boolean properties A.5 defines with a "dyn" (dynamically
41/// allocated, no fixed static tag) local tag — `PrecedingIndexTable`,
42/// `SingularPartitionUsage`, `FollowingIndexTable`, `IsSparse` — are
43/// preserved byte-for-byte in `dark` rather than individually typed in this
44/// first pass, per `docs/st377-1.md`'s Scope section.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct EssenceContainerData {
47    /// Interchange Object (A.1) base properties.
48    pub interchange: InterchangeObjectFields,
49    /// Linked Package UID (`0x2701`, Req) — the Package this Set is linked
50    /// to (opaque UMID, see `docs/st377-1.md`'s Scope section).
51    pub linked_package_uid: PackageId,
52    /// IndexSID (`0x3F06`, Opt) — ID of the Index Table for the linked
53    /// Essence Container, if any.
54    pub index_sid: Option<u32>,
55    /// BodySID (`0x3F07`, Req) — ID of the linked Essence Container (`0` =
56    /// external to the file).
57    pub body_sid: u32,
58    /// Every other property found on parse, including the four "dyn"-tagged
59    /// Boolean properties above and any private/dark extension — preserved
60    /// byte-for-byte, not decoded.
61    pub dark: Vec<(u16, Vec<u8>)>,
62}
63
64impl<'a> Parse<'a> for EssenceContainerData {
65    type Error = Error;
66
67    fn parse(bytes: &'a [u8]) -> Result<Self> {
68        let set = LocalSet::parse(bytes)?;
69        if set.kind() != StructuralSetKind::EssenceContainerData {
70            return Err(Error::KeyPrefixMismatch {
71                what: "Essence Container Data (Table 17)",
72            });
73        }
74        let items = &set.items;
75        let interchange = InterchangeObjectFields::decode(items, "Essence Container Data")?;
76        let linked_package_uid = PackageId(get_required_fixed::<32>(
77            items,
78            TAG_LINKED_PACKAGE_UID,
79            "Linked Package UID",
80            "Essence Container Data",
81        )?);
82        let index_sid =
83            get_optional_fixed::<4>(items, TAG_INDEX_SID, "IndexSID")?.map(u32::from_be_bytes);
84        let body_sid = u32::from_be_bytes(get_required_fixed::<4>(
85            items,
86            TAG_BODY_SID,
87            "BodySID",
88            "Essence Container Data",
89        )?);
90        let dark = collect_dark(items, &KNOWN_TAGS);
91
92        Ok(EssenceContainerData {
93            interchange,
94            linked_package_uid,
95            index_sid,
96            body_sid,
97            dark,
98        })
99    }
100}
101
102impl EssenceContainerData {
103    fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
104        let mut out = Vec::new();
105        self.interchange.encode_into(&mut out);
106        out.push(LocalSetOwnedItem::fixed(
107            TAG_LINKED_PACKAGE_UID,
108            self.linked_package_uid.0,
109        ));
110        if let Some(idx) = self.index_sid {
111            out.push(LocalSetOwnedItem::fixed(TAG_INDEX_SID, idx.to_be_bytes()));
112        }
113        out.push(LocalSetOwnedItem::fixed(
114            TAG_BODY_SID,
115            self.body_sid.to_be_bytes(),
116        ));
117        out
118    }
119}
120
121impl Serialize for EssenceContainerData {
122    type Error = Error;
123
124    fn serialized_len(&self) -> usize {
125        let (key, items) = finish_owned_set(
126            StructuralSetKind::EssenceContainerData,
127            self.owned_items(),
128            &self.dark,
129        );
130        owned_set_serialized_len(key, &items)
131    }
132
133    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
134        let (key, items) = finish_owned_set(
135            StructuralSetKind::EssenceContainerData,
136            self.owned_items(),
137            &self.dark,
138        );
139        serialize_owned_set(key, &items, buf)
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    fn sample() -> EssenceContainerData {
148        EssenceContainerData {
149            interchange: InterchangeObjectFields {
150                instance_uid: [0x11; 16],
151                generation_uid: None,
152                object_class: None,
153            },
154            linked_package_uid: PackageId([0x22; 32]),
155            index_sid: Some(1),
156            body_sid: 1,
157            dark: Vec::new(),
158        }
159    }
160
161    #[test]
162    fn construct_serialize_parse_round_trip() {
163        let ecd = sample();
164        let bytes = ecd.to_bytes();
165        let parsed = EssenceContainerData::parse(&bytes).unwrap();
166        assert_eq!(parsed, ecd);
167        assert_eq!(parsed.to_bytes(), bytes);
168    }
169
170    #[test]
171    fn null_package_id_round_trips() {
172        let mut ecd = sample();
173        ecd.linked_package_uid = PackageId::NULL;
174        ecd.index_sid = None;
175        ecd.body_sid = 0;
176        let bytes = ecd.to_bytes();
177        let parsed = EssenceContainerData::parse(&bytes).unwrap();
178        assert!(parsed.linked_package_uid.is_null());
179        assert_eq!(parsed.index_sid, None);
180    }
181
182    #[test]
183    fn dark_bool_properties_preserved() {
184        let mut ecd = sample();
185        // Simulate a real file's dynamically-allocated `IsSparse` tag,
186        // e.g. resolved via that Partition's own Primer Pack.
187        ecd.dark = alloc::vec![(0x8010, alloc::vec![0x01])];
188        let bytes = ecd.to_bytes();
189        let parsed = EssenceContainerData::parse(&bytes).unwrap();
190        assert_eq!(parsed.dark, ecd.dark);
191    }
192}