#[cfg(feature = "nts")]
use rkik_nts::{NtsClient, NtsClientConfig};
use chrono::{DateTime, Utc};
use std::time::Duration;
use crate::error::RkikError;
#[cfg(feature = "json")]
use serde::Serialize;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "json", derive(Serialize))]
pub struct NtsTimeResult {
pub network_time: DateTime<Utc>,
pub offset_ms: f64,
pub rtt_ms: f64,
pub authenticated: bool,
pub server: String,
pub nts_ke_data: Option<NtsKeData>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "json", derive(Serialize))]
pub struct NtsKeData {
pub ke_duration_ms: f64,
pub cookie_count: usize,
pub cookie_sizes: Vec<usize>,
pub aead_algorithm: String,
pub ntp_server: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub certificate: Option<CertificateInfo>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "json", derive(Serialize))]
pub struct CertificateInfo {
pub subject: String,
pub issuer: String,
pub valid_from: String,
pub valid_until: String,
pub serial_number: String,
pub san_dns_names: Vec<String>,
pub signature_algorithm: String,
pub public_key_algorithm: String,
pub fingerprint_sha256: String,
pub is_self_signed: bool,
}
#[cfg(feature = "nts")]
pub async fn query_nts(
server: &str,
nts_ke_port: Option<u16>,
timeout: Duration,
) -> Result<NtsTimeResult, RkikError> {
let mut config = NtsClientConfig::new(server);
if let Some(port) = nts_ke_port {
config = config.with_port(port);
}
config = config.with_timeout(timeout);
let mut client = NtsClient::new(config);
client
.connect()
.await
.map_err(|e| RkikError::Nts(format!("NTS-KE failed: {}", e)))?;
let time_snapshot = client
.get_time()
.await
.map_err(|e| RkikError::Nts(format!("NTS time query failed: {}", e)))?;
let nts_ke_data = client.nts_ke_info().map(|ke_result| {
let certificate = ke_result.certificate.as_ref().map(|cert| CertificateInfo {
subject: cert.subject.clone(),
issuer: cert.issuer.clone(),
valid_from: cert.valid_from.clone(),
valid_until: cert.valid_until.clone(),
serial_number: cert.serial_number.clone(),
san_dns_names: cert.san_dns_names.clone(),
signature_algorithm: cert.signature_algorithm.clone(),
public_key_algorithm: cert.public_key_algorithm.clone(),
fingerprint_sha256: cert.fingerprint_sha256.clone(),
is_self_signed: cert.is_self_signed,
});
NtsKeData {
ke_duration_ms: ke_result.ke_duration().as_secs_f64() * 1000.0,
cookie_count: ke_result.cookie_count(),
cookie_sizes: ke_result.cookie_sizes(),
aead_algorithm: ke_result.aead_algorithm.clone(),
ntp_server: ke_result.ntp_server.to_string(),
certificate,
}
});
let network_time: DateTime<Utc> = time_snapshot.network_time.into();
let offset_ms = time_snapshot.offset.as_secs_f64() * 1000.0;
let rtt_ms = time_snapshot.round_trip_delay.as_secs_f64() * 1000.0;
Ok(NtsTimeResult {
network_time,
offset_ms,
rtt_ms,
authenticated: time_snapshot.authenticated,
server: time_snapshot.server.clone(),
nts_ke_data,
})
}
#[cfg(not(feature = "nts"))]
pub async fn query_nts(
_server: &str,
_nts_ke_port: Option<u16>,
_timeout: Duration,
) -> Result<NtsTimeResult, RkikError> {
Err(RkikError::Other(
"NTS support not enabled. Compile with --features nts".to_string(),
))
}