use std::time::{Duration, Instant, SystemTime};
use hang::catalog::{MAX_SAFE_INTEGER, MOQ_EPOCH_UNIX_MILLIS};
fn wall_clock(wall: SystemTime) -> crate::Result<hang::catalog::Clock> {
let unix_micros = wall
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_micros())
.map_err(|_| hang::Error::InvalidWall(0))?;
let moq_epoch_micros = MOQ_EPOCH_UNIX_MILLIS as u128 * 1000;
if unix_micros < moq_epoch_micros {
return Err(hang::Error::InvalidWall(0).into());
}
let wall = u64::try_from(unix_micros - moq_epoch_micros).unwrap_or(u64::MAX);
if wall > MAX_SAFE_INTEGER {
return Err(hang::Error::InvalidWall(wall).into());
}
Ok(hang::catalog::Clock::new(moq_net::Timestamp::from_micros(wall)?)?)
}
#[derive(Clone, Copy, Debug)]
pub struct Clock {
epoch: Instant,
wall: hang::catalog::Clock,
}
impl Clock {
pub const TIMESCALE: moq_net::Timescale = moq_net::Timescale::MICRO;
const LEAD: Duration = Duration::from_secs(10);
pub fn new() -> Self {
let (now, wall) = (Instant::now(), SystemTime::now());
let (epoch, wall) = match (now.checked_sub(Self::LEAD), wall.checked_sub(Self::LEAD)) {
(Some(epoch), Some(wall)) => (epoch, wall),
_ => (now, wall),
};
Self::at(epoch, wall).expect("the current wall time is representable as a broadcast clock")
}
pub fn at(epoch: Instant, wall: SystemTime) -> crate::Result<Self> {
Ok(Self {
epoch,
wall: wall_clock(wall)?,
})
}
pub fn now(&self) -> moq_net::Timestamp {
moq_net::Timestamp::from_micros(self.epoch.elapsed().as_micros() as u64)
.expect("an instant elapsed duration fits in a timestamp")
}
pub(crate) fn capture(&self, at: Instant) -> crate::Result<moq_net::Timestamp> {
if at > Instant::now() {
return Err(crate::Error::InvalidCapture);
}
let elapsed = at
.checked_duration_since(self.epoch)
.ok_or(crate::Error::InvalidCapture)?;
Ok(moq_net::Timestamp::from_micros(elapsed.as_micros() as u64)
.expect("an instant elapsed duration fits in a timestamp"))
}
pub(crate) fn stamp<P>(
&self,
timed: moq_net::Timed<P, Instant>,
) -> crate::Result<(moq_net::Timed<P>, Option<moq_net::Timestamp>)> {
let captured = timed.at.map(|at| self.capture(at)).transpose()?;
let at = captured.unwrap_or_else(|| self.now());
Ok((
moq_net::Timed {
value: timed.value,
at: Some(at),
},
captured,
))
}
pub fn timescale(&self) -> moq_net::Timescale {
Self::TIMESCALE
}
pub fn wall(&self) -> hang::catalog::Clock {
self.wall
}
pub fn wall_clock(&self, pts: moq_net::Timestamp) -> crate::Result<SystemTime> {
self.wall.wall_clock(pts).map_err(crate::Error::from)
}
pub fn source(&self) -> SourceMap {
SourceMap::new(*self)
}
}
impl Default for Clock {
fn default() -> Self {
Self::new()
}
}
pub struct SourceMap {
anchor: Anchor,
lane: Lane,
}
impl SourceMap {
pub const MAX_REORDER: Duration = Duration::from_millis(500);
pub fn new(clock: Clock) -> Self {
Self {
anchor: Anchor::new(clock),
lane: Lane::default(),
}
}
pub fn clock(&self) -> Clock {
self.anchor.clock
}
pub fn translate(&mut self, pts: moq_net::Timestamp) -> crate::Result<moq_net::Timestamp> {
self.anchor.translate(&mut self.lane, pts)
}
pub fn translate_at(&mut self, pts: moq_net::Timestamp, now: u64) -> crate::Result<moq_net::Timestamp> {
self.anchor.translate_at(&mut self.lane, pts, now)
}
pub fn reset(&mut self, pts: moq_net::Timestamp) -> crate::Result<moq_net::Timestamp> {
self.lane.restart();
self.translate(pts)
}
pub fn reset_at(&mut self, pts: moq_net::Timestamp, now: u64) -> crate::Result<moq_net::Timestamp> {
self.lane.restart();
self.translate_at(pts, now)
}
}
#[derive(Debug)]
pub struct Anchor {
clock: Clock,
offset: Option<i128>,
generation: u64,
last_broadcast: Option<u128>,
last_end: Option<u128>,
last_arrival: Option<u64>,
}
#[derive(Debug, Default)]
pub struct Lane {
offset: Option<i128>,
generation: u64,
last_source: Option<u128>,
step: Option<u128>,
restart: bool,
}
impl Lane {
pub fn restart(&mut self) {
self.restart = true;
}
}
impl Anchor {
pub fn new(clock: Clock) -> Self {
Self {
clock,
offset: None,
generation: 0,
last_broadcast: None,
last_end: None,
last_arrival: None,
}
}
pub fn translate(&mut self, lane: &mut Lane, pts: moq_net::Timestamp) -> crate::Result<moq_net::Timestamp> {
self.translate_at(lane, pts, self.clock.now().value())
}
pub fn translate_at(
&mut self,
lane: &mut Lane,
pts: moq_net::Timestamp,
now: u64,
) -> crate::Result<moq_net::Timestamp> {
let src = pts.as_micros();
match self.offset {
None => self.offset = Some(now as i128 - src as i128),
Some(_) => {
let stepped_back = lane
.last_source
.is_some_and(|last| last > src + SourceMap::MAX_REORDER.as_micros());
if (lane.restart || stepped_back) && lane.offset.is_some() && lane.generation == self.generation {
self.reanchor(src, now);
}
if lane.restart || stepped_back {
lane.offset = None;
}
}
}
lane.restart = false;
let offset = *lane.offset.get_or_insert_with(|| {
lane.generation = self.generation;
self.offset.expect("anchored above")
});
let mapped = src as i128 + offset;
if mapped < 0 {
return Err(crate::Error::UnmappableTimestamp(format!(
"{pts:?} lands before the broadcast began"
)));
}
u64::try_from(mapped)
.ok()
.and_then(|mapped| moq_net::Timestamp::from_micros(mapped).ok())
.ok_or_else(|| {
crate::Error::UnmappableTimestamp(format!("{pts:?} lands outside the representable range"))
})?;
let scale = pts.scale();
let shift = offset * scale.as_u64() as i128 / 1_000_000;
let value = u64::try_from(pts.value() as i128 + shift)
.map_err(|_| crate::Error::UnmappableTimestamp(format!("{pts:?} lands outside the representable range")))?;
let translated = moq_net::Timestamp::new(value, scale)
.map_err(|_| crate::Error::UnmappableTimestamp(format!("{pts:?} lands outside the representable range")))?;
if let Some(step) = lane
.last_source
.and_then(|last| src.checked_sub(last))
.filter(|step| *step > 0)
{
lane.step = Some(lane.step.map_or(step, |min| min.min(step)));
}
lane.last_source = Some(src);
let start = mapped as u128;
self.last_broadcast = Some(self.last_broadcast.map_or(start, |last| last.max(start)));
self.extend_micros(start + lane.step.unwrap_or(0));
self.last_arrival = Some(now);
Ok(translated)
}
pub fn extend(&mut self, end: moq_net::Timestamp) {
self.extend_micros(end.as_micros());
}
fn extend_micros(&mut self, end: u128) {
self.last_end = Some(self.last_end.map_or(end, |last| last.max(end)));
}
fn reanchor(&mut self, src: u128, now: u64) {
let base = match (self.last_broadcast, self.last_arrival) {
(Some(last), Some(arrival)) => {
let idle = last + now.saturating_sub(arrival) as u128;
idle.max(self.last_end.unwrap_or(0))
}
_ => now as u128,
};
self.offset = Some(base as i128 - src as i128);
self.generation += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn epoch() -> Instant {
Instant::now()
}
fn moq_epoch() -> SystemTime {
SystemTime::UNIX_EPOCH + Duration::from_millis(MOQ_EPOCH_UNIX_MILLIS)
}
fn us(v: u64) -> moq_net::Timestamp {
moq_net::Timestamp::from_micros(v).unwrap()
}
#[test]
fn copies_share_one_epoch() {
let clock = Clock::at(epoch(), moq_epoch() + Duration::from_secs(1)).unwrap();
let shared = clock;
assert_eq!(clock.epoch, shared.epoch);
assert_eq!(clock.wall(), shared.wall());
}
#[test]
fn a_capture_maps_onto_the_clock() {
let now = Instant::now();
let clock = Clock::at(now - Duration::from_secs(5), moq_epoch()).unwrap();
assert_eq!(clock.capture(now - Duration::from_secs(2)).unwrap(), us(3_000_000));
assert!(matches!(
clock.capture(Instant::now() + Duration::from_secs(1)),
Err(crate::Error::InvalidCapture)
));
assert!(matches!(
clock.capture(now - Duration::from_secs(6)),
Err(crate::Error::InvalidCapture)
));
}
#[test]
fn section_advertises_wall_in_clock_timescale() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
assert_eq!(clock.wall().wall.value(), 0);
assert_eq!(clock.wall().wall.scale(), Clock::TIMESCALE);
let clock = Clock::at(epoch(), moq_epoch() + Duration::from_secs(1)).unwrap();
assert_eq!(clock.wall().wall.value(), 1_000_000);
}
#[test]
fn unrepresentable_walls_are_refused() {
let past_safe = MOQ_EPOCH_UNIX_MILLIS * 1000 + MAX_SAFE_INTEGER + 1;
let far = SystemTime::UNIX_EPOCH + Duration::from_micros(past_safe);
assert!(Clock::at(epoch(), far).is_err());
assert!(Clock::at(epoch(), SystemTime::UNIX_EPOCH).is_err());
assert!(Clock::at(epoch(), moq_epoch() - Duration::from_micros(1)).is_err());
assert_eq!(Clock::at(epoch(), moq_epoch()).unwrap().wall().wall.value(), 0);
}
#[test]
fn delayed_first_frame_anchors_live() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
let first = source.translate_at(us(5_000_000), 100_000).unwrap();
assert_eq!(first.as_micros(), 100_000);
let second = source.translate_at(us(6_000_000), 1_100_000).unwrap();
assert_eq!(second.as_micros(), 1_100_000);
assert_eq!(
clock.wall_clock(first).unwrap(),
moq_epoch() + Duration::from_micros(100_000)
);
}
#[test]
fn multiple_timescales_share_one_mapping() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut video = clock.source();
let mut audio = clock.source();
let v = video
.translate_at(
moq_net::Timestamp::new(180_000, moq_net::Timescale::new(90_000).unwrap()).unwrap(),
1_000_000,
)
.unwrap();
let a = audio
.translate_at(
moq_net::Timestamp::new(96_000, moq_net::Timescale::new(48_000).unwrap()).unwrap(),
1_000_000,
)
.unwrap();
assert_eq!(v.as_micros(), 1_000_000);
assert_eq!(a.as_micros(), 1_000_000);
assert_eq!(clock.wall_clock(v).unwrap(), clock.wall_clock(a).unwrap());
let v2 = video
.translate_at(
moq_net::Timestamp::new(270_000, moq_net::Timescale::new(90_000).unwrap()).unwrap(),
2_000_000,
)
.unwrap();
let a2 = audio
.translate_at(
moq_net::Timestamp::new(144_000, moq_net::Timescale::new(48_000).unwrap()).unwrap(),
2_000_000,
)
.unwrap();
assert_eq!(v2.as_micros(), 2_000_000);
assert_eq!(a2.as_micros(), 2_000_000);
}
#[test]
fn reset_translation_preserves_the_idle_gap() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
assert_eq!(source.translate_at(us(0), 1_000_000).unwrap().as_micros(), 1_000_000);
assert_eq!(
source.translate_at(us(2_000_000), 3_000_000).unwrap().as_micros(),
3_000_000
);
let wall_before = clock.wall();
let resumed = source.translate_at(us(0), 8_000_000).unwrap();
assert_eq!(resumed.as_micros(), 8_000_000);
assert_eq!(clock.wall(), wall_before);
let next = source.translate_at(us(1_000_000), 9_000_000).unwrap();
assert_eq!(next.as_micros(), 9_000_000);
}
#[test]
fn explicit_reset_marks_a_detected_restart() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
assert_eq!(source.translate_at(us(0), 1_000_000).unwrap().as_micros(), 1_000_000);
let resumed = source.reset_at(us(0), 4_000_000).unwrap();
assert_eq!(resumed.as_micros(), 4_000_000);
}
#[test]
fn bframe_reordering_within_a_group_survives() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
assert_eq!(source.translate_at(us(0), 1_000_000).unwrap().as_micros(), 1_000_000);
assert_eq!(
source.translate_at(us(40_000), 1_040_000).unwrap().as_micros(),
1_040_000
);
let reordered = source.translate_at(us(20_000), 1_040_000).unwrap();
assert_eq!(reordered.as_micros(), 1_020_000);
let next = source.translate_at(us(80_000), 1_080_000).unwrap();
assert_eq!(next.as_micros(), 1_080_000);
}
#[test]
fn mapping_past_u64_micros_is_refused() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
assert_eq!(source.translate_at(us(0), 0).unwrap().as_micros(), 0);
let huge = moq_net::Timestamp::from_secs(u64::MAX / 1_000_000 + 2).unwrap();
assert!(matches!(
source.translate_at(huge, 0),
Err(crate::Error::UnmappableTimestamp(_))
));
}
#[test]
fn fresh_clock_leaves_room_before_now() {
let clock = Clock::new();
assert!(clock.now().as_micros() >= Clock::LEAD.as_micros());
let now = clock.wall_clock(clock.now()).unwrap();
let drift = now
.duration_since(SystemTime::now())
.unwrap_or_else(|err| err.duration());
assert!(drift < Duration::from_secs(1), "wall + now is the current wall time");
}
#[test]
fn muxed_lanes_share_one_mapping() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut anchor = Anchor::new(clock);
let (mut video, mut audio) = (Lane::default(), Lane::default());
let v = anchor.translate_at(&mut video, us(10_800_000), 2_000_000).unwrap();
assert_eq!(v.as_micros(), 2_000_000);
let a = anchor.translate_at(&mut audio, us(10_000_000), 2_000_000).unwrap();
assert_eq!(a.as_micros(), 1_200_000);
let v = anchor.translate_at(&mut video, us(11_800_000), 3_000_000).unwrap();
assert_eq!(v.as_micros(), 3_000_000);
let a = anchor.translate_at(&mut audio, us(11_000_000), 3_000_000).unwrap();
assert_eq!(a.as_micros(), 2_200_000);
}
#[test]
fn muxed_restart_reanchors_once() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut anchor = Anchor::new(clock);
let (mut video, mut audio) = (Lane::default(), Lane::default());
anchor.translate_at(&mut video, us(5_000_000), 1_000_000).unwrap();
anchor.translate_at(&mut audio, us(5_000_000), 1_000_000).unwrap();
let v = anchor.translate_at(&mut video, us(0), 5_000_000).unwrap();
assert_eq!(v.as_micros(), 5_000_000);
let a = anchor.translate_at(&mut audio, us(20_000), 5_020_000).unwrap();
assert_eq!(a.as_micros(), 5_020_000);
video.restart();
audio.restart();
let v = anchor.translate_at(&mut video, us(0), 6_000_000).unwrap();
assert_eq!(v.as_micros(), 6_000_000);
let a = anchor.translate_at(&mut audio, us(0), 6_000_000).unwrap();
assert_eq!(a.as_micros(), 6_000_000);
}
#[test]
fn translation_keeps_the_source_timescale() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
let scale = moq_net::Timescale::new(90_000).unwrap();
let first = source
.translate_at(moq_net::Timestamp::new(3003, scale).unwrap(), 1_000_000)
.unwrap();
let second = source
.translate_at(moq_net::Timestamp::new(6006, scale).unwrap(), 1_033_000)
.unwrap();
assert_eq!(first.scale(), scale);
assert_eq!(second.value() - first.value(), 3003);
}
#[test]
fn mapping_before_the_broadcast_began_is_refused() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let mut source = clock.source();
assert_eq!(source.translate_at(us(100_000), 50_000).unwrap().as_micros(), 50_000);
assert!(matches!(
source.translate_at(us(0), 50_000),
Err(crate::Error::UnmappableTimestamp(_))
));
}
#[test]
fn wall_mapping_survives_a_system_clock_adjustment() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let before = clock.wall_clock(us(2_000_000)).unwrap();
assert_eq!(clock.wall_clock(us(2_000_000)).unwrap(), before);
assert_eq!(
before,
moq_epoch() + Duration::from_secs(2),
"archive records keep their wall times"
);
}
#[test]
fn wall_clock_validates_bounds() {
let clock = Clock::at(epoch(), moq_epoch()).unwrap();
let max = moq_net::Timestamp::from_micros((1u64 << 62) - 1).unwrap();
assert!(clock.wall_clock(max).is_err(), "past the JSON-safe range is refused");
}
}