use broadcast_common::impl_spec_display;
use super::TimingError;
use super::datetime::{parse_http_date, parse_ntp_timestamp, parse_xs_datetime};
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;
const MICROS_PER_SECOND: i64 = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum UtcScheme {
Ntp,
Sntp,
HttpHead,
HttpXsdate,
HttpIso,
HttpNtp,
Direct,
Unknown,
}
impl UtcScheme {
#[must_use]
pub fn from_scheme_id_uri(uri: &str) -> Self {
match uri {
SCHEME_NTP => Self::Ntp,
SCHEME_SNTP => Self::Sntp,
SCHEME_HTTP_HEAD => Self::HttpHead,
SCHEME_HTTP_XSDATE => Self::HttpXsdate,
SCHEME_HTTP_ISO => Self::HttpIso,
SCHEME_HTTP_NTP => Self::HttpNtp,
SCHEME_DIRECT => Self::Direct,
_ => Self::Unknown,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Ntp => "ntp",
Self::Sntp => "sntp",
Self::HttpHead => "http_head",
Self::HttpXsdate => "http_xsdate",
Self::HttpIso => "http_iso",
Self::HttpNtp => "http_ntp",
Self::Direct => "direct",
Self::Unknown => "unknown",
}
}
#[must_use]
pub fn is_actionable(&self) -> bool {
!matches!(self, Self::Ntp | Self::Sntp | Self::Unknown)
}
#[must_use]
pub fn needs_request(&self) -> bool {
matches!(
self,
Self::HttpHead | Self::HttpXsdate | Self::HttpIso | Self::HttpNtp
)
}
}
impl_spec_display!(UtcScheme);
#[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)?;
let dt = parse_xs_datetime(text)?;
sample_from_exchange(exchange, dt.unix_us, dt.resolution_us(), 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_SECOND / 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> {
let dt = parse_xs_datetime(value)?;
sample_from_exchange(mpd_exchange, dt.unix_us, dt.resolution_us(), max_rtt_us)
}