Skip to main content

st377_1/
partition.rs

1//! Partition Pack — SMPTE ST 377-1:2019 §7.1-§7.4 (`docs/st377-1.md`), the
2//! file's own backbone: every Header/Body/Footer Partition begins with one
3//! of these.
4
5extern crate alloc;
6
7use alloc::vec::Vec;
8
9use broadcast_common::{Parse, Serialize};
10
11use crate::ber::{ber_length_size, decode_ber_length, encode_ber_length};
12use crate::error::{Error, Result};
13use crate::types::{UlBytes, parse_uid_batch, serialize_uid_batch, ul_bytes_from_prefix};
14
15/// Fixed bytes 1-13 of every Partition Pack Key (Table 4), i.e. everything
16/// except byte 8 (registry version, wildcard on parse), byte 14
17/// ([`PartitionKind`]), byte 15 ([`PartitionStatus`]), and byte 16
18/// (reserved, must be `0x00`).
19const PARTITION_KEY_PREFIX: [u8; 7] = [0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01];
20const PARTITION_KEY_MID: [u8; 4] = [0x0D, 0x01, 0x02, 0x01];
21
22/// Which kind of Partition a Partition Pack Key identifies (Table 4 byte
23/// 14; Tables 6-8).
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[non_exhaustive]
27pub enum PartitionKind {
28    /// `0x02` — Header Partition (§7.2, Table 6).
29    Header,
30    /// `0x03` — Body Partition (§7.3, Table 7).
31    Body,
32    /// `0x04` — Footer Partition (§7.4, Table 8; never Open).
33    Footer,
34}
35
36impl PartitionKind {
37    /// The spec's own label.
38    #[must_use]
39    pub fn name(&self) -> &'static str {
40        match self {
41            Self::Header => "Header Partition",
42            Self::Body => "Body Partition",
43            Self::Footer => "Footer Partition",
44        }
45    }
46
47    fn from_byte(b: u8) -> Result<Self> {
48        match b {
49            0x02 => Ok(Self::Header),
50            0x03 => Ok(Self::Body),
51            0x04 => Ok(Self::Footer),
52            other => Err(Error::UnknownPartitionKind { byte: other }),
53        }
54    }
55
56    fn to_byte(self) -> u8 {
57        match self {
58            Self::Header => 0x02,
59            Self::Body => 0x03,
60            Self::Footer => 0x04,
61        }
62    }
63}
64
65broadcast_common::impl_spec_display!(PartitionKind);
66
67/// The Open/Closed × Complete/Incomplete status of a Partition (§6.2.3,
68/// Table 4 byte 15).
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71#[non_exhaustive]
72pub enum PartitionStatus {
73    /// `0x01` — Open and Incomplete.
74    OpenIncomplete,
75    /// `0x02` — Closed and Incomplete.
76    ClosedIncomplete,
77    /// `0x03` — Open and Complete.
78    OpenComplete,
79    /// `0x04` — Closed and Complete.
80    ClosedComplete,
81}
82
83impl PartitionStatus {
84    /// The spec's own label.
85    #[must_use]
86    pub fn name(&self) -> &'static str {
87        match self {
88            Self::OpenIncomplete => "open and incomplete",
89            Self::ClosedIncomplete => "closed and incomplete",
90            Self::OpenComplete => "open and complete",
91            Self::ClosedComplete => "closed and complete",
92        }
93    }
94
95    /// True for [`Self::OpenIncomplete`]/[`Self::OpenComplete`].
96    #[must_use]
97    pub fn is_open(&self) -> bool {
98        matches!(self, Self::OpenIncomplete | Self::OpenComplete)
99    }
100
101    fn from_byte(b: u8) -> Result<Self> {
102        match b {
103            0x01 => Ok(Self::OpenIncomplete),
104            0x02 => Ok(Self::ClosedIncomplete),
105            0x03 => Ok(Self::OpenComplete),
106            0x04 => Ok(Self::ClosedComplete),
107            other => Err(Error::UnknownPartitionStatus { byte: other }),
108        }
109    }
110
111    fn to_byte(self) -> u8 {
112        match self {
113            Self::OpenIncomplete => 0x01,
114            Self::ClosedIncomplete => 0x02,
115            Self::OpenComplete => 0x03,
116            Self::ClosedComplete => 0x04,
117        }
118    }
119}
120
121broadcast_common::impl_spec_display!(PartitionStatus);
122
123/// The Partition Pack — SMPTE ST 377-1:2019 §7.1, Table 4 (Key) + Table 5
124/// (Value). Covers the Header/Body/Footer variants (§7.2-7.4); which one a
125/// given instance is lives in `kind`.
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct PartitionPack {
128    /// Which kind of Partition this is (Table 4 byte 14).
129    pub kind: PartitionKind,
130    /// Open/Closed × Complete/Incomplete (Table 4 byte 15).
131    pub status: PartitionStatus,
132    /// Fixed `0x0001` for this revision.
133    pub major_version: u16,
134    /// Fixed `0x0003` for this revision (`0x0002` = ST 377M-2004).
135    pub minor_version: u16,
136    /// KLV Alignment Grid size in bytes: `0`=undefined, `1`=byte-aligned,
137    /// `2..=1048576` a valid grid, `>1048576` forbidden (§6.4.1).
138    pub kag_size: u32,
139    /// Byte offset of this Partition from the start of the Header
140    /// Partition (always 0 for the Header Partition itself).
141    pub this_partition: u64,
142    /// Byte offset of the previous Partition (0 for the first Partition
143    /// after the Header).
144    pub previous_partition: u64,
145    /// Byte offset of the Footer Partition (0 if not yet known).
146    pub footer_partition: u64,
147    /// Bytes used for Header Metadata + Primer Pack in this Partition (0 if
148    /// none).
149    pub header_byte_count: u64,
150    /// Bytes used for Index Table Segments in this Partition (0 if none).
151    pub index_byte_count: u64,
152    /// Index Table Segment stream ID in this Partition (0 = none).
153    pub index_sid: u32,
154    /// Byte offset of the Essence Container segment in this Partition,
155    /// relative to the start of its BodySID stream.
156    pub body_offset: u64,
157    /// Essence Container stream ID in this Partition (0 = none).
158    pub body_sid: u32,
159    /// Operational Pattern UL (copy of the Preface's own value).
160    pub operational_pattern: UlBytes,
161    /// Batch of Essence Container ULs used in/referenced by this file.
162    pub essence_containers: Vec<UlBytes>,
163}
164
165impl PartitionPack {
166    /// Build the 16-byte Key for `kind`/`status` (Table 4/6/7/8).
167    ///
168    /// Byte numbering (1-indexed per the spec) -> 0-indexed array position:
169    /// bytes 1-7 = the fixed prefix `06 0E 2B 34 02 05 01`; byte 8 =
170    /// registry version (wildcard on parse, written as `0x01`); bytes 9-12
171    /// = the fixed mid section `0D 01 02 01`; byte 13 = Structure Kind
172    /// (`0x01`, part of the fixed prefix); byte 14 = Partition Kind
173    /// ([`PartitionKind`]); byte 15 = Partition Status ([`PartitionStatus`]);
174    /// byte 16 = reserved (`0x00`).
175    #[must_use]
176    pub fn key(kind: PartitionKind, status: PartitionStatus) -> UlBytes {
177        let mut key = [0u8; 16];
178        key[0..7].copy_from_slice(&PARTITION_KEY_PREFIX);
179        key[7] = 0x01;
180        key[8..12].copy_from_slice(&PARTITION_KEY_MID);
181        key[12] = 0x01; // Structure Kind
182        key[13] = kind.to_byte();
183        key[14] = status.to_byte();
184        key[15] = 0x00;
185        key
186    }
187
188    /// True if `key` matches the Partition Pack Key (Table 4): the fixed
189    /// prefix bytes AND byte 14 one of the three valid [`PartitionKind`]
190    /// values. Useful to recognize a Partition Pack while walking a file's
191    /// top-level KLV items, before committing to a full parse.
192    ///
193    /// The fixed prefix alone is **not** sufficient to distinguish a
194    /// Partition Pack from a [`crate::PrimerPack`] or
195    /// [`crate::RandomIndexPack`] Key: all three share the same "Defined-
196    /// Length Pack, Set/Pack Registry" family (bytes 1-13, Tables 4/13/29)
197    /// and differ only in byte 14 (`0x02`/`0x03`/`0x04` Partition vs. `0x05`
198    /// Primer Pack vs. `0x11` Random Index Pack) — so byte 14 must be
199    /// checked too, not just the shared prefix.
200    #[must_use]
201    pub fn is_partition_key(key: &UlBytes) -> bool {
202        key[0..7] == PARTITION_KEY_PREFIX
203            && key[8..12] == PARTITION_KEY_MID
204            && key[12] == 0x01
205            && PartitionKind::from_byte(key[13]).is_ok()
206    }
207
208    fn parse_key(key: &UlBytes) -> Result<(PartitionKind, PartitionStatus)> {
209        if key[0..7] != PARTITION_KEY_PREFIX || key[8..12] != PARTITION_KEY_MID || key[12] != 0x01 {
210            return Err(Error::KeyPrefixMismatch {
211                what: "Partition Pack (Table 4)",
212            });
213        }
214        let kind = PartitionKind::from_byte(key[13])?;
215        let status = PartitionStatus::from_byte(key[14])?;
216        if kind == PartitionKind::Footer && status.is_open() {
217            return Err(Error::OpenFooterPartition { byte: key[14] });
218        }
219        Ok((kind, status))
220    }
221}
222
223impl<'a> Parse<'a> for PartitionPack {
224    type Error = Error;
225
226    fn parse(bytes: &'a [u8]) -> Result<Self> {
227        if bytes.len() < 16 {
228            return Err(Error::BufferTooShort {
229                need: 16,
230                have: bytes.len(),
231                what: "Partition Pack key",
232            });
233        }
234        let key: UlBytes = ul_bytes_from_prefix(bytes);
235        let (kind, status) = Self::parse_key(&key)?;
236
237        let (len, len_size) = decode_ber_length(&bytes[16..])?;
238        let value_start = 16 + len_size;
239        let len = len as usize;
240        let value_end = value_start.checked_add(len).ok_or(Error::BufferTooShort {
241            need: usize::MAX,
242            have: bytes.len(),
243            what: "Partition Pack value (length overflow)",
244        })?;
245        if bytes.len() < value_end {
246            return Err(Error::BufferTooShort {
247                need: value_end,
248                have: bytes.len(),
249                what: "Partition Pack value",
250            });
251        }
252        let v = &bytes[value_start..value_end];
253
254        const FIXED_FIELDS_LEN: usize = 2 + 2 + 4 + 8 + 8 + 8 + 8 + 8 + 4 + 8 + 4 + 16;
255        if v.len() < FIXED_FIELDS_LEN {
256            return Err(Error::BufferTooShort {
257                need: FIXED_FIELDS_LEN,
258                have: v.len(),
259                what: "Partition Pack fixed fields",
260            });
261        }
262        let u16_at = |o: usize| u16::from_be_bytes([v[o], v[o + 1]]);
263        let u32_at = |o: usize| u32::from_be_bytes([v[o], v[o + 1], v[o + 2], v[o + 3]]);
264        let u64_at = |o: usize| {
265            u64::from_be_bytes([
266                v[o],
267                v[o + 1],
268                v[o + 2],
269                v[o + 3],
270                v[o + 4],
271                v[o + 5],
272                v[o + 6],
273                v[o + 7],
274            ])
275        };
276
277        let major_version = u16_at(0);
278        let minor_version = u16_at(2);
279        let kag_size = u32_at(4);
280        let this_partition = u64_at(8);
281        let previous_partition = u64_at(16);
282        let footer_partition = u64_at(24);
283        let header_byte_count = u64_at(32);
284        let index_byte_count = u64_at(40);
285        let index_sid = u32_at(48);
286        let body_offset = u64_at(52);
287        let body_sid = u32_at(60);
288        let operational_pattern: UlBytes = ul_bytes_from_prefix(&v[64..]);
289        let essence_containers = parse_uid_batch(&v[80..])?;
290
291        Ok(PartitionPack {
292            kind,
293            status,
294            major_version,
295            minor_version,
296            kag_size,
297            this_partition,
298            previous_partition,
299            footer_partition,
300            header_byte_count,
301            index_byte_count,
302            index_sid,
303            body_offset,
304            body_sid,
305            operational_pattern,
306            essence_containers,
307        })
308    }
309}
310
311impl Serialize for PartitionPack {
312    type Error = Error;
313
314    fn serialized_len(&self) -> usize {
315        let value_len =
316            2 + 2 + 4 + 8 + 8 + 8 + 8 + 8 + 4 + 8 + 4 + 16 + 8 + self.essence_containers.len() * 16;
317        16 + ber_length_size(value_len as u64) + value_len
318    }
319
320    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
321        if self.kind == PartitionKind::Footer && self.status.is_open() {
322            return Err(Error::OpenFooterPartition {
323                byte: self.status.to_byte(),
324            });
325        }
326        let total = self.serialized_len();
327        if buf.len() < total {
328            return Err(Error::BufferTooShort {
329                need: total,
330                have: buf.len(),
331                what: "Partition Pack",
332            });
333        }
334        buf[0..16].copy_from_slice(&Self::key(self.kind, self.status));
335        let batch = serialize_uid_batch(&self.essence_containers);
336        let value_len = 2 + 2 + 4 + 8 + 8 + 8 + 8 + 8 + 4 + 8 + 4 + 16 + batch.len();
337        let len_size = encode_ber_length(value_len as u64, &mut buf[16..])?;
338        let mut pos = 16 + len_size;
339
340        buf[pos..pos + 2].copy_from_slice(&self.major_version.to_be_bytes());
341        pos += 2;
342        buf[pos..pos + 2].copy_from_slice(&self.minor_version.to_be_bytes());
343        pos += 2;
344        buf[pos..pos + 4].copy_from_slice(&self.kag_size.to_be_bytes());
345        pos += 4;
346        buf[pos..pos + 8].copy_from_slice(&self.this_partition.to_be_bytes());
347        pos += 8;
348        buf[pos..pos + 8].copy_from_slice(&self.previous_partition.to_be_bytes());
349        pos += 8;
350        buf[pos..pos + 8].copy_from_slice(&self.footer_partition.to_be_bytes());
351        pos += 8;
352        buf[pos..pos + 8].copy_from_slice(&self.header_byte_count.to_be_bytes());
353        pos += 8;
354        buf[pos..pos + 8].copy_from_slice(&self.index_byte_count.to_be_bytes());
355        pos += 8;
356        buf[pos..pos + 4].copy_from_slice(&self.index_sid.to_be_bytes());
357        pos += 4;
358        buf[pos..pos + 8].copy_from_slice(&self.body_offset.to_be_bytes());
359        pos += 8;
360        buf[pos..pos + 4].copy_from_slice(&self.body_sid.to_be_bytes());
361        pos += 4;
362        buf[pos..pos + 16].copy_from_slice(&self.operational_pattern);
363        pos += 16;
364        buf[pos..pos + batch.len()].copy_from_slice(&batch);
365        pos += batch.len();
366        Ok(pos)
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    fn sample(kind: PartitionKind, status: PartitionStatus) -> PartitionPack {
375        PartitionPack {
376            kind,
377            status,
378            major_version: 1,
379            minor_version: 3,
380            kag_size: 512,
381            this_partition: 0,
382            previous_partition: 0,
383            footer_partition: 12345,
384            header_byte_count: 1000,
385            index_byte_count: 0,
386            index_sid: 0,
387            body_offset: 0,
388            body_sid: 1,
389            operational_pattern: [0xAA; 16],
390            essence_containers: alloc::vec![[0xBBu8; 16]],
391        }
392    }
393
394    #[test]
395    fn header_partition_round_trip() {
396        let pp = sample(PartitionKind::Header, PartitionStatus::ClosedComplete);
397        let mut buf = alloc::vec![0u8; pp.serialized_len()];
398        pp.serialize_into(&mut buf).unwrap();
399        let parsed = PartitionPack::parse(&buf).unwrap();
400        assert_eq!(parsed, pp);
401    }
402
403    #[test]
404    fn body_partition_round_trip_empty_essence_containers() {
405        let mut pp = sample(PartitionKind::Body, PartitionStatus::OpenIncomplete);
406        pp.essence_containers.clear();
407        let mut buf = alloc::vec![0u8; pp.serialized_len()];
408        pp.serialize_into(&mut buf).unwrap();
409        let parsed = PartitionPack::parse(&buf).unwrap();
410        assert_eq!(parsed, pp);
411        assert!(parsed.essence_containers.is_empty());
412    }
413
414    #[test]
415    fn footer_partition_cannot_be_open() {
416        let pp = sample(PartitionKind::Footer, PartitionStatus::OpenComplete);
417        let mut buf = alloc::vec![0u8; pp.serialized_len()];
418        assert!(matches!(
419            pp.serialize_into(&mut buf),
420            Err(Error::OpenFooterPartition { .. })
421        ));
422    }
423
424    #[test]
425    fn parse_rejects_open_footer_key() {
426        let key = PartitionPack::key(PartitionKind::Footer, PartitionStatus::OpenComplete);
427        let mut bytes = alloc::vec::Vec::new();
428        bytes.extend_from_slice(&key);
429        bytes.push(0); // zero-length value (short-form BER)
430        assert!(matches!(
431            PartitionPack::parse(&bytes),
432            Err(Error::OpenFooterPartition { .. })
433        ));
434    }
435
436    #[test]
437    fn unknown_kind_byte_rejected() {
438        assert!(matches!(
439            PartitionKind::from_byte(0xFF),
440            Err(Error::UnknownPartitionKind { byte: 0xFF })
441        ));
442    }
443}