feat(client): track and report specific negotiation restrictions

Replace basic validation with a detailed negotiation process that records specific parameter changes. This allows the client to provide better feedback when a server restricts requested session parameters like duration or DSCP.
This commit is contained in:
Nils Andreas Svee committed 2026-05-12 15:18:02 +07:00
1 parent a7e10358fd
commit cbe91f2fe0
7 files changed
+409 -87

No files matched your search

+1
View File
@@ -1456,6 +1456,7 @@ mod tests {
interval_ns: 1_000_000_000, interval_ns: 1_000_000_000,
..Params::default() ..Params::default()
}, },
restrictions: vec![],
}, },
at: test_timestamp(Duration::from_secs(1)), at: test_timestamp(Duration::from_secs(1)),
}; };
+9 -14
View File
@@ -23,7 +23,7 @@ use crate::{
metadata::ReceiveMeta, metadata::ReceiveMeta,
probe::{CompletedSet, PendingMap, PendingProbe, TimedOutMap}, probe::{CompletedSet, PendingMap, PendingProbe, TimedOutMap},
receive::recv_datagram, receive::recv_datagram,
session::{validate_negotiated_params, ActiveSession, ClientPhase, NegotiatedParams}, session::{negotiate_params, ActiveSession, ClientPhase, NegotiatedParams},
socket::{connect_udp_socket, resolve_remote, validate_open_timeouts}, socket::{connect_udp_socket, resolve_remote, validate_open_timeouts},
socket_options::{apply_dscp_to_socket, clear_dscp_on_socket}, socket_options::{apply_dscp_to_socket, clear_dscp_on_socket},
timing::ClientTimestamp, timing::ClientTimestamp,
@@ -605,14 +605,12 @@ impl Client {
reply: OpenReply, reply: OpenReply,
now: ClientTimestamp, now: ClientTimestamp,
) -> Result<OpenOutcome, ClientError> { ) -> Result<OpenOutcome, ClientError> {
validate_negotiated_params( let token = reply.token;
let negotiated = negotiate_params(
&self.requested, &self.requested,
&reply.params, reply.params,
self.config.negotiation_policy, self.config.negotiation_policy,
)?; )?;
let negotiated = NegotiatedParams {
params: reply.params.clone(),
};
let recv_buffer_size = recv_buffer_size(self.config.hmac_key.is_some(), Some(&negotiated))?; let recv_buffer_size = recv_buffer_size(self.config.hmac_key.is_some(), Some(&negotiated))?;
let negotiated_dscp = let negotiated_dscp =
u8::try_from(negotiated.params.dscp).map_err(|_| ClientError::InvalidConfig { u8::try_from(negotiated.params.dscp).map_err(|_| ClientError::InvalidConfig {
@@ -621,7 +619,7 @@ impl Client {
apply_dscp_to_socket(&self.socket, self.remote, negotiated_dscp)?; apply_dscp_to_socket(&self.socket, self.remote, negotiated_dscp)?;
self.negotiated = Some(negotiated.clone()); self.negotiated = Some(negotiated.clone());
self.recv_buffer.resize(recv_buffer_size, 0); self.recv_buffer.resize(recv_buffer_size, 0);
self.phase = ClientPhase::Open { token: reply.token }; self.phase = ClientPhase::Open { token };
let end_mono = if negotiated.params.duration_ns > 0 { let end_mono = if negotiated.params.duration_ns > 0 {
Some(negotiated_end_mono( Some(negotiated_end_mono(
@@ -647,14 +645,14 @@ impl Client {
let event = ClientEvent::SessionStarted { let event = ClientEvent::SessionStarted {
remote: self.remote, remote: self.remote,
token: reply.token, token,
negotiated: negotiated.clone(), negotiated: negotiated.clone(),
at: now, at: now,
}; };
Ok(OpenOutcome::Started { Ok(OpenOutcome::Started {
remote: self.remote, remote: self.remote,
token: reply.token, token,
negotiated, negotiated,
event, event,
}) })
@@ -665,14 +663,11 @@ impl Client {
reply: OpenReply, reply: OpenReply,
now: ClientTimestamp, now: ClientTimestamp,
) -> Result<OpenOutcome, ClientError> { ) -> Result<OpenOutcome, ClientError> {
validate_negotiated_params( let negotiated = negotiate_params(
&self.requested, &self.requested,
&reply.params, reply.params,
self.config.negotiation_policy, self.config.negotiation_policy,
)?; )?;
let negotiated = NegotiatedParams {
params: reply.params.clone(),
};
self.negotiated = Some(negotiated.clone()); self.negotiated = Some(negotiated.clone());
self.phase = ClientPhase::NoTestCompleted; self.phase = ClientPhase::NoTestCompleted;
let event = ClientEvent::NoTestCompleted { let event = ClientEvent::NoTestCompleted {
@@ -60,6 +60,13 @@ fn normal_open_uses_negotiated_dscp_not_requested_dscp() {
let negotiated = assert_open_started(client.open().unwrap()); let negotiated = assert_open_started(client.open().unwrap());
assert_eq!(negotiated.params.dscp, 0); assert_eq!(negotiated.params.dscp, 0);
assert_eq!(
negotiated.restrictions,
vec![crate::NegotiationRestriction::DscpChanged {
requested: 46,
negotiated: 0,
}]
);
assert_eq!( assert_eq!(
socket_traffic_class(&client.socket, client.remote).unwrap(), socket_traffic_class(&client.socket, client.remote).unwrap(),
0 0
@@ -1,10 +1,29 @@
use super::*; use super::*;
use crate::NegotiationRestriction;
fn assert_negotiates(
requested: &Params,
returned: &Params,
policy: NegotiationPolicy,
) -> NegotiatedParams {
negotiate_params(requested, returned.clone(), policy)
.unwrap_or_else(|err| panic!("expected negotiation success, got {err:?}"))
}
fn rejection_reason(requested: &Params, returned: &Params, policy: NegotiationPolicy) -> String {
match negotiate_params(requested, returned.clone(), policy) {
Err(ClientError::NegotiationRejected { reason }) => reason,
other => panic!("expected negotiation rejection, got {other:?}"),
}
}
#[test] #[test]
fn strict_negotiation_accepts_identical_params() { fn strict_negotiation_accepts_identical_params() {
let config = ClientConfig::default(); let config = ClientConfig::default();
let params = params_from_config(&config).unwrap(); let params = params_from_config(&config).unwrap();
assert!(validate_negotiated_params(&params, &params, NegotiationPolicy::Strict).is_ok()); let negotiated = assert_negotiates(&params, &params, NegotiationPolicy::Strict);
assert_eq!(negotiated.params, params);
assert!(negotiated.restrictions.is_empty());
} }
#[test] #[test]
@@ -13,20 +32,38 @@ fn strict_negotiation_rejects_changed_params() {
let requested = params_from_config(&config).unwrap(); let requested = params_from_config(&config).unwrap();
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.dscp = 1; returned.dscp = 1;
assert!(matches!( assert_eq!(
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Strict), rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
Err(ClientError::NegotiationRejected { .. }) NegotiationRestriction::DscpChanged {
)); requested: requested.dscp,
negotiated: returned.dscp,
}
.message()
);
} }
#[test] #[test]
fn loose_negotiation_accepts_server_restricted_params() { fn loose_negotiation_accepts_server_restricted_params() {
let config = ClientConfig::default(); let config = ClientConfig::default();
let requested = params_from_config(&config).unwrap(); let mut requested = params_from_config(&config).unwrap();
requested.length = 128;
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.duration_ns /= 2; returned.duration_ns /= 2;
returned.length = 0; returned.length = 0;
assert!(validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose).is_ok()); let negotiated = assert_negotiates(&requested, &returned, NegotiationPolicy::Loose);
assert_eq!(
negotiated.restrictions,
vec![
NegotiationRestriction::DurationReduced {
requested_ns: requested.duration_ns,
negotiated_ns: returned.duration_ns,
},
NegotiationRestriction::LengthReduced {
requested: requested.length,
negotiated: returned.length,
},
]
);
} }
#[test] #[test]
@@ -37,11 +74,10 @@ fn loose_negotiation_rejects_non_positive_returned_interval() {
for interval_ns in [0, -1] { for interval_ns in [0, -1] {
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.interval_ns = interval_ns; returned.interval_ns = interval_ns;
assert!(matches!( assert_eq!(
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose), rejection_reason(&requested, &returned, NegotiationPolicy::Loose),
Err(ClientError::NegotiationRejected { reason }) "interval must be positive"
if reason == "interval must be positive" );
));
} }
} }
@@ -51,21 +87,28 @@ fn loose_negotiation_accepts_documented_duration_combinations() {
let requested_finite = params_from_config(&finite_config).unwrap(); let requested_finite = params_from_config(&finite_config).unwrap();
let returned_same_finite = requested_finite.clone(); let returned_same_finite = requested_finite.clone();
assert!(validate_negotiated_params( assert!(assert_negotiates(
&requested_finite, &requested_finite,
&returned_same_finite, &returned_same_finite,
NegotiationPolicy::Loose NegotiationPolicy::Loose
) )
.is_ok()); .restrictions
.is_empty());
let mut returned_shorter_finite = requested_finite.clone(); let mut returned_shorter_finite = requested_finite.clone();
returned_shorter_finite.duration_ns /= 2; returned_shorter_finite.duration_ns /= 2;
assert!(validate_negotiated_params( assert_eq!(
&requested_finite, assert_negotiates(
&returned_shorter_finite, &requested_finite,
NegotiationPolicy::Loose &returned_shorter_finite,
) NegotiationPolicy::Loose
.is_ok()); )
.restrictions,
vec![NegotiationRestriction::DurationReduced {
requested_ns: requested_finite.duration_ns,
negotiated_ns: returned_shorter_finite.duration_ns,
}]
);
let continuous_config = ClientConfig { let continuous_config = ClientConfig {
duration: None, duration: None,
@@ -74,21 +117,28 @@ fn loose_negotiation_accepts_documented_duration_combinations() {
let requested_continuous = params_from_config(&continuous_config).unwrap(); let requested_continuous = params_from_config(&continuous_config).unwrap();
let returned_continuous = requested_continuous.clone(); let returned_continuous = requested_continuous.clone();
assert!(validate_negotiated_params( assert!(assert_negotiates(
&requested_continuous, &requested_continuous,
&returned_continuous, &returned_continuous,
NegotiationPolicy::Loose NegotiationPolicy::Loose
) )
.is_ok()); .restrictions
.is_empty());
let mut returned_finite = requested_continuous.clone(); let mut returned_finite = requested_continuous.clone();
returned_finite.duration_ns = 1_000_000_000; returned_finite.duration_ns = 1_000_000_000;
assert!(validate_negotiated_params( assert_eq!(
&requested_continuous, assert_negotiates(
&returned_finite, &requested_continuous,
NegotiationPolicy::Loose &returned_finite,
) NegotiationPolicy::Loose
.is_ok()); )
.restrictions,
vec![NegotiationRestriction::DurationReduced {
requested_ns: requested_continuous.duration_ns,
negotiated_ns: returned_finite.duration_ns,
}]
);
} }
#[test] #[test]
@@ -98,11 +148,10 @@ fn loose_negotiation_rejects_finite_request_returned_continuous() {
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.duration_ns = 0; returned.duration_ns = 0;
assert!(matches!( assert_eq!(
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose), rejection_reason(&requested, &returned, NegotiationPolicy::Loose),
Err(ClientError::NegotiationRejected { reason }) "server returned continuous duration for finite request"
if reason == "server returned continuous duration for finite request" );
));
} }
#[test] #[test]
@@ -111,10 +160,10 @@ fn loose_negotiation_rejects_negative_returned_length() {
let requested = params_from_config(&config).unwrap(); let requested = params_from_config(&config).unwrap();
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.length = -1; returned.length = -1;
assert!(matches!( assert_eq!(
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose), rejection_reason(&requested, &returned, NegotiationPolicy::Loose),
Err(ClientError::NegotiationRejected { reason }) if reason == "length must be non-negative" "length must be non-negative"
)); );
} }
#[test] #[test]
@@ -123,10 +172,10 @@ fn strict_negotiation_rejects_negative_returned_length() {
let requested = params_from_config(&config).unwrap(); let requested = params_from_config(&config).unwrap();
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.length = -1; returned.length = -1;
assert!(matches!( assert_eq!(
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Strict), rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
Err(ClientError::NegotiationRejected { reason }) if reason == "length must be non-negative" "length must be non-negative"
)); );
} }
#[test] #[test]
@@ -137,10 +186,9 @@ fn loose_negotiation_rejects_runtime_invalid_returned_dscp() {
for dscp in [-1, 64] { for dscp in [-1, 64] {
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.dscp = dscp; returned.dscp = dscp;
assert!(matches!( assert_eq!(
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose), rejection_reason(&requested, &returned, NegotiationPolicy::Loose),
Err(ClientError::NegotiationRejected { reason }) "dscp must be in range 0..=63"
if reason == "dscp must be in range 0..=63" );
));
} }
} }
@@ -79,13 +79,21 @@ fn no_test_loose_negotiation_accepts_restricted_params() {
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
config.run_mode = RunMode::NoTest; config.run_mode = RunMode::NoTest;
config.negotiation_policy = NegotiationPolicy::Loose; config.negotiation_policy = NegotiationPolicy::Loose;
let mut params = params_from_config(&config).unwrap(); let requested = params_from_config(&config).unwrap();
let mut params = requested.clone();
params.duration_ns /= 2; params.duration_ns /= 2;
let server = no_test_server(params.clone(), 0); let server = no_test_server(params.clone(), 0);
config.server_addr = server.addr.to_string(); config.server_addr = server.addr.to_string();
let mut client = Client::connect(config).unwrap(); let mut client = Client::connect(config).unwrap();
let negotiated = assert_no_test_completed(client.open().unwrap()); let negotiated = assert_no_test_completed(client.open().unwrap());
assert_eq!(negotiated.params, params); assert_eq!(negotiated.params, params);
assert_eq!(
negotiated.restrictions,
vec![crate::NegotiationRestriction::DurationReduced {
requested_ns: requested.duration_ns,
negotiated_ns: params.duration_ns,
}]
);
server.join(); server.join();
} }
+1 -1
View File
@@ -31,5 +31,5 @@ pub use managed::{
CancellationToken, EventHub, EventSubscription, ManagedClient, ManagedClientSession, CancellationToken, EventHub, EventSubscription, ManagedClient, ManagedClientSession,
SessionEndReason, SessionOutcome, SubscriberConfig, SubscriberOverflow, SessionEndReason, SessionOutcome, SubscriberConfig, SubscriberOverflow,
}; };
pub use session::NegotiatedParams; pub use session::{NegotiatedParams, NegotiationRestriction};
pub use timing::ClientTimestamp; pub use timing::ClientTimestamp;
+288 -25
View File
@@ -1,6 +1,6 @@
use std::time::Instant; use std::time::Instant;
use irtt_proto::{Params, PROTOCOL_VERSION}; use irtt_proto::{Clock, Params, ReceivedStats, StampAt, PROTOCOL_VERSION};
use crate::{ use crate::{
config::{NegotiationPolicy, MAX_DSCP_CODEPOINT}, config::{NegotiationPolicy, MAX_DSCP_CODEPOINT},
@@ -11,6 +11,106 @@ use crate::{
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NegotiatedParams { pub struct NegotiatedParams {
pub params: Params, pub params: Params,
pub restrictions: Vec<NegotiationRestriction>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NegotiationRestriction {
DurationReduced {
requested_ns: i64,
negotiated_ns: i64,
},
IntervalIncreased {
requested_ns: i64,
negotiated_ns: i64,
},
IntervalReduced {
requested_ns: i64,
negotiated_ns: i64,
},
LengthReduced {
requested: i64,
negotiated: i64,
},
ReceivedStatsChanged {
requested: ReceivedStats,
negotiated: ReceivedStats,
},
StampAtChanged {
requested: StampAt,
negotiated: StampAt,
},
ClockChanged {
requested: Clock,
negotiated: Clock,
},
DscpChanged {
requested: i64,
negotiated: i64,
},
ServerFillChanged,
}
impl NegotiationRestriction {
pub fn message(&self) -> String {
match self {
Self::DurationReduced {
requested_ns: 0,
negotiated_ns,
} => {
format!("server limited continuous duration to {negotiated_ns} ns")
}
Self::DurationReduced {
requested_ns,
negotiated_ns,
} => {
format!("server reduced duration from {requested_ns} ns to {negotiated_ns} ns")
}
Self::IntervalIncreased {
requested_ns,
negotiated_ns,
} => {
format!("server increased interval from {requested_ns} ns to {negotiated_ns} ns")
}
Self::IntervalReduced {
requested_ns,
negotiated_ns,
} => {
format!("server reduced interval from {requested_ns} ns to {negotiated_ns} ns")
}
Self::LengthReduced {
requested,
negotiated,
} => {
format!("server reduced packet length from {requested} bytes to {negotiated} bytes")
}
Self::ReceivedStatsChanged {
requested,
negotiated,
} => {
format!("server changed received-stats from {requested:?} to {negotiated:?}")
}
Self::StampAtChanged {
requested,
negotiated,
} => {
format!("server changed stamp-at from {requested:?} to {negotiated:?}")
}
Self::ClockChanged {
requested,
negotiated,
} => {
format!("server changed clock from {requested:?} to {negotiated:?}")
}
Self::DscpChanged {
requested,
negotiated,
} => {
format!("server changed dscp from {requested} to {negotiated}")
}
Self::ServerFillChanged => "server changed payload fill behavior".to_owned(),
}
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -35,17 +135,19 @@ pub(crate) struct ActiveSession {
pub sending_done: bool, pub sending_done: bool,
} }
pub(crate) fn validate_negotiated_params( pub(crate) fn negotiate_params(
requested: &Params, requested: &Params,
returned: &Params, returned: Params,
policy: NegotiationPolicy, policy: NegotiationPolicy,
) -> Result<(), ClientError> { ) -> Result<NegotiatedParams, ClientError> {
if returned.protocol_version != PROTOCOL_VERSION { if returned.protocol_version != PROTOCOL_VERSION {
return Err(ClientError::ProtocolVersionMismatch { return Err(ClientError::ProtocolVersionMismatch {
requested: PROTOCOL_VERSION, requested: PROTOCOL_VERSION,
received: returned.protocol_version, received: returned.protocol_version,
}); });
} }
let mut restrictions = Vec::new();
validate_duration_restriction(requested.duration_ns, returned.duration_ns)?; validate_duration_restriction(requested.duration_ns, returned.duration_ns)?;
if returned.length < 0 { if returned.length < 0 {
return Err(ClientError::NegotiationRejected { return Err(ClientError::NegotiationRejected {
@@ -64,12 +166,100 @@ pub(crate) fn validate_negotiated_params(
} }
validate_dscp_restriction(returned.dscp)?; validate_dscp_restriction(returned.dscp)?;
if policy == NegotiationPolicy::Strict && returned != requested { if returned.duration_ns < requested.duration_ns
return Err(ClientError::NegotiationRejected { || (requested.duration_ns == 0 && returned.duration_ns > 0)
reason: "returned params differ from requested params".to_owned(), {
}); record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::DurationReduced {
requested_ns: requested.duration_ns,
negotiated_ns: returned.duration_ns,
},
)?;
} }
Ok(()) if returned.interval_ns > requested.interval_ns {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::IntervalIncreased {
requested_ns: requested.interval_ns,
negotiated_ns: returned.interval_ns,
},
)?;
}
if returned.interval_ns < requested.interval_ns {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::IntervalReduced {
requested_ns: requested.interval_ns,
negotiated_ns: returned.interval_ns,
},
)?;
}
if returned.length < requested.length {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::LengthReduced {
requested: requested.length,
negotiated: returned.length,
},
)?;
}
if returned.received_stats != requested.received_stats {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::ReceivedStatsChanged {
requested: requested.received_stats,
negotiated: returned.received_stats,
},
)?;
}
if returned.stamp_at != requested.stamp_at {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::StampAtChanged {
requested: requested.stamp_at,
negotiated: returned.stamp_at,
},
)?;
}
if returned.clock != requested.clock {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::ClockChanged {
requested: requested.clock,
negotiated: returned.clock,
},
)?;
}
if returned.dscp != requested.dscp {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::DscpChanged {
requested: requested.dscp,
negotiated: returned.dscp,
},
)?;
}
if returned.server_fill != requested.server_fill {
record_restriction(
policy,
&mut restrictions,
NegotiationRestriction::ServerFillChanged,
)?;
}
Ok(NegotiatedParams {
params: returned,
restrictions,
})
} }
fn validate_duration_restriction(requested: i64, returned: i64) -> Result<(), ClientError> { fn validate_duration_restriction(requested: i64, returned: i64) -> Result<(), ClientError> {
@@ -104,6 +294,21 @@ fn validate_dscp_restriction(returned: i64) -> Result<(), ClientError> {
Ok(()) Ok(())
} }
fn record_restriction(
policy: NegotiationPolicy,
restrictions: &mut Vec<NegotiationRestriction>,
restriction: NegotiationRestriction,
) -> Result<(), ClientError> {
if policy == NegotiationPolicy::Strict {
return Err(ClientError::NegotiationRejected {
reason: restriction.message(),
});
}
restrictions.push(restriction);
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -127,17 +332,26 @@ mod tests {
fn assert_rejected(requested: &Params, returned: &Params, policy: NegotiationPolicy) { fn assert_rejected(requested: &Params, returned: &Params, policy: NegotiationPolicy) {
assert!(matches!( assert!(matches!(
validate_negotiated_params(requested, returned, policy), negotiate_params(requested, returned.clone(), policy),
Err(ClientError::NegotiationRejected { .. }) Err(ClientError::NegotiationRejected { .. })
)); ));
} }
fn assert_negotiates(
requested: &Params,
returned: &Params,
policy: NegotiationPolicy,
) -> NegotiatedParams {
negotiate_params(requested, returned.clone(), policy)
.unwrap_or_else(|err| panic!("expected negotiation success, got {err:?}"))
}
fn rejection_reason( fn rejection_reason(
requested: &Params, requested: &Params,
returned: &Params, returned: &Params,
policy: NegotiationPolicy, policy: NegotiationPolicy,
) -> String { ) -> String {
match validate_negotiated_params(requested, returned, policy) { match negotiate_params(requested, returned.clone(), policy) {
Err(ClientError::NegotiationRejected { reason }) => reason, Err(ClientError::NegotiationRejected { reason }) => reason,
other => panic!("expected negotiation rejection, got {other:?}"), other => panic!("expected negotiation rejection, got {other:?}"),
} }
@@ -149,27 +363,65 @@ mod tests {
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.length = 128; returned.length = 128;
assert_rejected(&requested, &returned, NegotiationPolicy::Strict); assert_eq!(
rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
NegotiationRestriction::LengthReduced {
requested: requested.length,
negotiated: returned.length,
}
.message()
);
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.dscp = 8; returned.dscp = 8;
assert_rejected(&requested, &returned, NegotiationPolicy::Strict); assert_eq!(
rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
NegotiationRestriction::DscpChanged {
requested: requested.dscp,
negotiated: returned.dscp,
}
.message()
);
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.received_stats = ReceivedStats::Count; returned.received_stats = ReceivedStats::Count;
assert_rejected(&requested, &returned, NegotiationPolicy::Strict); assert_eq!(
rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
NegotiationRestriction::ReceivedStatsChanged {
requested: requested.received_stats,
negotiated: returned.received_stats,
}
.message()
);
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.stamp_at = StampAt::Midpoint; returned.stamp_at = StampAt::Midpoint;
assert_rejected(&requested, &returned, NegotiationPolicy::Strict); assert_eq!(
rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
NegotiationRestriction::StampAtChanged {
requested: requested.stamp_at,
negotiated: returned.stamp_at,
}
.message()
);
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.clock = Clock::Wall; returned.clock = Clock::Wall;
assert_rejected(&requested, &returned, NegotiationPolicy::Strict); assert_eq!(
rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
NegotiationRestriction::ClockChanged {
requested: requested.clock,
negotiated: returned.clock,
}
.message()
);
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.server_fill = None; returned.server_fill = None;
assert_rejected(&requested, &returned, NegotiationPolicy::Strict); assert_eq!(
rejection_reason(&requested, &returned, NegotiationPolicy::Strict),
NegotiationRestriction::ServerFillChanged.message()
);
} }
#[test] #[test]
@@ -178,8 +430,13 @@ mod tests {
let mut returned = requested.clone(); let mut returned = requested.clone();
returned.duration_ns = requested.duration_ns / 2; returned.duration_ns = requested.duration_ns / 2;
assert!( let negotiated = assert_negotiates(&requested, &returned, NegotiationPolicy::Loose);
validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose).is_ok() assert_eq!(
negotiated.restrictions,
vec![NegotiationRestriction::DurationReduced {
requested_ns: requested.duration_ns,
negotiated_ns: returned.duration_ns,
}]
); );
let mut returned = requested.clone(); let mut returned = requested.clone();
@@ -200,12 +457,18 @@ mod tests {
continuous_requested.duration_ns = 0; continuous_requested.duration_ns = 0;
let mut finite_returned = continuous_requested.clone(); let mut finite_returned = continuous_requested.clone();
finite_returned.duration_ns = 1_000_000_000; finite_returned.duration_ns = 1_000_000_000;
assert!(validate_negotiated_params( let negotiated = assert_negotiates(
&continuous_requested, &continuous_requested,
&finite_returned, &finite_returned,
NegotiationPolicy::Loose NegotiationPolicy::Loose,
) );
.is_ok()); assert_eq!(
negotiated.restrictions,
vec![NegotiationRestriction::DurationReduced {
requested_ns: 0,
negotiated_ns: finite_returned.duration_ns,
}]
);
assert_rejected( assert_rejected(
&continuous_requested, &continuous_requested,
@@ -213,9 +476,9 @@ mod tests {
NegotiationPolicy::Strict, NegotiationPolicy::Strict,
); );
assert!(validate_negotiated_params( assert!(negotiate_params(
&continuous_requested,
&continuous_requested, &continuous_requested,
continuous_requested.clone(),
NegotiationPolicy::Strict NegotiationPolicy::Strict
) )
.is_ok()); .is_ok());