Skip to main content

st377_1/
random_index_pack.rs

1//! Random Index Pack — SMPTE ST 377-1:2019 §12, Tables 29-30
2//! (`docs/st377-1.md`): the optional last KLV item in a file, letting a
3//! decoder locate every Partition without a linear scan.
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::ul_bytes_from_prefix;
14
15const RIP_KEY_PREFIX: [u8; 7] = [0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01];
16const RIP_KEY_MID: [u8; 4] = [0x0D, 0x01, 0x02, 0x01];
17/// Byte 14 (Set/Pack Kind = Random Index Pack) and byte 15 (RIP version).
18const RIP_KEY_TAIL: [u8; 2] = [0x11, 0x01];
19
20/// One `{BodySID, ByteOffset}` pair (Table 30) locating a single Partition.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct PartitionLocation {
24    /// Stream ID of the Body in that Partition (0 if none).
25    pub body_sid: u32,
26    /// Byte offset from the first byte of the Header Partition Pack Key
27    /// (byte 0) to the first byte of that Partition's own Partition Pack
28    /// Key.
29    pub byte_offset: u64,
30}
31
32/// The Random Index Pack — SMPTE ST 377-1:2019 §12: one
33/// [`PartitionLocation`] per Partition in the file (including Header and
34/// Footer), ascending `byte_offset` order, plus a trailing overall-length
35/// field (§12.2 Note 2) that lets a decoder seek from EOF directly to this
36/// Pack's own Key without a forward scan.
37#[derive(Debug, Clone, PartialEq, Eq, Default)]
38pub struct RandomIndexPack {
39    /// One entry per Partition in the file, ascending `byte_offset` order.
40    pub partitions: Vec<PartitionLocation>,
41}
42
43impl RandomIndexPack {
44    /// Build the 16-byte Random Index Pack Key (Table 29).
45    #[must_use]
46    pub fn key() -> crate::types::UlBytes {
47        let mut key = [0u8; 16];
48        key[0..7].copy_from_slice(&RIP_KEY_PREFIX);
49        key[7] = 0x01; // registry version
50        key[8..12].copy_from_slice(&RIP_KEY_MID);
51        key[12] = 0x01; // Structure Kind
52        key[13..15].copy_from_slice(&RIP_KEY_TAIL);
53        key[15] = 0x00; // reserved
54        key
55    }
56
57    /// True if `key` is the Random Index Pack Key (Table 29), ignoring
58    /// byte 8 (registry version, wildcard).
59    #[must_use]
60    pub fn is_rip_key(key: &[u8; 16]) -> bool {
61        key[0..7] == RIP_KEY_PREFIX
62            && key[8..12] == RIP_KEY_MID
63            && key[12] == 0x01
64            && key[13..15] == RIP_KEY_TAIL
65    }
66
67    fn check_key(key: &[u8; 16]) -> Result<()> {
68        if Self::is_rip_key(key) {
69            Ok(())
70        } else {
71            Err(Error::KeyPrefixMismatch {
72                what: "Random Index Pack (Table 29)",
73            })
74        }
75    }
76
77    /// Parse a Random Index Pack (Key + Length + Value, including the
78    /// trailing overall-length field) from `bytes`.
79    pub fn parse_prefix(bytes: &[u8]) -> Result<(Self, usize)> {
80        if bytes.len() < 16 {
81            return Err(Error::BufferTooShort {
82                need: 16,
83                have: bytes.len(),
84                what: "Random Index Pack key",
85            });
86        }
87        let key: [u8; 16] = ul_bytes_from_prefix(bytes);
88        Self::check_key(&key)?;
89
90        let (len, len_size) = decode_ber_length(&bytes[16..])?;
91        let value_start = 16 + len_size;
92        let len = len as usize;
93        let value_end = value_start.checked_add(len).ok_or(Error::BufferTooShort {
94            need: usize::MAX,
95            have: bytes.len(),
96            what: "Random Index Pack value (length overflow)",
97        })?;
98        if bytes.len() < value_end {
99            return Err(Error::BufferTooShort {
100                need: value_end,
101                have: bytes.len(),
102                what: "Random Index Pack value",
103            });
104        }
105        let v = &bytes[value_start..value_end];
106        // Value = N * {BodySID: u32, ByteOffset: u64} (12 bytes each) +
107        // trailing overall Length: u32.
108        if v.len() < 4 || !(v.len() - 4).is_multiple_of(12) {
109            return Err(Error::InvalidBatchHeader {
110                count: 0,
111                item_len: 12,
112                buffer_len: v.len(),
113            });
114        }
115        let pairs_len = v.len() - 4;
116        let mut partitions = Vec::with_capacity(pairs_len / 12);
117        for chunk in v[..pairs_len].chunks_exact(12) {
118            let body_sid = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
119            let byte_offset = u64::from_be_bytes([
120                chunk[4], chunk[5], chunk[6], chunk[7], chunk[8], chunk[9], chunk[10], chunk[11],
121            ]);
122            partitions.push(PartitionLocation {
123                body_sid,
124                byte_offset,
125            });
126        }
127        // Trailing Length field is a redundant seek-optimization value
128        // (recomputed on serialize, not stored) — validated against the
129        // pack's own actual total length.
130        let trailing = u32::from_be_bytes([
131            v[pairs_len],
132            v[pairs_len + 1],
133            v[pairs_len + 2],
134            v[pairs_len + 3],
135        ]);
136        let actual_total = value_end as u64;
137        if u64::from(trailing) != actual_total {
138            return Err(Error::InvalidPropertyLength {
139                tag: 0,
140                name: "Random Index Pack trailing Length",
141                found: trailing as usize,
142                expected: actual_total as usize,
143            });
144        }
145
146        Ok((RandomIndexPack { partitions }, value_end))
147    }
148}
149
150impl<'a> Parse<'a> for RandomIndexPack {
151    type Error = Error;
152
153    fn parse(bytes: &'a [u8]) -> Result<Self> {
154        let (rip, consumed) = Self::parse_prefix(bytes)?;
155        if consumed != bytes.len() {
156            return Err(Error::BufferTooShort {
157                need: consumed,
158                have: bytes.len(),
159                what: "Random Index Pack (trailing bytes after exact-fit parse)",
160            });
161        }
162        Ok(rip)
163    }
164}
165
166impl Serialize for RandomIndexPack {
167    type Error = Error;
168
169    fn serialized_len(&self) -> usize {
170        let value_len = self.partitions.len() * 12 + 4;
171        16 + ber_length_size(value_len as u64) + value_len
172    }
173
174    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
175        let total = self.serialized_len();
176        if buf.len() < total {
177            return Err(Error::BufferTooShort {
178                need: total,
179                have: buf.len(),
180                what: "Random Index Pack",
181            });
182        }
183        buf[0..16].copy_from_slice(&Self::key());
184        let value_len = self.partitions.len() * 12 + 4;
185        let len_size = encode_ber_length(value_len as u64, &mut buf[16..])?;
186        let mut pos = 16 + len_size;
187        for p in &self.partitions {
188            buf[pos..pos + 4].copy_from_slice(&p.body_sid.to_be_bytes());
189            pos += 4;
190            buf[pos..pos + 8].copy_from_slice(&p.byte_offset.to_be_bytes());
191            pos += 8;
192        }
193        buf[pos..pos + 4].copy_from_slice(&(total as u32).to_be_bytes());
194        pos += 4;
195        Ok(pos)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn rip_round_trip() {
205        let rip = RandomIndexPack {
206            partitions: alloc::vec![
207                PartitionLocation {
208                    body_sid: 0,
209                    byte_offset: 0,
210                },
211                PartitionLocation {
212                    body_sid: 1,
213                    byte_offset: 65536,
214                },
215                PartitionLocation {
216                    body_sid: 0,
217                    byte_offset: 131072,
218                },
219            ],
220        };
221        let mut buf = alloc::vec![0u8; rip.serialized_len()];
222        rip.serialize_into(&mut buf).unwrap();
223        let parsed = RandomIndexPack::parse(&buf).unwrap();
224        assert_eq!(parsed, rip);
225        // Trailing 4 bytes equal the RIP's own total length (§12.2 Note 2).
226        let trailing_len = u32::from_be_bytes(buf[buf.len() - 4..].try_into().unwrap());
227        assert_eq!(trailing_len as usize, buf.len());
228    }
229
230    #[test]
231    fn empty_rip_round_trip() {
232        let rip = RandomIndexPack::default();
233        let mut buf = alloc::vec![0u8; rip.serialized_len()];
234        rip.serialize_into(&mut buf).unwrap();
235        assert_eq!(RandomIndexPack::parse(&buf).unwrap(), rip);
236    }
237
238    #[test]
239    fn wrong_trailing_length_rejected() {
240        let rip = RandomIndexPack {
241            partitions: alloc::vec![PartitionLocation {
242                body_sid: 1,
243                byte_offset: 100,
244            }],
245        };
246        let mut buf = alloc::vec![0u8; rip.serialized_len()];
247        rip.serialize_into(&mut buf).unwrap();
248        let last = buf.len() - 1;
249        buf[last] ^= 0xFF; // corrupt the trailing Length field
250        assert!(matches!(
251            RandomIndexPack::parse(&buf),
252            Err(Error::InvalidPropertyLength { .. })
253        ));
254    }
255}