use broadcast_common::impl_spec_display;
use super::utc_timing::UtcSample;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ClockSource {
MpdDate,
Host,
UtcTiming,
}
impl ClockSource {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::MpdDate => "mpd_date",
Self::Host => "host",
Self::UtcTiming => "utc_timing",
}
}
}
impl_spec_display!(ClockSource);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Anchor {
pub mono_us: u64,
pub utc_us: i64,
pub source: ClockSource,
pub uncertainty_us: u64,
}
impl Anchor {
#[must_use]
pub fn from_sample(sample: UtcSample, source: ClockSource) -> Self {
Self {
mono_us: sample.mono_us,
utc_us: sample.utc_us,
source,
uncertainty_us: sample.uncertainty_us,
}
}
#[must_use]
pub fn utc_at(&self, now_us: u64) -> i64 {
let delta = i128::from(now_us) - i128::from(self.mono_us);
saturate(i128::from(self.utc_us) + delta)
}
#[must_use]
pub fn mono_for_utc(&self, utc_us: i64) -> u64 {
let delta = i128::from(utc_us) - i128::from(self.utc_us);
let mono = i128::from(self.mono_us) + delta;
u64::try_from(mono.clamp(0, i128::from(u64::MAX))).unwrap_or(u64::MAX)
}
}
fn saturate(v: i128) -> i64 {
i64::try_from(v.clamp(i128::from(i64::MIN), i128::from(i64::MAX))).unwrap_or(i64::MAX)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AdoptReason {
First,
HigherTrust,
Refined,
Stale,
Step,
Downgrade,
}
impl AdoptReason {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::First => "first",
Self::HigherTrust => "higher_trust",
Self::Refined => "refined",
Self::Stale => "stale",
Self::Step => "step",
Self::Downgrade => "downgrade",
}
}
}
impl_spec_display!(AdoptReason);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RejectReason {
LowerTrust,
LessPrecise,
}
impl RejectReason {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::LowerTrust => "lower_trust",
Self::LessPrecise => "less_precise",
}
}
}
impl_spec_display!(RejectReason);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum OfferOutcome {
Adopted(AdoptReason),
Rejected(RejectReason),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct OfferPolicy {
pub resync_us: u64,
pub allow_downgrade: bool,
}
impl OfferPolicy {
#[must_use]
pub fn new(resync_us: u64, allow_downgrade: bool) -> Self {
Self {
resync_us,
allow_downgrade,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct UtcClock {
anchor: Option<Anchor>,
}
impl UtcClock {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn anchor(&self) -> Option<&Anchor> {
self.anchor.as_ref()
}
#[must_use]
pub fn utc_at(&self, now_us: u64) -> Option<i64> {
self.anchor.map(|a| a.utc_at(now_us))
}
#[must_use]
pub fn mono_for_utc(&self, utc_us: i64) -> Option<u64> {
self.anchor.map(|a| a.mono_for_utc(utc_us))
}
pub fn offer(
&mut self,
sample: UtcSample,
source: ClockSource,
now_us: u64,
policy: OfferPolicy,
) -> OfferOutcome {
let new = Anchor::from_sample(sample, source);
let Some(old) = self.anchor else {
self.anchor = Some(new);
return OfferOutcome::Adopted(AdoptReason::First);
};
let reason = if new.source > old.source {
AdoptReason::HigherTrust
} else if new.source < old.source {
if policy.allow_downgrade {
AdoptReason::Downgrade
} else {
return OfferOutcome::Rejected(RejectReason::LowerTrust);
}
} else {
let predicted = i128::from(old.utc_at(new.mono_us));
let diff = (i128::from(new.utc_us) - predicted).unsigned_abs();
let bars = u128::from(old.uncertainty_us) + u128::from(new.uncertainty_us);
if diff > bars {
AdoptReason::Step
} else if now_us.saturating_sub(old.mono_us) >= policy.resync_us {
AdoptReason::Stale
} else if new.uncertainty_us <= old.uncertainty_us {
AdoptReason::Refined
} else {
return OfferOutcome::Rejected(RejectReason::LessPrecise);
}
};
self.anchor = Some(new);
OfferOutcome::Adopted(reason)
}
}