use tracing::debug;
use crate::protocol;
use super::{ClientState, ServerSystemState};
pub(crate) fn build_interleaved_response(
request: &protocol::Packet,
server_state: &ServerSystemState,
client_state: &ClientState,
t2: protocol::TimestampFormat,
) -> Option<protocol::Packet> {
if client_state.last_t3.seconds == 0 && client_state.last_t3.fraction == 0 {
return None;
}
if request.origin_timestamp != client_state.last_t3 {
return None;
}
debug!(
"interleaved mode detected for client (origin matches prev T3: {:?})",
client_state.last_t3
);
Some(protocol::Packet {
leap_indicator: server_state.leap_indicator,
version: request.version,
mode: protocol::Mode::Server,
stratum: server_state.stratum,
poll: request.poll,
precision: server_state.precision,
root_delay: server_state.root_delay,
root_dispersion: server_state.root_dispersion,
reference_id: server_state.reference_id,
reference_timestamp: server_state.reference_timestamp,
origin_timestamp: client_state.last_client_xmt,
receive_timestamp: client_state.last_t2,
transmit_timestamp: t2, })
}
pub(crate) fn update_client_state(
client: &mut ClientState,
t2: protocol::TimestampFormat,
t3: protocol::TimestampFormat,
client_xmt: protocol::TimestampFormat,
) {
client.last_t2 = t2;
client.last_t3 = t3;
client.last_client_xmt = client_xmt;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::{
LeapIndicator, Mode, Packet, ReferenceIdentifier, ShortFormat, Stratum, TimestampFormat,
Version,
};
fn ts(secs: u32, frac: u32) -> TimestampFormat {
TimestampFormat {
seconds: secs,
fraction: frac,
}
}
fn test_request(origin: TimestampFormat, xmt: TimestampFormat) -> Packet {
Packet {
leap_indicator: LeapIndicator::NoWarning,
version: Version::V4,
mode: Mode::Client,
stratum: Stratum::UNSPECIFIED,
poll: 6,
precision: 0,
root_delay: ShortFormat::default(),
root_dispersion: ShortFormat::default(),
reference_id: ReferenceIdentifier::default(),
reference_timestamp: TimestampFormat::default(),
origin_timestamp: origin,
receive_timestamp: TimestampFormat::default(),
transmit_timestamp: xmt,
}
}
fn test_server_state() -> ServerSystemState {
ServerSystemState::default()
}
fn test_client_state(
last_t2: TimestampFormat,
last_t3: TimestampFormat,
last_xmt: TimestampFormat,
) -> ClientState {
let mut cs = ClientState::new(std::time::Instant::now());
cs.last_t2 = last_t2;
cs.last_t3 = last_t3;
cs.last_client_xmt = last_xmt;
cs
}
#[test]
fn no_history_returns_none() {
let req = test_request(TimestampFormat::default(), ts(100, 0));
let state = test_server_state();
let cs = test_client_state(
TimestampFormat::default(),
ts(0, 0),
TimestampFormat::default(),
);
let t2 = ts(200, 0);
assert!(build_interleaved_response(&req, &state, &cs, t2).is_none());
}
#[test]
fn origin_matches_returns_some() {
let last_t3 = ts(500, 0);
let req = test_request(last_t3, ts(600, 0));
let state = test_server_state();
let cs = test_client_state(ts(400, 0), last_t3, ts(300, 0));
let t2 = ts(700, 0);
assert!(build_interleaved_response(&req, &state, &cs, t2).is_some());
}
#[test]
fn origin_mismatch_returns_none() {
let last_t3 = ts(500, 0);
let req = test_request(ts(999, 0), ts(600, 0));
let state = test_server_state();
let cs = test_client_state(ts(400, 0), last_t3, ts(300, 0));
let t2 = ts(700, 0);
assert!(build_interleaved_response(&req, &state, &cs, t2).is_none());
}
#[test]
fn interleaved_origin_is_last_client_xmt() {
let last_t3 = ts(500, 0);
let last_xmt = ts(300, 0);
let req = test_request(last_t3, ts(600, 0));
let state = test_server_state();
let cs = test_client_state(ts(400, 0), last_t3, last_xmt);
let t2 = ts(700, 0);
let resp = build_interleaved_response(&req, &state, &cs, t2).unwrap();
assert_eq!(resp.origin_timestamp, last_xmt);
}
#[test]
fn interleaved_receive_is_last_t2() {
let last_t3 = ts(500, 0);
let last_t2 = ts(400, 0);
let req = test_request(last_t3, ts(600, 0));
let state = test_server_state();
let cs = test_client_state(last_t2, last_t3, ts(300, 0));
let t2 = ts(700, 0);
let resp = build_interleaved_response(&req, &state, &cs, t2).unwrap();
assert_eq!(resp.receive_timestamp, last_t2);
}
#[test]
fn update_client_state_sets_fields() {
let mut cs = ClientState::new(std::time::Instant::now());
let t2 = ts(100, 1);
let t3 = ts(200, 2);
let xmt = ts(300, 3);
update_client_state(&mut cs, t2, t3, xmt);
assert_eq!(cs.last_t2, t2);
assert_eq!(cs.last_t3, t3);
assert_eq!(cs.last_client_xmt, xmt);
}
}