use crate::error::{Error, Result};
const BASE_MASK: u64 = (1 << 33) - 1;
const EXT_MASK: u64 = (1 << 9) - 1;
pub(crate) const SCR_FIELD_LEN: usize = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Scr {
pub base: u64,
pub extension: u16,
}
impl Scr {
#[must_use]
pub const fn ticks(self) -> u64 {
self.base * 300 + self.extension as u64
}
#[must_use]
pub fn seconds(self) -> f64 {
self.ticks() as f64 / 27_000_000.0
}
}
pub(crate) fn read_scr_field(b: &[u8], what: &'static str) -> Result<Scr> {
if b.len() < SCR_FIELD_LEN {
return Err(Error::BufferTooShort {
need: SCR_FIELD_LEN,
have: b.len(),
what,
});
}
let b0 = b[0];
let b1 = b[1];
let b2 = b[2];
let b3 = b[3];
let b4 = b[4];
let b5 = b[5];
if (b0 >> 6) != 0b01 {
return Err(Error::BadScrPrefix(b0 >> 6));
}
if (b0 & 0x04) == 0 {
return Err(Error::BadMarker(what));
}
if (b2 & 0x04) == 0 {
return Err(Error::BadMarker(what));
}
if (b4 & 0x04) == 0 {
return Err(Error::BadMarker(what));
}
if (b5 & 0x01) == 0 {
return Err(Error::BadMarker(what));
}
let hi = u64::from((b0 >> 3) & 0x07);
let mid_hi = u64::from(b0 & 0x03);
let mid_mid = u64::from(b1);
let mid_lo = u64::from((b2 >> 3) & 0x1F);
let lo_hi = u64::from(b2 & 0x03);
let lo_mid = u64::from(b3);
let lo_lo = u64::from((b4 >> 3) & 0x1F);
let base = (hi << 30)
| (mid_hi << 28)
| (mid_mid << 20)
| (mid_lo << 15)
| (lo_hi << 13)
| (lo_mid << 5)
| lo_lo;
let ext_hi = u16::from(b4 & 0x03);
let ext_lo = u16::from(b5 >> 1);
let extension = (ext_hi << 7) | ext_lo;
Ok(Scr { base, extension })
}
pub(crate) fn write_scr_field(scr: Scr) -> [u8; SCR_FIELD_LEN] {
let base = scr.base & BASE_MASK;
let extension = (scr.extension as u64) & EXT_MASK;
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]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scr_round_trip_boundary_values() {
let cases: &[(u64, u16)] = &[
(0, 0),
(1, 0),
(0, 1),
(BASE_MASK, EXT_MASK as u16),
(0x0001_9999, 0x0123),
(0x0123_4567, 0x1AB),
];
for &(base_val, ext) in cases {
let scr = Scr {
base: base_val,
extension: ext,
};
let enc = write_scr_field(scr);
let dec = read_scr_field(&enc, "SCR").unwrap();
assert_eq!(dec, scr, "base={base_val:#x}, ext={ext:#x}");
}
}
#[test]
fn scr_zero_matches_fixture_pattern() {
let scr = Scr {
base: 0,
extension: 0,
};
let enc = write_scr_field(scr);
assert_eq!(enc, [0x44, 0x00, 0x04, 0x00, 0x04, 0x01]);
let dec = read_scr_field(&enc, "SCR").unwrap();
assert_eq!(dec, scr);
}
#[test]
fn scr_rejects_bad_prefix() {
let mut enc = write_scr_field(Scr {
base: 0,
extension: 0,
});
enc[0] |= 0x80; assert!(matches!(
read_scr_field(&enc, "SCR"),
Err(Error::BadScrPrefix(_))
));
}
#[test]
fn scr_rejects_bad_marker() {
for byte_idx in [0usize, 2, 4] {
let mut enc = write_scr_field(Scr {
base: 0,
extension: 0,
});
enc[byte_idx] &= !0x04; assert!(matches!(
read_scr_field(&enc, "SCR"),
Err(Error::BadMarker(_))
));
}
let mut enc = write_scr_field(Scr {
base: 0,
extension: 0,
});
enc[5] &= 0xFE;
assert!(matches!(
read_scr_field(&enc, "SCR"),
Err(Error::BadMarker(_))
));
}
#[test]
fn scr_ticks() {
let scr = Scr {
base: 100,
extension: 50,
};
assert_eq!(scr.ticks(), 100 * 300 + 50);
}
#[test]
fn scr_seconds() {
let scr = Scr {
base: 27_000_000 / 300,
extension: 0,
};
assert!((scr.seconds() - 1.0).abs() < 1e-9);
}
}