use std::time::Duration;
use irtt_proto::{Clock, Params, StampAt};
use super::support::{
client_params, core_with_tokens, echo_request, expect_echo_reply, expect_normal_open_reply,
open_request, peer, unthrottled, ScriptedTokens,
};
use crate::{ServerConfig, TimestampAllowance};
const TOKEN_A: u64 = 0x2f45_9a01_c37d_6e88;
fn negotiated(config: ServerConfig, requested: &Params) -> Params {
let mut core = core_with_tokens(config, ScriptedTokens::new([TOKEN_A]));
let packet = core
.handle_datagram(peer(), &open_request(requested, None))
.unwrap()
.expect("the open must be answered");
expect_normal_open_reply(&packet, None).params
}
fn requesting(duration_ns: i64, interval_ns: i64) -> Params {
Params {
duration_ns,
interval_ns,
..client_params()
}
}
#[test]
fn a_requested_duration_is_reduced_to_the_configured_maximum() {
let config = || ServerConfig::default().with_max_test_duration(Duration::from_secs(2));
assert_eq!(
negotiated(config(), &requesting(10_000_000_000, 1_000_000_000)).duration_ns,
2_000_000_000,
"a longer duration is reduced to the maximum"
);
assert_eq!(
negotiated(config(), &requesting(1_000_000_000, 1_000_000_000)).duration_ns,
1_000_000_000,
"a duration under the maximum is left alone"
);
assert_eq!(
negotiated(config(), &requesting(0, 1_000_000_000)).duration_ns,
2_000_000_000,
"a continuous request takes the maximum"
);
}
#[test]
fn without_a_configured_maximum_a_duration_is_never_restricted() {
let config = || ServerConfig::default();
assert_eq!(
negotiated(config(), &requesting(86_400_000_000_000, 1_000_000_000)).duration_ns,
86_400_000_000_000,
"a day-long test is the operator's business, not negotiation's"
);
assert_eq!(
negotiated(config(), &requesting(0, 1_000_000_000)).duration_ns,
0,
"continuous stays continuous"
);
let zero = ServerConfig::default().with_max_test_duration(Duration::ZERO);
assert_eq!(zero.max_test_duration(), None);
assert_eq!(
negotiated(zero, &requesting(1_000_000_000, 1_000_000_000)).duration_ns,
1_000_000_000
);
}
#[test]
fn a_requested_interval_is_raised_to_the_configured_minimum() {
let config = || ServerConfig::default().with_min_send_interval(Duration::from_millis(100));
assert_eq!(
negotiated(config(), &requesting(3_000_000_000, 20_000_000)).interval_ns,
100_000_000,
"a faster interval is raised to the minimum"
);
assert_eq!(
negotiated(config(), &requesting(3_000_000_000, 500_000_000)).interval_ns,
500_000_000,
"an interval above the minimum is left alone"
);
assert_eq!(
negotiated(config(), &requesting(3_000_000_000, 0)).interval_ns,
100_000_000,
"an absent interval takes the minimum"
);
}
#[test]
fn a_negotiated_interval_is_capped_at_a_quarter_of_the_idle_timeout() {
let config = ServerConfig::default().with_idle_timeout(Duration::from_secs(8));
assert_eq!(
negotiated(config, &requesting(3_000_000_000, 5_000_000_000)).interval_ns,
2_000_000_000
);
let absurd = ServerConfig::default().with_idle_timeout(Duration::MAX);
assert_eq!(
negotiated(absurd, &requesting(3_000_000_000, i64::MAX)).interval_ns,
i64::MAX
);
}
#[test]
fn the_idle_cap_wins_over_a_larger_configured_minimum_interval() {
let config = ServerConfig::default()
.with_min_send_interval(Duration::from_secs(5))
.with_idle_timeout(Duration::from_secs(8));
assert_eq!(
negotiated(config, &requesting(3_000_000_000, 10_000_000)).interval_ns,
2_000_000_000
);
}
#[test]
fn a_zero_idle_timeout_caps_nothing() {
let config = ServerConfig::default()
.with_idle_timeout(Duration::ZERO)
.with_min_send_interval(Duration::from_millis(100));
assert_eq!(
negotiated(config, &requesting(3_000_000_000, 3_600_000_000_000)).interval_ns,
3_600_000_000_000
);
}
#[test]
fn the_default_configuration_restricts_neither_capability() {
let config = ServerConfig::default();
assert_eq!(config.timestamp_allowance(), TimestampAllowance::Dual);
assert!(config.dscp_allowed());
assert_eq!(TimestampAllowance::default(), TimestampAllowance::Dual);
let restricted = config
.clone()
.with_timestamp_allowance(TimestampAllowance::Single)
.with_dscp_allowed(false);
assert_eq!(restricted.timestamp_allowance(), TimestampAllowance::Single);
assert!(!restricted.dscp_allowed());
assert_ne!(
restricted, config,
"the policy is part of the configuration"
);
}
#[test]
fn a_requested_stamp_at_is_reduced_to_the_configured_allowance() {
for (requested, allowance, expected) in [
(StampAt::None, TimestampAllowance::Dual, StampAt::None),
(StampAt::None, TimestampAllowance::Single, StampAt::None),
(StampAt::None, TimestampAllowance::None, StampAt::None),
(StampAt::Send, TimestampAllowance::Dual, StampAt::Send),
(StampAt::Send, TimestampAllowance::Single, StampAt::Send),
(StampAt::Send, TimestampAllowance::None, StampAt::None),
(StampAt::Receive, TimestampAllowance::Dual, StampAt::Receive),
(
StampAt::Receive,
TimestampAllowance::Single,
StampAt::Receive,
),
(StampAt::Receive, TimestampAllowance::None, StampAt::None),
(StampAt::Both, TimestampAllowance::Dual, StampAt::Both),
(StampAt::Both, TimestampAllowance::Single, StampAt::Midpoint),
(StampAt::Both, TimestampAllowance::None, StampAt::None),
(
StampAt::Midpoint,
TimestampAllowance::Dual,
StampAt::Midpoint,
),
(
StampAt::Midpoint,
TimestampAllowance::Single,
StampAt::Midpoint,
),
(StampAt::Midpoint, TimestampAllowance::None, StampAt::None),
] {
let params = Params {
stamp_at: requested,
clock: Clock::Both,
..client_params()
};
let negotiated = negotiated(
ServerConfig::default().with_timestamp_allowance(allowance),
¶ms,
);
assert_eq!(
negotiated.stamp_at, expected,
"{requested:?} under {allowance:?}"
);
assert_eq!(
negotiated.clock,
Clock::Both,
"{requested:?} under {allowance:?}: the allowance restricts placement, not the clock"
);
}
}
#[test]
fn a_timestamp_restriction_never_rewrites_the_requested_clock() {
let single = negotiated(
ServerConfig::default().with_timestamp_allowance(TimestampAllowance::Single),
&Params {
stamp_at: StampAt::Both,
clock: Clock::Wall,
..client_params()
},
);
assert_eq!(single.stamp_at, StampAt::Midpoint);
assert_eq!(single.clock, Clock::Wall);
let none = negotiated(
ServerConfig::default().with_timestamp_allowance(TimestampAllowance::None),
&Params {
stamp_at: StampAt::Both,
clock: Clock::Both,
..client_params()
},
);
assert_eq!(none.stamp_at, StampAt::None);
assert_eq!(
none.clock,
Clock::Both,
"the clock tag stays exactly as requested, selecting nothing"
);
}
#[test]
fn an_allowed_dscp_is_negotiated_stored_and_transported_unchanged() {
let requested = Params {
dscp: 0xbb,
..client_params()
};
let mut core = core_with_tokens(unthrottled(), ScriptedTokens::new([TOKEN_A]));
let open = core
.handle_datagram(peer(), &open_request(&requested, None))
.unwrap()
.expect("the open must be answered");
let negotiated = expect_normal_open_reply(&open, None).params;
assert_eq!(negotiated.dscp, 0xbb);
assert_eq!(
core.session(TOKEN_A)
.expect("the session must be live")
.params()
.dscp,
0xbb
);
let reply = core
.handle_datagram(peer(), &echo_request(TOKEN_A, 0, &negotiated, &[], None))
.unwrap()
.expect("an admissible echo must be answered");
expect_echo_reply(&reply, &negotiated, None);
assert_eq!(i64::from(reply.traffic_class()), 0xbb);
}
#[test]
fn a_disallowed_dscp_is_negotiated_to_zero_and_the_session_runs_unmarked() {
let requested = Params {
dscp: 0xb8,
..client_params()
};
let mut core = core_with_tokens(
unthrottled().with_dscp_allowed(false),
ScriptedTokens::new([TOKEN_A]),
);
let open = core
.handle_datagram(peer(), &open_request(&requested, None))
.unwrap()
.expect("a disallowed DSCP must not refuse the open");
let negotiated = expect_normal_open_reply(&open, None).params;
assert_eq!(negotiated.dscp, 0, "restricted to zero");
assert_eq!(
core.session(TOKEN_A)
.expect("the session must be live")
.params()
.dscp,
0,
"and stored as the value the session will be run with"
);
let reply = core
.handle_datagram(peer(), &echo_request(TOKEN_A, 0, &negotiated, &[], None))
.unwrap()
.expect("an unmarked session is an ordinary session");
expect_echo_reply(&reply, &negotiated, None);
assert_eq!(reply.traffic_class(), 0);
}
#[test]
fn a_disallowed_dscp_restricts_a_value_no_socket_could_carry_too() {
for dscp in [-1, 256, i64::MAX] {
let requested = Params {
dscp,
..client_params()
};
let mut core = core_with_tokens(
unthrottled().with_dscp_allowed(false),
ScriptedTokens::new([TOKEN_A]),
);
let open = core
.handle_datagram(peer(), &open_request(&requested, None))
.unwrap()
.unwrap_or_else(|| panic!("an open requesting DSCP {dscp} must still be answered"));
let negotiated = expect_normal_open_reply(&open, None).params;
assert_eq!(negotiated.dscp, 0, "requested {dscp}");
assert_eq!(
core.session(TOKEN_A)
.expect("the session must be live")
.params()
.dscp,
0,
"requested {dscp}"
);
let reply = core
.handle_datagram(peer(), &echo_request(TOKEN_A, 0, &negotiated, &[], None))
.unwrap()
.unwrap_or_else(|| panic!("the session must serve echoes after restricting {dscp}"));
expect_echo_reply(&reply, &negotiated, None);
assert_eq!(reply.traffic_class(), 0, "requested {dscp}");
}
}