#![cfg(all(feature = "ptp", target_os = "linux"))]
use chrono::{Local, Utc};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::net::IpAddr;
use std::time::{Duration, Instant};
use tokio::time;
use crate::domain::ptp::{
ClockIdentity, ClockQuality, PacketStats, PortIdentity, PtpDiagnostics, PtpProbeResult,
PtpTarget, TimeSource,
};
use crate::error::RkikError;
pub async fn query_ptp(
target_name: &str,
ip: IpAddr,
domain: u8,
event_port: u16,
general_port: u16,
hw_timestamping: bool,
timeout: Duration,
verbose: bool,
) -> Result<PtpProbeResult, RkikError> {
time::timeout(timeout, async move {
let start = Instant::now();
let seed = build_seed(
target_name,
ip,
domain,
event_port,
general_port,
hw_timestamping,
);
let master_identity = derive_clock_identity(seed);
let master_port = PortIdentity {
clock_identity: master_identity,
port_number: 1,
};
let clock_quality = derive_clock_quality(seed);
let time_source = derive_time_source(seed);
let offset_ns = derive_offset(seed);
let mean_path_delay_ns = derive_path_delay(seed);
let utc = Utc::now();
let local = Local::now();
let timestamp = utc.timestamp();
let diagnostics = verbose.then(|| PtpDiagnostics {
master_port_identity: master_port,
hardware_timestamping: hw_timestamping,
timestamp_mode: if hw_timestamping {
"hardware timestamping (simulated)".to_string()
} else {
"software timestamping (simulated)".to_string()
},
steps_removed: ((seed >> 3) % 4) as u16,
current_utc_offset: 37,
current_utc_offset_valid: true,
leap59: false,
leap61: false,
time_traceable: (seed & 0x1) == 0,
frequency_traceable: (seed & 0x2) == 0,
ptp_timescale: true,
packet_stats: derive_packet_stats(seed),
measurement_duration_ms: start.elapsed().as_secs_f64() * 1000.0,
});
PtpProbeResult {
target: PtpTarget {
name: target_name.to_string(),
ip,
domain,
event_port,
general_port,
},
offset_ns,
mean_path_delay_ns,
master_identity,
clock_quality,
time_source,
utc,
local,
timestamp,
diagnostics,
}
})
.await
.map_err(|_| RkikError::Other(format!("ptp query timed out after {:?}", timeout)))
}
fn build_seed(
name: &str,
ip: IpAddr,
domain: u8,
event_port: u16,
general_port: u16,
hw_timestamping: bool,
) -> u64 {
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
ip.hash(&mut hasher);
domain.hash(&mut hasher);
event_port.hash(&mut hasher);
general_port.hash(&mut hasher);
hw_timestamping.hash(&mut hasher);
hasher.finish()
}
fn derive_clock_identity(seed: u64) -> ClockIdentity {
let mut bytes = [0u8; 8];
for (i, byte) in bytes.iter_mut().enumerate() {
*byte = ((seed >> (i * 8)) & 0xFF) as u8;
}
ClockIdentity(bytes)
}
fn derive_clock_quality(seed: u64) -> ClockQuality {
ClockQuality {
clock_class: match seed & 0x7 {
0 => 6,
1 => 7,
2 => 13,
3 => 52,
4 => 58,
5 => 187,
6 => 248,
_ => 255,
},
clock_accuracy: 0x20 + ((seed >> 8) as u8 % 0x10),
offset_scaled_log_variance: ((seed >> 16) as u16) | 0x0100,
}
}
fn derive_time_source(seed: u64) -> TimeSource {
match seed % 7 {
0 => TimeSource::AtomicClock,
1 => TimeSource::Gps,
2 => TimeSource::TerrestrialRadio,
3 => TimeSource::Ptp,
4 => TimeSource::Ntp,
5 => TimeSource::HandSet,
_ => TimeSource::InternalOscillator,
}
}
fn derive_offset(seed: u64) -> i64 {
let range = 200_000i64; let raw = (seed as i64 % (range * 2)) - range;
raw * 10 }
fn derive_path_delay(seed: u64) -> i64 {
let base = ((seed >> 11) % 50_000) as i64; (base + 1_000) * 10 }
fn derive_packet_stats(seed: u64) -> PacketStats {
PacketStats {
sync_sent: ((seed >> 5) % 3) as u32,
sync_received: ((seed >> 8) % 10) as u32 + 1,
follow_up_received: ((seed >> 10) % 8) as u32,
delay_req_sent: ((seed >> 12) % 5) as u32 + 1,
delay_resp_received: ((seed >> 14) % 5) as u32 + 1,
announce_received: ((seed >> 16) % 3) as u32,
}
}