#![allow(dead_code)] use std::{ collections::HashSet, net::{Ipv4Addr, Ipv6Addr, SocketAddr}, time::Instant, }; use happy_eyeballs::{ CONNECTION_ATTEMPT_DELAY, ConnectionAttemptHttpVersions, DnsRecordType, DnsResult, Endpoint, HappyEyeballs, HttpVersion, Id, Input, NetworkConfig, Output, RESOLUTION_DELAY, ServiceInfo, }; pub const HOSTNAME: &str = "example.com"; pub const SVC1: &str = "svc1.example.com."; pub const PORT: u16 = 443; pub const CUSTOM_PORT: u16 = 8443; pub const V6_ADDR: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1); pub const V6_ADDR_2: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2); pub const V6_ADDR_3: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 3); pub const V4_ADDR: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 1); pub const V4_ADDR_2: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 2); pub const ECH_CONFIG: &[u8] = &[1, 2, 3, 4, 5]; pub trait HappyEyeballsExt { fn expect(&mut self, input_output: Vec<(Option, Option)>, now: Instant); fn expect_connection_attempts(&mut self, now: &mut Instant, connections: Vec); } impl HappyEyeballsExt for HappyEyeballs { fn expect(&mut self, input_output: Vec<(Option, Option)>, now: Instant) { for (input, expected_output) in input_output { if let Some(input) = input { self.process_input(input, now); } let output = self.process_output(now); assert_eq!(expected_output, output); } } fn expect_connection_attempts(&mut self, now: &mut Instant, connections: Vec) { for conn in connections { *now += CONNECTION_ATTEMPT_DELAY; self.expect( vec![ (None, Some(conn)), (None, Some(out_connection_attempt_delay())), ], *now, ); } *now += CONNECTION_ATTEMPT_DELAY; self.expect(vec![(None, None)], *now); } } pub fn in_dns_https_positive(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Https(Ok(vec![ServiceInfo { priority: 1, target_name: HOSTNAME.into(), alpn_protocols: HashSet::from([HttpVersion::H3, HttpVersion::H2]), ipv6_hints: vec![], ipv4_hints: vec![], ech_config: None, port: None, }])), } } pub fn in_dns_https_positive_no_alpn(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Https(Ok(vec![ServiceInfo { priority: 1, target_name: HOSTNAME.into(), alpn_protocols: HashSet::new(), ipv6_hints: vec![], ipv4_hints: vec![], ech_config: None, port: None, }])), } } pub fn in_dns_https_positive_v6_hints(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Https(Ok(vec![ServiceInfo { priority: 1, target_name: HOSTNAME.into(), alpn_protocols: HashSet::from([HttpVersion::H3, HttpVersion::H2]), ipv6_hints: vec![V6_ADDR], ipv4_hints: vec![], ech_config: None, port: None, }])), } } pub fn in_dns_https_positive_svc1(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Https(Ok(vec![ServiceInfo { priority: 1, target_name: SVC1.into(), alpn_protocols: HashSet::from([HttpVersion::H3, HttpVersion::H2]), ipv6_hints: vec![V6_ADDR_2], ipv4_hints: vec![], ech_config: None, port: None, }])), } } pub fn in_dns_https_negative(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Https(Err(())), } } pub fn in_dns_aaaa_positive(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Aaaa(Ok(vec![V6_ADDR])), } } pub fn in_dns_a_positive(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::A(Ok(vec![V4_ADDR])), } } pub fn in_dns_aaaa_negative(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::Aaaa(Err(())), } } pub fn in_dns_a_negative(id: Id) -> Input { Input::DnsResult { id, result: DnsResult::A(Err(())), } } pub fn in_connection_result_positive(id: Id) -> Input { Input::ConnectionResult { id, result: Ok(()) } } pub fn in_connection_result_negative(id: Id) -> Input { Input::ConnectionResult { id, result: Err("connection refused".to_string()), } } pub fn out_send_dns_https(id: Id) -> Output { Output::SendDnsQuery { id, hostname: HOSTNAME.into(), record_type: DnsRecordType::Https, } } pub fn out_send_dns_aaaa(id: Id) -> Output { Output::SendDnsQuery { id, hostname: HOSTNAME.into(), record_type: DnsRecordType::Aaaa, } } pub fn out_send_dns_svc1(id: Id) -> Output { Output::SendDnsQuery { id, hostname: SVC1.into(), record_type: DnsRecordType::Aaaa, } } pub fn out_send_dns_a(id: Id) -> Output { Output::SendDnsQuery { id, hostname: HOSTNAME.into(), record_type: DnsRecordType::A, } } pub fn out_attempt_v6_h1_h2(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V6_ADDR.into(), PORT), protocol: ConnectionAttemptHttpVersions::H2OrH1, ech_config: None, }, } } pub fn out_attempt_v6_h2(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V6_ADDR.into(), PORT), protocol: ConnectionAttemptHttpVersions::H2, ech_config: None, }, } } pub fn out_attempt_v6_h3(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V6_ADDR.into(), PORT), protocol: ConnectionAttemptHttpVersions::H3, ech_config: None, }, } } pub fn out_attempt_v6_h3_custom_port(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V6_ADDR.into(), CUSTOM_PORT), protocol: ConnectionAttemptHttpVersions::H3, ech_config: None, }, } } pub fn out_attempt_v4_h1_h2(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V4_ADDR.into(), PORT), protocol: ConnectionAttemptHttpVersions::H2OrH1, ech_config: None, }, } } pub fn out_attempt_v4_h2(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V4_ADDR.into(), PORT), protocol: ConnectionAttemptHttpVersions::H2, ech_config: None, }, } } pub fn out_attempt_v4_h3(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V4_ADDR.into(), PORT), protocol: ConnectionAttemptHttpVersions::H3, ech_config: None, }, } } pub fn out_attempt_v4_h3_custom_port(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V4_ADDR.into(), CUSTOM_PORT), protocol: ConnectionAttemptHttpVersions::H3, ech_config: None, }, } } pub fn out_attempt_v6_h2_custom_port(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V6_ADDR.into(), CUSTOM_PORT), protocol: ConnectionAttemptHttpVersions::H2, ech_config: None, }, } } pub fn out_attempt_v4_h2_custom_port(id: Id) -> Output { Output::AttemptConnection { id, endpoint: Endpoint { address: SocketAddr::new(V4_ADDR.into(), CUSTOM_PORT), protocol: ConnectionAttemptHttpVersions::H2, ech_config: None, }, } } pub fn out_resolution_delay() -> Output { Output::Timer { duration: RESOLUTION_DELAY, } } pub fn out_connection_attempt_delay() -> Output { Output::Timer { duration: CONNECTION_ATTEMPT_DELAY, } } pub fn setup() -> (Instant, HappyEyeballs) { setup_with_config(NetworkConfig::default()) } pub fn setup_with_config(config: NetworkConfig) -> (Instant, HappyEyeballs) { let _ = env_logger::builder().is_test(true).try_init(); let now = Instant::now(); let he = HappyEyeballs::new_with_network_config(HOSTNAME, PORT, config).unwrap(); (now, he) }