// file: crates/common/game-realtime-webtransport-lib/tests/establishment.rs // version: 2 //! Deterministic native loopback proof for WebTransport session establishment and SHA-256 pinning. const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); fn normalized_socket_addr(value: std::net::SocketAddr) -> std::net::SocketAddr { match value { std::net::SocketAddr::V4(_) => return value, std::net::SocketAddr::V6(ipv6) => match ipv6.ip().to_ipv4_mapped() { Some(ipv4) => return std::net::SocketAddr::new(std::net::IpAddr::V4(ipv4), ipv6.port()), None => return std::net::SocketAddr::V6(ipv6), }, } } #[tokio::test(flavor = "current_thread")] async fn pinned_client_and_server_establish_a_loopback_session() { let identity = match game_realtime_webtransport_lib::WebTransportServerIdentity::generate_loopback() { Ok(value) => value, Err(error) => panic!("loopback identity generation failed: {error}"), }; let certificate_hash = identity.certificate_hash().clone(); let server_config = game_realtime_webtransport_lib::WebTransportServerConfig::new(std::net::SocketAddr::from(([127, 0, 0, 1], 0)), identity); let mut listener = match game_realtime_webtransport_lib::WebTransportListener::bind(server_config) { Ok(value) => value, Err(error) => panic!("WebTransport listener bind failed: {error}"), }; let endpoint = format!("https://{}/establishment", listener.local_addr()); let client_config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(endpoint.as_str(), certificate_hash) { Ok(value) => value, Err(error) => panic!("WebTransport client configuration failed: {error}"), }; let pair = tokio::time::timeout(TEST_TIMEOUT, async { return tokio::join!(listener.accept(), game_realtime_webtransport_lib::connect(&client_config)); }) .await; let (server_session, client_session) = match pair { Ok((Ok(server), Ok(client))) => (server, client), Ok((Err(error), _)) => panic!("WebTransport server establishment failed: {error}"), Ok((_, Err(error))) => panic!("WebTransport client establishment failed: {error}"), Err(_) => panic!("WebTransport loopback establishment timed out"), }; assert_eq!(client_session.request_url(), Some(endpoint.as_str())); assert_eq!(server_session.request_url(), Some(endpoint.as_str())); assert_eq!(normalized_socket_addr(client_session.remote_addr()), normalized_socket_addr(listener.local_addr())); } #[tokio::test(flavor = "current_thread")] async fn incorrect_certificate_pin_rejects_establishment() { let identity = match game_realtime_webtransport_lib::WebTransportServerIdentity::generate_loopback() { Ok(value) => value, Err(error) => panic!("loopback identity generation failed: {error}"), }; let server_config = game_realtime_webtransport_lib::WebTransportServerConfig::new(std::net::SocketAddr::from(([127, 0, 0, 1], 0)), identity); let mut listener = match game_realtime_webtransport_lib::WebTransportListener::bind(server_config) { Ok(value) => value, Err(error) => panic!("WebTransport listener bind failed: {error}"), }; let endpoint = format!("https://{}/wrong-pin", listener.local_addr()); let wrong_hash = game_realtime_webtransport_lib::WebTransportCertificateHash::from_sha256([0_u8; 32]); let client_config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(endpoint.as_str(), wrong_hash) { Ok(value) => value, Err(error) => panic!("WebTransport client configuration failed: {error}"), }; let (server_result, client_result) = tokio::join!( tokio::time::timeout(TEST_TIMEOUT, listener.accept()), tokio::time::timeout(TEST_TIMEOUT, game_realtime_webtransport_lib::connect(&client_config)), ); match client_result { Ok(Ok(_)) => panic!("WebTransport establishment unexpectedly accepted an incorrect certificate pin"), Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Connect), Err(_) => panic!("incorrect-pin connection attempt timed out"), } assert!(matches!(server_result, Err(_) | Ok(Err(_)))); }