dash_runtime/timing/
clock.rs1use broadcast_common::impl_spec_display;
8use broadcast_common::time::MonoUtc;
9
10use super::utc_timing::UtcSample;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[non_exhaustive]
15pub enum ClockSource {
16 MpdDate,
18 Host,
20 UtcTiming,
22}
23
24impl ClockSource {
25 #[must_use]
27 pub fn name(&self) -> &'static str {
28 match self {
29 Self::MpdDate => "mpd_date",
30 Self::Host => "host",
31 Self::UtcTiming => "utc_timing",
32 }
33 }
34}
35impl_spec_display!(ClockSource);
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub struct Anchor {
41 pub mono_us: u64,
43 pub utc_us: i64,
45 pub source: ClockSource,
47 pub uncertainty_us: u64,
49}
50
51impl Anchor {
52 #[must_use]
54 pub fn from_sample(sample: UtcSample, source: ClockSource) -> Self {
55 Self {
56 mono_us: sample.mono_us,
57 utc_us: sample.utc_us,
58 source,
59 uncertainty_us: sample.uncertainty_us,
60 }
61 }
62
63 #[must_use]
65 pub fn utc_at(&self, now_us: u64) -> i64 {
66 let m = MonoUtc::new(self.mono_us, i128::from(self.utc_us));
67 saturate(m.utc_at(now_us))
68 }
69
70 #[must_use]
73 pub fn mono_for_utc(&self, utc_us: i64) -> u64 {
74 let m = MonoUtc::new(self.mono_us, i128::from(self.utc_us));
75 let mono = m.mono_for_utc(i128::from(utc_us));
76 u64::try_from(mono.clamp(0, i128::from(u64::MAX))).unwrap_or(u64::MAX)
77 }
78}
79
80fn saturate(v: i128) -> i64 {
81 i64::try_from(v.clamp(i128::from(i64::MIN), i128::from(i64::MAX))).unwrap_or(i64::MAX)
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[non_exhaustive]
87pub enum AdoptReason {
88 First,
90 HigherTrust,
92 Refined,
94 Stale,
96 Step,
99 Downgrade,
101}
102
103impl AdoptReason {
104 #[must_use]
106 pub fn name(&self) -> &'static str {
107 match self {
108 Self::First => "first",
109 Self::HigherTrust => "higher_trust",
110 Self::Refined => "refined",
111 Self::Stale => "stale",
112 Self::Step => "step",
113 Self::Downgrade => "downgrade",
114 }
115 }
116}
117impl_spec_display!(AdoptReason);
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum RejectReason {
123 LowerTrust,
125 LessPrecise,
127}
128
129impl RejectReason {
130 #[must_use]
132 pub fn name(&self) -> &'static str {
133 match self {
134 Self::LowerTrust => "lower_trust",
135 Self::LessPrecise => "less_precise",
136 }
137 }
138}
139impl_spec_display!(RejectReason);
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[non_exhaustive]
144pub enum OfferOutcome {
145 Adopted(AdoptReason),
147 Rejected(RejectReason),
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153#[non_exhaustive]
154pub struct OfferPolicy {
155 pub resync_us: u64,
157 pub allow_downgrade: bool,
160}
161
162impl OfferPolicy {
163 #[must_use]
165 pub fn new(resync_us: u64, allow_downgrade: bool) -> Self {
166 Self {
167 resync_us,
168 allow_downgrade,
169 }
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
175pub struct UtcClock {
176 anchor: Option<Anchor>,
177}
178
179impl UtcClock {
180 #[must_use]
182 pub fn new() -> Self {
183 Self::default()
184 }
185
186 #[must_use]
188 pub fn anchor(&self) -> Option<&Anchor> {
189 self.anchor.as_ref()
190 }
191
192 #[must_use]
194 pub fn utc_at(&self, now_us: u64) -> Option<i64> {
195 self.anchor.map(|a| a.utc_at(now_us))
196 }
197
198 #[must_use]
200 pub fn mono_for_utc(&self, utc_us: i64) -> Option<u64> {
201 self.anchor.map(|a| a.mono_for_utc(utc_us))
202 }
203
204 pub fn offer(
213 &mut self,
214 sample: UtcSample,
215 source: ClockSource,
216 now_us: u64,
217 policy: OfferPolicy,
218 ) -> OfferOutcome {
219 let new = Anchor::from_sample(sample, source);
220 let Some(old) = self.anchor else {
221 self.anchor = Some(new);
222 return OfferOutcome::Adopted(AdoptReason::First);
223 };
224 let reason = if new.source > old.source {
225 AdoptReason::HigherTrust
226 } else if new.source < old.source {
227 if policy.allow_downgrade {
228 AdoptReason::Downgrade
229 } else {
230 return OfferOutcome::Rejected(RejectReason::LowerTrust);
231 }
232 } else {
233 let predicted = i128::from(old.utc_at(new.mono_us));
234 let diff = (i128::from(new.utc_us) - predicted).unsigned_abs();
235 let bars = u128::from(old.uncertainty_us) + u128::from(new.uncertainty_us);
236 if diff > bars {
237 AdoptReason::Step
238 } else if now_us.saturating_sub(old.mono_us) >= policy.resync_us {
239 AdoptReason::Stale
240 } else if new.uncertainty_us <= old.uncertainty_us {
241 AdoptReason::Refined
242 } else {
243 return OfferOutcome::Rejected(RejectReason::LessPrecise);
244 }
245 };
246 self.anchor = Some(new);
247 OfferOutcome::Adopted(reason)
248 }
249}