dash_runtime/timing/
utc_timing.rs1pub use broadcast_dash::utc_timing::UtcScheme;
10use broadcast_dash::xs_datetime::{XsDateTime, parse_ntp_timestamp, parse_xs_datetime};
11
12use super::TimingError;
13use super::http_date::parse_http_date;
14use broadcast_common::units::MICROS_PER_SEC;
15
16pub const SCHEME_NTP: &str = "urn:mpeg:dash:utc:ntp:2014";
18pub const SCHEME_SNTP: &str = "urn:mpeg:dash:utc:sntp:2014";
20pub const SCHEME_HTTP_HEAD: &str = "urn:mpeg:dash:utc:http-head:2014";
22pub const SCHEME_HTTP_XSDATE: &str = "urn:mpeg:dash:utc:http-xsdate:2014";
24pub const SCHEME_HTTP_ISO: &str = "urn:mpeg:dash:utc:http-iso:2014";
26pub const SCHEME_HTTP_NTP: &str = "urn:mpeg:dash:utc:http-ntp:2014";
28pub const SCHEME_DIRECT: &str = "urn:mpeg:dash:utc:direct:2014";
30
31pub const UTC_BODY_MAX_BYTES: usize = 4 * 1024;
33pub const DATE_HEADER_HALF_SECOND_US: u64 = 500_000;
36pub const HTTP_NTP_RESOLUTION_US: u64 = 1_000;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[non_exhaustive]
42pub struct Exchange {
43 pub sent_us: u64,
45 pub received_us: u64,
47}
48
49impl Exchange {
50 #[must_use]
52 pub fn new(sent_us: u64, received_us: u64) -> Self {
53 Self {
54 sent_us,
55 received_us,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63#[non_exhaustive]
64pub struct UtcSample {
65 pub mono_us: u64,
67 pub utc_us: i64,
69 pub uncertainty_us: u64,
71}
72
73pub fn sample_from_exchange(
83 exchange: Exchange,
84 server_utc_us: i64,
85 resolution_us: u64,
86 max_rtt_us: u64,
87) -> Result<UtcSample, TimingError> {
88 let rtt = exchange
89 .received_us
90 .checked_sub(exchange.sent_us)
91 .ok_or(TimingError::ExchangeReversed)?;
92 if rtt > max_rtt_us {
93 return Err(TimingError::RttTooLarge {
94 rtt_us: rtt,
95 max_us: max_rtt_us,
96 });
97 }
98 let half = rtt / 2;
99 Ok(UtcSample {
100 mono_us: exchange.sent_us + half,
101 utc_us: server_utc_us,
102 uncertainty_us: half.saturating_add(resolution_us),
103 })
104}
105
106pub fn sample_from_body(
113 scheme: UtcScheme,
114 body: &[u8],
115 exchange: Exchange,
116 max_rtt_us: u64,
117) -> Result<UtcSample, TimingError> {
118 if body.len() > UTC_BODY_MAX_BYTES {
119 return Err(TimingError::BodyTooLarge {
120 max: UTC_BODY_MAX_BYTES,
121 });
122 }
123 match scheme {
124 UtcScheme::HttpXsdate | UtcScheme::HttpIso => {
125 let text = core::str::from_utf8(body).map_err(|_| TimingError::NotUtf8)?;
126 sample_from_datetime(parse_xs_datetime(text)?, exchange, max_rtt_us)
127 }
128 UtcScheme::HttpNtp => {
129 let utc = parse_ntp_timestamp(body)?;
130 sample_from_exchange(exchange, utc, HTTP_NTP_RESOLUTION_US, max_rtt_us)
131 }
132 other => Err(TimingError::SchemeNotSampled(other)),
133 }
134}
135
136pub fn sample_from_date_header(
141 date: &str,
142 exchange: Exchange,
143 max_rtt_us: u64,
144) -> Result<UtcSample, TimingError> {
145 let seconds_us = parse_http_date(date)?;
146 let half_second =
147 i64::try_from(DATE_HEADER_HALF_SECOND_US).unwrap_or((MICROS_PER_SEC as i64) / 2);
148 sample_from_exchange(
149 exchange,
150 seconds_us.saturating_add(half_second),
151 DATE_HEADER_HALF_SECOND_US,
152 max_rtt_us,
153 )
154}
155
156pub fn sample_from_direct(
163 value: &str,
164 mpd_exchange: Exchange,
165 max_rtt_us: u64,
166) -> Result<UtcSample, TimingError> {
167 sample_from_datetime(parse_xs_datetime(value)?, mpd_exchange, max_rtt_us)
168}
169
170fn sample_from_datetime(
171 dt: XsDateTime,
172 exchange: Exchange,
173 max_rtt_us: u64,
174) -> Result<UtcSample, TimingError> {
175 if dt.fraction_digits > 0 {
176 return sample_from_exchange(exchange, dt.unix_us, dt.resolution_us(), max_rtt_us);
177 }
178 let half_second =
179 i64::try_from(DATE_HEADER_HALF_SECOND_US).unwrap_or((MICROS_PER_SEC as i64) / 2);
180 sample_from_exchange(
181 exchange,
182 dt.unix_us.saturating_add(half_second),
183 DATE_HEADER_HALF_SECOND_US,
184 max_rtt_us,
185 )
186}