use crate::clock::{ClockAnchor, ClockSource};
use crate::error::{ClockError, Result};
pub trait PtpTimeProvider: Send + Sync {
fn ptp_now_ns(&self) -> Result<i64>;
}
pub struct PtpClock {
anchor: ClockAnchor,
provider: Box<dyn PtpTimeProvider>,
}
impl std::fmt::Debug for PtpClock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PtpClock")
.field("anchor", &self.anchor)
.field("provider", &"<dyn PtpTimeProvider>")
.finish()
}
}
impl PtpClock {
#[must_use]
pub fn with_provider(anchor: ClockAnchor, provider: Box<dyn PtpTimeProvider>) -> Self {
Self { anchor, provider }
}
#[must_use]
pub fn unavailable(anchor: ClockAnchor) -> Self {
Self {
anchor,
provider: Box::new(NoPtp),
}
}
#[must_use]
pub fn anchor(&self) -> ClockAnchor {
self.anchor
}
}
impl ClockSource for PtpClock {
fn ptp_now_ns(&self) -> Result<i64> {
self.provider.ptp_now_ns()
}
fn sample_to_ptp(&self, sample_idx: i64) -> i64 {
self.anchor
.sample_to_ptp_checked(sample_idx)
.unwrap_or(i64::MIN)
}
fn ptp_to_sample(&self, ptp_ns: i64) -> i64 {
self.anchor
.ptp_to_sample_checked(ptp_ns)
.unwrap_or(i64::MIN)
}
}
struct NoPtp;
impl PtpTimeProvider for NoPtp {
fn ptp_now_ns(&self) -> Result<i64> {
Err(ClockError::Unavailable(
"no PTP hardware timestamping available".into(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicI64, Ordering};
struct StubProvider {
t: AtomicI64,
}
impl PtpTimeProvider for StubProvider {
fn ptp_now_ns(&self) -> Result<i64> {
Ok(self.t.load(Ordering::Relaxed))
}
}
#[test]
fn unavailable_reports_unavailable() {
let clock = PtpClock::unavailable(ClockAnchor::new(48_000, 0, 0).unwrap());
let err = clock.ptp_now_ns().unwrap_err();
assert!(matches!(err, ClockError::Unavailable(_)));
}
#[test]
fn unavailable_conversions_still_work() {
let clock = PtpClock::unavailable(ClockAnchor::new(48_000, 0, 0).unwrap());
assert_eq!(clock.sample_to_ptp(48_000), 1_000_000_000);
assert_eq!(clock.ptp_to_sample(1_000_000_000), 48_000);
}
#[test]
fn with_provider_reads_live_timestamp() {
let provider = StubProvider {
t: AtomicI64::new(7_000_000_000),
};
let clock =
PtpClock::with_provider(ClockAnchor::new(48_000, 0, 0).unwrap(), Box::new(provider));
assert_eq!(clock.ptp_now_ns().unwrap(), 7_000_000_000);
}
#[test]
fn provider_not_synced_propagates() {
struct Unsynced;
impl PtpTimeProvider for Unsynced {
fn ptp_now_ns(&self) -> Result<i64> {
Err(ClockError::NotSynced("ptp master not locked".into()))
}
}
let clock =
PtpClock::with_provider(ClockAnchor::new(48_000, 0, 0).unwrap(), Box::new(Unsynced));
let err = clock.ptp_now_ns().unwrap_err();
assert!(matches!(err, ClockError::NotSynced(_)));
}
#[test]
fn dyn_clock_source_object_with_provider() {
let clock: Box<dyn ClockSource> = Box::new(PtpClock::unavailable(
ClockAnchor::new(48_000, 0, 0).unwrap(),
));
assert!(clock.ptp_now_ns().is_err());
assert_eq!(clock.sample_to_ptp(0), 0);
}
}