1use crate::error::{Error, Result};
8
9const BASE_MASK: u64 = (1 << 33) - 1;
11const EXT_MASK: u64 = (1 << 9) - 1;
13
14pub(crate) const SCR_FIELD_LEN: usize = 6;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct Scr {
22 pub base: u64,
24 pub extension: u16,
26}
27
28impl Scr {
29 #[must_use]
33 pub const fn ticks(self) -> u64 {
34 broadcast_common::clock33::pcr_from_parts(self.base, self.extension)
35 }
36
37 #[must_use]
39 pub fn seconds(self) -> f64 {
40 self.ticks() as f64 / broadcast_common::clock33::PCR_HZ as f64
41 }
42}
43
44pub(crate) fn read_scr_field(b: &[u8], what: &'static str) -> Result<Scr> {
61 if b.len() < SCR_FIELD_LEN {
62 return Err(Error::BufferTooShort(
63 broadcast_common::error::BufferTooShort {
64 need: SCR_FIELD_LEN,
65 have: b.len(),
66 what,
67 },
68 ));
69 }
70
71 let b0 = b[0];
72 let b1 = b[1];
73 let b2 = b[2];
74 let b3 = b[3];
75 let b4 = b[4];
76 let b5 = b[5];
77
78 if (b0 >> 4) == 0b0010 {
86 return Err(Error::Mpeg1NotSupported);
87 }
88 if (b0 >> 6) != 0b01 {
90 return Err(Error::BadScrPrefix(b0 >> 6));
91 }
92
93 if (b0 & 0x04) == 0 {
95 return Err(Error::BadMarker(what));
96 }
97 if (b2 & 0x04) == 0 {
98 return Err(Error::BadMarker(what));
99 }
100 if (b4 & 0x04) == 0 {
101 return Err(Error::BadMarker(what));
102 }
103 if (b5 & 0x01) == 0 {
104 return Err(Error::BadMarker(what));
105 }
106
107 let hi = u64::from((b0 >> 3) & 0x07);
109 let mid_hi = u64::from(b0 & 0x03);
111 let mid_mid = u64::from(b1);
113 let mid_lo = u64::from((b2 >> 3) & 0x1F);
115 let lo_hi = u64::from(b2 & 0x03);
117 let lo_mid = u64::from(b3);
119 let lo_lo = u64::from((b4 >> 3) & 0x1F);
121
122 let base = (hi << 30)
123 | (mid_hi << 28)
124 | (mid_mid << 20)
125 | (mid_lo << 15)
126 | (lo_hi << 13)
127 | (lo_mid << 5)
128 | lo_lo;
129
130 let ext_hi = u16::from(b4 & 0x03);
132 let ext_lo = u16::from(b5 >> 1);
134
135 let extension = (ext_hi << 7) | ext_lo;
136
137 Ok(Scr { base, extension })
138}
139
140pub(crate) fn encode_scr_field(scr: Scr) -> crate::error::Result<[u8; SCR_FIELD_LEN]> {
143 broadcast_common::len::fit_bits(scr.base, 33, "pack_header.scr.base")?;
144 broadcast_common::len::fit_bits(u64::from(scr.extension), 9, "pack_header.scr.extension")?;
145 Ok(write_scr_field(scr))
146}
147
148pub(crate) fn write_scr_field(scr: Scr) -> [u8; SCR_FIELD_LEN] {
152 let base = scr.base & BASE_MASK;
153 let extension = (scr.extension as u64) & EXT_MASK;
154
155 let b0 = 0x40 | (((base >> 30) & 0x07) as u8) << 3 | 0x04 | ((base >> 28) & 0x03) as u8; let b1 = ((base >> 20) & 0xFF) as u8; let b2 = (((base >> 15) & 0x1F) as u8) << 3 | 0x04 | ((base >> 13) & 0x03) as u8; let b3 = ((base >> 5) & 0xFF) as u8; let b4 = ((base & 0x1F) as u8) << 3 | 0x04 | ((extension >> 7) & 0x03) as u8; let b5 = (((extension & 0x7F) as u8) << 1) | 0x01; [b0, b1, b2, b3, b4, b5]
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn scr_round_trip_boundary_values() {
184 let cases: &[(u64, u16)] = &[
185 (0, 0),
186 (1, 0),
187 (0, 1),
188 (BASE_MASK, EXT_MASK as u16),
189 (0x0001_9999, 0x0123),
190 (0x0123_4567, 0x1AB),
191 ];
192 for &(base_val, ext) in cases {
193 let scr = Scr {
194 base: base_val,
195 extension: ext,
196 };
197 let enc = write_scr_field(scr);
198 let dec = read_scr_field(&enc, "SCR").unwrap();
199 assert_eq!(dec, scr, "base={base_val:#x}, ext={ext:#x}");
200 }
201 }
202
203 #[test]
204 fn scr_zero_matches_fixture_pattern() {
205 let scr = Scr {
207 base: 0,
208 extension: 0,
209 };
210 let enc = write_scr_field(scr);
211 assert_eq!(enc, [0x44, 0x00, 0x04, 0x00, 0x04, 0x01]);
213 let dec = read_scr_field(&enc, "SCR").unwrap();
214 assert_eq!(dec, scr);
215 }
216
217 #[test]
218 fn scr_rejects_bad_prefix() {
219 let mut enc = write_scr_field(Scr {
220 base: 0,
221 extension: 0,
222 });
223 enc[0] |= 0x80; assert!(matches!(
225 read_scr_field(&enc, "SCR"),
226 Err(Error::BadScrPrefix(_))
227 ));
228 }
229
230 #[test]
235 fn scr_reports_mpeg1_prefix_distinctly_from_corruption() {
236 let mut enc = write_scr_field(Scr {
237 base: 0,
238 extension: 0,
239 });
240 enc[0] = (enc[0] & 0x0F) | 0x20; assert!(matches!(
242 read_scr_field(&enc, "SCR"),
243 Err(Error::Mpeg1NotSupported)
244 ));
245 }
246
247 #[test]
248 fn scr_rejects_bad_marker() {
249 for byte_idx in [0usize, 2, 4] {
250 let mut enc = write_scr_field(Scr {
251 base: 0,
252 extension: 0,
253 });
254 enc[byte_idx] &= !0x04; assert!(matches!(
256 read_scr_field(&enc, "SCR"),
257 Err(Error::BadMarker(_))
258 ));
259 }
260 let mut enc = write_scr_field(Scr {
262 base: 0,
263 extension: 0,
264 });
265 enc[5] &= 0xFE;
266 assert!(matches!(
267 read_scr_field(&enc, "SCR"),
268 Err(Error::BadMarker(_))
269 ));
270 }
271
272 #[test]
273 fn scr_ticks() {
274 let scr = Scr {
275 base: 100,
276 extension: 50,
277 };
278 assert_eq!(scr.ticks(), 100 * 300 + 50);
279 }
280
281 #[test]
282 fn scr_seconds() {
283 let scr = Scr {
284 base: 27_000_000 / 300,
285 extension: 0,
286 };
287 assert!((scr.seconds() - 1.0).abs() < 1e-9);
288 }
289}