Skip to main content

st377_1/
sets.rs

1//! Shared decode/encode helpers for the four typed Root Metadata Sets
2//! (Annex A: [`crate::Preface`], [`crate::Identification`],
3//! [`crate::ContentStorage`], [`crate::EssenceContainerData`]).
4//!
5//! Every typed Set is an **owned** struct (no borrowed lifetime — these are
6//! small, KB-scale structures, unlike the essence payload [`crate::KlvItem`]
7//! walks zero-copy) built by decoding known local tags out of a parsed
8//! [`crate::LocalSet`] into typed fields, and preserving every other tag
9//! (including "dyn"-tagged optional properties this first pass does not
10//! individually type — see `docs/st377-1.md`'s Scope section) in a `dark`
11//! catch-all so nothing is ever silently dropped.
12
13extern crate alloc;
14
15use alloc::vec::Vec;
16
17use crate::error::{Error, Result};
18use crate::local_set::{ItemLengthMode, LocalSet, LocalSetItem, StructuralSetKind};
19use crate::types::UlBytes;
20
21/// The two Interchange Object (Annex A.1) properties with a static local
22/// tag, common to every Root Metadata Set.
23#[derive(Debug, Clone, PartialEq, Eq, Default)]
24pub struct InterchangeObjectFields {
25    /// Instance UID (`0x3C0A`) — required on every Set.
26    pub instance_uid: UlBytes,
27    /// Generation UID (`0x0102`) — optional (and never encoded on
28    /// `Identification` per A.3's closing note).
29    pub generation_uid: Option<UlBytes>,
30    /// Object Class (`0x0101`) — optional.
31    pub object_class: Option<UlBytes>,
32}
33
34/// Local tag: Instance UID (A.1).
35pub const TAG_INSTANCE_UID: u16 = 0x3C0A;
36/// Local tag: Generation UID (A.1).
37pub const TAG_GENERATION_UID: u16 = 0x0102;
38/// Local tag: Object Class (A.1).
39pub const TAG_OBJECT_CLASS: u16 = 0x0101;
40
41impl InterchangeObjectFields {
42    /// Decode from a parsed [`LocalSet`]'s items, collecting any of the
43    /// three tags it doesn't recognize is not this function's job — the
44    /// caller removes the tags it consumes from its own dark-item pass.
45    pub fn decode(items: &[LocalSetItem<'_>], set_name: &'static str) -> Result<Self> {
46        let instance_uid =
47            get_required_fixed::<16>(items, TAG_INSTANCE_UID, "Instance UID", set_name)?;
48        let generation_uid = get_optional_fixed::<16>(items, TAG_GENERATION_UID, "Generation UID")?;
49        let object_class = get_optional_fixed::<16>(items, TAG_OBJECT_CLASS, "Object Class")?;
50        Ok(Self {
51            instance_uid,
52            generation_uid,
53            object_class,
54        })
55    }
56
57    /// Emit this Set's Interchange Object items in canonical order.
58    pub fn encode_into(&self, out: &mut Vec<LocalSetOwnedItem>) {
59        out.push(LocalSetOwnedItem::fixed(
60            TAG_INSTANCE_UID,
61            self.instance_uid,
62        ));
63        if let Some(g) = self.generation_uid {
64            out.push(LocalSetOwnedItem::fixed(TAG_GENERATION_UID, g));
65        }
66        if let Some(o) = self.object_class {
67            out.push(LocalSetOwnedItem::fixed(TAG_OBJECT_CLASS, o));
68        }
69    }
70}
71
72/// An owned `{tag, value}` pair, used to build a fresh [`LocalSet`] for
73/// serialization (the borrowed [`LocalSetItem`] can't hold bytes owned by
74/// the very struct being serialized).
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct LocalSetOwnedItem {
77    /// The local tag.
78    pub tag: u16,
79    /// The value bytes.
80    pub value: Vec<u8>,
81}
82
83impl LocalSetOwnedItem {
84    /// Build an owned item from a fixed-size array value.
85    #[must_use]
86    pub fn fixed<const N: usize>(tag: u16, value: [u8; N]) -> Self {
87        LocalSetOwnedItem {
88            tag,
89            value: value.to_vec(),
90        }
91    }
92
93    /// Build an owned item from already-owned bytes.
94    #[must_use]
95    pub fn owned(tag: u16, value: Vec<u8>) -> Self {
96        LocalSetOwnedItem { tag, value }
97    }
98}
99
100/// Fetch a required property's raw bytes, checked to be exactly `N` bytes.
101pub fn get_required_fixed<const N: usize>(
102    items: &[LocalSetItem<'_>],
103    tag: u16,
104    name: &'static str,
105    set_name: &'static str,
106) -> Result<[u8; N]> {
107    let value = items.iter().find(|i| i.tag == tag).map(|i| i.value).ok_or(
108        Error::MissingRequiredProperty {
109            tag,
110            name,
111            set: set_name,
112        },
113    )?;
114    <[u8; N]>::try_from(value).map_err(|_| Error::InvalidPropertyLength {
115        tag,
116        name,
117        found: value.len(),
118        expected: N,
119    })
120}
121
122/// Fetch an optional property's raw bytes, checked to be exactly `N` bytes
123/// if present.
124pub fn get_optional_fixed<const N: usize>(
125    items: &[LocalSetItem<'_>],
126    tag: u16,
127    name: &'static str,
128) -> Result<Option<[u8; N]>> {
129    match items.iter().find(|i| i.tag == tag) {
130        None => Ok(None),
131        Some(i) => {
132            <[u8; N]>::try_from(i.value)
133                .map(Some)
134                .map_err(|_| Error::InvalidPropertyLength {
135                    tag,
136                    name,
137                    found: i.value.len(),
138                    expected: N,
139                })
140        }
141    }
142}
143
144/// Fetch a required property's raw bytes (variable length — e.g. a Batch,
145/// Array, or UTF-16 string).
146pub fn get_required_raw<'a>(
147    items: &[LocalSetItem<'a>],
148    tag: u16,
149    name: &'static str,
150    set_name: &'static str,
151) -> Result<&'a [u8]> {
152    items
153        .iter()
154        .find(|i| i.tag == tag)
155        .map(|i| i.value)
156        .ok_or(Error::MissingRequiredProperty {
157            tag,
158            name,
159            set: set_name,
160        })
161}
162
163/// Fetch an optional property's raw bytes (variable length), if present.
164pub fn get_optional_raw<'a>(items: &[LocalSetItem<'a>], tag: u16) -> Option<&'a [u8]> {
165    items.iter().find(|i| i.tag == tag).map(|i| i.value)
166}
167
168/// Every item's tag NOT in `known_tags`, copied into an owned dark list
169/// (round-trip fidelity for properties this crate doesn't individually
170/// decode — including every "dyn"-tagged optional property, see
171/// `docs/st377-1.md`'s Scope section).
172pub fn collect_dark(items: &[LocalSetItem<'_>], known_tags: &[u16]) -> Vec<(u16, Vec<u8>)> {
173    items
174        .iter()
175        .filter(|i| !known_tags.contains(&i.tag))
176        .map(|i| (i.tag, i.value.to_vec()))
177        .collect()
178}
179
180/// Build the final [`LocalSet`]-shaped byte layout for a typed Set: choose
181/// [`ItemLengthMode::TwoByte`] unless any item's value exceeds 65535 bytes
182/// (§9.3 — BER local length encoding is required in that case), build the
183/// Set Key, and hand back `(key, items)` ready for
184/// [`LocalSet::serialize_into`] via a temporary borrowed [`LocalSet`].
185pub fn finish_owned_set(
186    kind: StructuralSetKind,
187    mut owned_items: Vec<LocalSetOwnedItem>,
188    dark: &[(u16, Vec<u8>)],
189) -> (UlBytes, Vec<LocalSetOwnedItem>) {
190    for (tag, value) in dark {
191        owned_items.push(LocalSetOwnedItem {
192            tag: *tag,
193            value: value.clone(),
194        });
195    }
196    let mode = if owned_items.iter().any(|i| i.value.len() > 0xFFFF) {
197        ItemLengthMode::Ber
198    } else {
199        ItemLengthMode::TwoByte
200    };
201    (LocalSet::build_key(kind, mode), owned_items)
202}
203
204/// Serialize `owned_items` under `key` by borrowing each owned item's bytes
205/// into a transient [`LocalSet`], then delegating to its `Serialize` impl.
206pub fn serialize_owned_set(
207    key: UlBytes,
208    owned_items: &[LocalSetOwnedItem],
209    buf: &mut [u8],
210) -> Result<usize> {
211    use broadcast_common::Serialize;
212    let items = owned_items
213        .iter()
214        .map(|i| LocalSetItem {
215            tag: i.tag,
216            value: i.value.as_slice(),
217        })
218        .collect();
219    let set = LocalSet { key, items };
220    set.serialize_into(buf)
221}
222
223/// Length [`serialize_owned_set`] will need.
224#[must_use]
225pub fn owned_set_serialized_len(key: UlBytes, owned_items: &[LocalSetOwnedItem]) -> usize {
226    use broadcast_common::Serialize;
227    let items = owned_items
228        .iter()
229        .map(|i| LocalSetItem {
230            tag: i.tag,
231            value: i.value.as_slice(),
232        })
233        .collect();
234    LocalSet { key, items }.serialized_len()
235}