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 self.base * 300 + self.extension as u64
35 }
36
37 #[must_use]
39 pub fn seconds(self) -> f64 {
40 self.ticks() as f64 / 27_000_000.0
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 need: SCR_FIELD_LEN,
64 have: b.len(),
65 what,
66 });
67 }
68
69 let b0 = b[0];
70 let b1 = b[1];
71 let b2 = b[2];
72 let b3 = b[3];
73 let b4 = b[4];
74 let b5 = b[5];
75
76 if (b0 >> 6) != 0b01 {
78 return Err(Error::BadScrPrefix(b0 >> 6));
79 }
80
81 if (b0 & 0x04) == 0 {
83 return Err(Error::BadMarker(what));
84 }
85 if (b2 & 0x04) == 0 {
86 return Err(Error::BadMarker(what));
87 }
88 if (b4 & 0x04) == 0 {
89 return Err(Error::BadMarker(what));
90 }
91 if (b5 & 0x01) == 0 {
92 return Err(Error::BadMarker(what));
93 }
94
95 let hi = u64::from((b0 >> 3) & 0x07);
97 let mid_hi = u64::from(b0 & 0x03);
99 let mid_mid = u64::from(b1);
101 let mid_lo = u64::from((b2 >> 3) & 0x1F);
103 let lo_hi = u64::from(b2 & 0x03);
105 let lo_mid = u64::from(b3);
107 let lo_lo = u64::from((b4 >> 3) & 0x1F);
109
110 let base = (hi << 30)
111 | (mid_hi << 28)
112 | (mid_mid << 20)
113 | (mid_lo << 15)
114 | (lo_hi << 13)
115 | (lo_mid << 5)
116 | lo_lo;
117
118 let ext_hi = u16::from(b4 & 0x03);
120 let ext_lo = u16::from(b5 >> 1);
122
123 let extension = (ext_hi << 7) | ext_lo;
124
125 Ok(Scr { base, extension })
126}
127
128pub(crate) fn write_scr_field(scr: Scr) -> [u8; SCR_FIELD_LEN] {
130 let base = scr.base & BASE_MASK;
131 let extension = (scr.extension as u64) & EXT_MASK;
132
133 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]
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn scr_round_trip_boundary_values() {
162 let cases: &[(u64, u16)] = &[
163 (0, 0),
164 (1, 0),
165 (0, 1),
166 (BASE_MASK, EXT_MASK as u16),
167 (0x0001_9999, 0x0123),
168 (0x0123_4567, 0x1AB),
169 ];
170 for &(base_val, ext) in cases {
171 let scr = Scr {
172 base: base_val,
173 extension: ext,
174 };
175 let enc = write_scr_field(scr);
176 let dec = read_scr_field(&enc, "SCR").unwrap();
177 assert_eq!(dec, scr, "base={base_val:#x}, ext={ext:#x}");
178 }
179 }
180
181 #[test]
182 fn scr_zero_matches_fixture_pattern() {
183 let scr = Scr {
185 base: 0,
186 extension: 0,
187 };
188 let enc = write_scr_field(scr);
189 assert_eq!(enc, [0x44, 0x00, 0x04, 0x00, 0x04, 0x01]);
191 let dec = read_scr_field(&enc, "SCR").unwrap();
192 assert_eq!(dec, scr);
193 }
194
195 #[test]
196 fn scr_rejects_bad_prefix() {
197 let mut enc = write_scr_field(Scr {
198 base: 0,
199 extension: 0,
200 });
201 enc[0] |= 0x80; assert!(matches!(
203 read_scr_field(&enc, "SCR"),
204 Err(Error::BadScrPrefix(_))
205 ));
206 }
207
208 #[test]
209 fn scr_rejects_bad_marker() {
210 for byte_idx in [0usize, 2, 4] {
211 let mut enc = write_scr_field(Scr {
212 base: 0,
213 extension: 0,
214 });
215 enc[byte_idx] &= !0x04; assert!(matches!(
217 read_scr_field(&enc, "SCR"),
218 Err(Error::BadMarker(_))
219 ));
220 }
221 let mut enc = write_scr_field(Scr {
223 base: 0,
224 extension: 0,
225 });
226 enc[5] &= 0xFE;
227 assert!(matches!(
228 read_scr_field(&enc, "SCR"),
229 Err(Error::BadMarker(_))
230 ));
231 }
232
233 #[test]
234 fn scr_ticks() {
235 let scr = Scr {
236 base: 100,
237 extension: 50,
238 };
239 assert_eq!(scr.ticks(), 100 * 300 + 50);
240 }
241
242 #[test]
243 fn scr_seconds() {
244 let scr = Scr {
245 base: 27_000_000 / 300,
246 extension: 0,
247 };
248 assert!((scr.seconds() - 1.0).abs() < 1e-9);
249 }
250}