pub use broadcast_dash::utc_timing::UtcScheme;
use broadcast_dash::xs_datetime::{XsDateTime, parse_ntp_timestamp, parse_xs_datetime};
use super::TimingError;
use super::http_date::parse_http_date;
use broadcast_common::units::MICROS_PER_SEC;
pub const SCHEME_NTP: &str = "urn:mpeg:dash:utc:ntp:2014";
pub const SCHEME_SNTP: &str = "urn:mpeg:dash:utc:sntp:2014";
pub const SCHEME_HTTP_HEAD: &str = "urn:mpeg:dash:utc:http-head:2014";
pub const SCHEME_HTTP_XSDATE: &str = "urn:mpeg:dash:utc:http-xsdate:2014";
pub const SCHEME_HTTP_ISO: &str = "urn:mpeg:dash:utc:http-iso:2014";
pub const SCHEME_HTTP_NTP: &str = "urn:mpeg:dash:utc:http-ntp:2014";
pub const SCHEME_DIRECT: &str = "urn:mpeg:dash:utc:direct:2014";
pub const UTC_BODY_MAX_BYTES: usize = 4 * 1024;
pub const DATE_HEADER_HALF_SECOND_US: u64 = 500_000;
pub const HTTP_NTP_RESOLUTION_US: u64 = 1_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Exchange {
pub sent_us: u64,
pub received_us: u64,
}
impl Exchange {
#[must_use]
pub fn new(sent_us: u64, received_us: u64) -> Self {
Self {
sent_us,
received_us,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct UtcSample {
pub mono_us: u64,
pub utc_us: i64,
pub uncertainty_us: u64,
}
pub fn sample_from_exchange(
exchange: Exchange,
server_utc_us: i64,
resolution_us: u64,
max_rtt_us: u64,
) -> Result<UtcSample, TimingError> {
let rtt = exchange
.received_us
.checked_sub(exchange.sent_us)
.ok_or(TimingError::ExchangeReversed)?;
if rtt > max_rtt_us {
return Err(TimingError::RttTooLarge {
rtt_us: rtt,
max_us: max_rtt_us,
});
}
let half = rtt / 2;
Ok(UtcSample {
mono_us: exchange.sent_us + half,
utc_us: server_utc_us,
uncertainty_us: half.saturating_add(resolution_us),
})
}
pub fn sample_from_body(
scheme: UtcScheme,
body: &[u8],
exchange: Exchange,
max_rtt_us: u64,
) -> Result<UtcSample, TimingError> {
if body.len() > UTC_BODY_MAX_BYTES {
return Err(TimingError::BodyTooLarge {
max: UTC_BODY_MAX_BYTES,
});
}
match scheme {
UtcScheme::HttpXsdate | UtcScheme::HttpIso => {
let text = core::str::from_utf8(body).map_err(|_| TimingError::NotUtf8)?;
sample_from_datetime(parse_xs_datetime(text)?, exchange, max_rtt_us)
}
UtcScheme::HttpNtp => {
let utc = parse_ntp_timestamp(body)?;
sample_from_exchange(exchange, utc, HTTP_NTP_RESOLUTION_US, max_rtt_us)
}
other => Err(TimingError::SchemeNotSampled(other)),
}
}
pub fn sample_from_date_header(
date: &str,
exchange: Exchange,
max_rtt_us: u64,
) -> Result<UtcSample, TimingError> {
let seconds_us = parse_http_date(date)?;
let half_second =
i64::try_from(DATE_HEADER_HALF_SECOND_US).unwrap_or((MICROS_PER_SEC as i64) / 2);
sample_from_exchange(
exchange,
seconds_us.saturating_add(half_second),
DATE_HEADER_HALF_SECOND_US,
max_rtt_us,
)
}
pub fn sample_from_direct(
value: &str,
mpd_exchange: Exchange,
max_rtt_us: u64,
) -> Result<UtcSample, TimingError> {
sample_from_datetime(parse_xs_datetime(value)?, mpd_exchange, max_rtt_us)
}
fn sample_from_datetime(
dt: XsDateTime,
exchange: Exchange,
max_rtt_us: u64,
) -> Result<UtcSample, TimingError> {
if dt.fraction_digits > 0 {
return sample_from_exchange(exchange, dt.unix_us, dt.resolution_us(), max_rtt_us);
}
let half_second =
i64::try_from(DATE_HEADER_HALF_SECOND_US).unwrap_or((MICROS_PER_SEC as i64) / 2);
sample_from_exchange(
exchange,
dt.unix_us.saturating_add(half_second),
DATE_HEADER_HALF_SECOND_US,
max_rtt_us,
)
}