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] fn strict_negotiation_accepts_identical_params() { let config = ClientConfig::default(); let params = params_from_config(&config).unwrap(); let negotiated = assert_negotiates(¶ms, ¶ms, NegotiationPolicy::Strict); assert_eq!(negotiated.params, params); assert!(negotiated.restrictions.is_empty()); } #[test] fn strict_negotiation_rejects_changed_params() { let config = ClientConfig { dscp: 46, ..ClientConfig::default() }; let requested = params_from_config(&config).unwrap(); assert_eq!( requested.dscp, 184, "codepoint 46 must become raw wire byte 184" ); let mut returned = requested.clone(); returned.dscp = 0; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Strict), NegotiationRestriction::DscpChanged { requested: 46, negotiated: 0, } .message() ); } #[test] fn loose_negotiation_accepts_server_restricted_params() { let config = ClientConfig::default(); let mut requested = params_from_config(&config).unwrap(); requested.length = 128; let mut returned = requested.clone(); returned.duration_ns /= 2; returned.length = 0; 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] fn loose_negotiation_rejects_non_positive_returned_interval() { let config = ClientConfig::default(); let requested = params_from_config(&config).unwrap(); for interval_ns in [0, -1] { let mut returned = requested.clone(); returned.interval_ns = interval_ns; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Loose), "interval must be positive" ); } } #[test] fn loose_negotiation_accepts_documented_duration_combinations() { let finite_config = ClientConfig::default(); let requested_finite = params_from_config(&finite_config).unwrap(); let returned_same_finite = requested_finite.clone(); assert!(assert_negotiates( &requested_finite, &returned_same_finite, NegotiationPolicy::Loose ) .restrictions .is_empty()); let mut returned_shorter_finite = requested_finite.clone(); returned_shorter_finite.duration_ns /= 2; assert_eq!( assert_negotiates( &requested_finite, &returned_shorter_finite, NegotiationPolicy::Loose ) .restrictions, vec![NegotiationRestriction::DurationReduced { requested_ns: requested_finite.duration_ns, negotiated_ns: returned_shorter_finite.duration_ns, }] ); let continuous_config = ClientConfig { duration: None, ..ClientConfig::default() }; let requested_continuous = params_from_config(&continuous_config).unwrap(); let returned_continuous = requested_continuous.clone(); assert!(assert_negotiates( &requested_continuous, &returned_continuous, NegotiationPolicy::Loose ) .restrictions .is_empty()); let mut returned_finite = requested_continuous.clone(); returned_finite.duration_ns = 1_000_000_000; assert_eq!( assert_negotiates( &requested_continuous, &returned_finite, NegotiationPolicy::Loose ) .restrictions, vec![NegotiationRestriction::DurationReduced { requested_ns: requested_continuous.duration_ns, negotiated_ns: returned_finite.duration_ns, }] ); } #[test] fn loose_negotiation_rejects_finite_request_returned_continuous() { let config = ClientConfig::default(); let requested = params_from_config(&config).unwrap(); let mut returned = requested.clone(); returned.duration_ns = 0; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Loose), "server returned continuous duration for finite request" ); } #[test] fn loose_negotiation_rejects_negative_returned_length() { let config = ClientConfig::default(); let requested = params_from_config(&config).unwrap(); let mut returned = requested.clone(); returned.length = -1; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Loose), "length must be non-negative" ); } #[test] fn strict_negotiation_rejects_negative_returned_length() { let config = ClientConfig::default(); let requested = params_from_config(&config).unwrap(); let mut returned = requested.clone(); returned.length = -1; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Strict), "length must be non-negative" ); } #[test] fn loose_negotiation_rejects_runtime_invalid_returned_dscp() { let config = ClientConfig { dscp: 46, ..ClientConfig::default() }; let requested = params_from_config(&config).unwrap(); assert_eq!( requested.dscp, 184, "codepoint 46 must become raw wire byte 184" ); for dscp in [-1, 256] { let mut returned = requested.clone(); returned.dscp = dscp; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Loose), "dscp must be in range 0..=255" ); } // A raw returned value of 184 must be accepted even though it exceeds the // 0..=63 codepoint range; it is the correctly negotiated wire value for // codepoint 46 and is not subject to the codepoint bound at all. let returned = requested.clone(); assert!( assert_negotiates(&requested, &returned, NegotiationPolicy::Loose) .restrictions .is_empty() ); } #[test] fn loose_negotiation_records_dscp_disabled_by_server() { let config = ClientConfig { dscp: 46, ..ClientConfig::default() }; let requested = params_from_config(&config).unwrap(); let mut returned = requested.clone(); returned.dscp = 0; let negotiated = assert_negotiates(&requested, &returned, NegotiationPolicy::Loose); assert_eq!( negotiated.restrictions, vec![NegotiationRestriction::DscpChanged { requested: 46, negotiated: 0, }] ); } #[test] fn strict_negotiation_rejects_dscp_disabled_by_server_as_specific_restriction() { let config = ClientConfig { dscp: 46, ..ClientConfig::default() }; let requested = params_from_config(&config).unwrap(); let mut returned = requested.clone(); returned.dscp = 0; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Strict), NegotiationRestriction::DscpChanged { requested: 46, negotiated: 0, } .message() ); } #[test] fn negotiation_rejects_unsupported_dscp_changes() { let config = ClientConfig { dscp: 46, ..ClientConfig::default() }; let requested = params_from_config(&config).unwrap(); let mut returned = requested.clone(); returned.dscp = 8; assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Loose), "server returned unsupported DSCP change" ); assert_eq!( rejection_reason(&requested, &returned, NegotiationPolicy::Strict), "server returned unsupported DSCP change" ); let zero_config = ClientConfig::default(); let zero_requested = params_from_config(&zero_config).unwrap(); let mut returned = zero_requested.clone(); returned.dscp = 46; assert_eq!( rejection_reason(&zero_requested, &returned, NegotiationPolicy::Loose), "server returned unsupported DSCP change" ); }