dash_runtime/timing/
clock.rs1use broadcast_common::impl_spec_display;
8
9use super::utc_timing::UtcSample;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[non_exhaustive]
14pub enum ClockSource {
15 MpdDate,
17 Host,
19 UtcTiming,
21}
22
23impl ClockSource {
24 #[must_use]
26 pub fn name(&self) -> &'static str {
27 match self {
28 Self::MpdDate => "mpd_date",
29 Self::Host => "host",
30 Self::UtcTiming => "utc_timing",
31 }
32 }
33}
34impl_spec_display!(ClockSource);
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38#[non_exhaustive]
39pub struct Anchor {
40 pub mono_us: u64,
42 pub utc_us: i64,
44 pub source: ClockSource,
46 pub uncertainty_us: u64,
48}
49
50impl Anchor {
51 #[must_use]
53 pub fn from_sample(sample: UtcSample, source: ClockSource) -> Self {
54 Self {
55 mono_us: sample.mono_us,
56 utc_us: sample.utc_us,
57 source,
58 uncertainty_us: sample.uncertainty_us,
59 }
60 }
61
62 #[must_use]
64 pub fn utc_at(&self, now_us: u64) -> i64 {
65 let delta = i128::from(now_us) - i128::from(self.mono_us);
66 saturate(i128::from(self.utc_us) + delta)
67 }
68
69 #[must_use]
72 pub fn mono_for_utc(&self, utc_us: i64) -> u64 {
73 let delta = i128::from(utc_us) - i128::from(self.utc_us);
74 let mono = i128::from(self.mono_us) + delta;
75 u64::try_from(mono.clamp(0, i128::from(u64::MAX))).unwrap_or(u64::MAX)
76 }
77}
78
79fn saturate(v: i128) -> i64 {
80 i64::try_from(v.clamp(i128::from(i64::MIN), i128::from(i64::MAX))).unwrap_or(i64::MAX)
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[non_exhaustive]
86pub enum AdoptReason {
87 First,
89 HigherTrust,
91 Refined,
93 Stale,
95 Step,
98 Downgrade,
100}
101
102impl AdoptReason {
103 #[must_use]
105 pub fn name(&self) -> &'static str {
106 match self {
107 Self::First => "first",
108 Self::HigherTrust => "higher_trust",
109 Self::Refined => "refined",
110 Self::Stale => "stale",
111 Self::Step => "step",
112 Self::Downgrade => "downgrade",
113 }
114 }
115}
116impl_spec_display!(AdoptReason);
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[non_exhaustive]
121pub enum RejectReason {
122 LowerTrust,
124 LessPrecise,
126}
127
128impl RejectReason {
129 #[must_use]
131 pub fn name(&self) -> &'static str {
132 match self {
133 Self::LowerTrust => "lower_trust",
134 Self::LessPrecise => "less_precise",
135 }
136 }
137}
138impl_spec_display!(RejectReason);
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142#[non_exhaustive]
143pub enum OfferOutcome {
144 Adopted(AdoptReason),
146 Rejected(RejectReason),
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[non_exhaustive]
153pub struct OfferPolicy {
154 pub resync_us: u64,
156 pub allow_downgrade: bool,
159}
160
161impl OfferPolicy {
162 #[must_use]
164 pub fn new(resync_us: u64, allow_downgrade: bool) -> Self {
165 Self {
166 resync_us,
167 allow_downgrade,
168 }
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
174pub struct UtcClock {
175 anchor: Option<Anchor>,
176}
177
178impl UtcClock {
179 #[must_use]
181 pub fn new() -> Self {
182 Self::default()
183 }
184
185 #[must_use]
187 pub fn anchor(&self) -> Option<&Anchor> {
188 self.anchor.as_ref()
189 }
190
191 #[must_use]
193 pub fn utc_at(&self, now_us: u64) -> Option<i64> {
194 self.anchor.map(|a| a.utc_at(now_us))
195 }
196
197 #[must_use]
199 pub fn mono_for_utc(&self, utc_us: i64) -> Option<u64> {
200 self.anchor.map(|a| a.mono_for_utc(utc_us))
201 }
202
203 pub fn offer(
212 &mut self,
213 sample: UtcSample,
214 source: ClockSource,
215 now_us: u64,
216 policy: OfferPolicy,
217 ) -> OfferOutcome {
218 let new = Anchor::from_sample(sample, source);
219 let Some(old) = self.anchor else {
220 self.anchor = Some(new);
221 return OfferOutcome::Adopted(AdoptReason::First);
222 };
223 let reason = if new.source > old.source {
224 AdoptReason::HigherTrust
225 } else if new.source < old.source {
226 if policy.allow_downgrade {
227 AdoptReason::Downgrade
228 } else {
229 return OfferOutcome::Rejected(RejectReason::LowerTrust);
230 }
231 } else {
232 let predicted = i128::from(old.utc_at(new.mono_us));
233 let diff = (i128::from(new.utc_us) - predicted).unsigned_abs();
234 let bars = u128::from(old.uncertainty_us) + u128::from(new.uncertainty_us);
235 if diff > bars {
236 AdoptReason::Step
237 } else if now_us.saturating_sub(old.mono_us) >= policy.resync_us {
238 AdoptReason::Stale
239 } else if new.uncertainty_us <= old.uncertainty_us {
240 AdoptReason::Refined
241 } else {
242 return OfferOutcome::Rejected(RejectReason::LessPrecise);
243 }
244 };
245 self.anchor = Some(new);
246 OfferOutcome::Adopted(reason)
247 }
248}