#[path = "../src/transport/http3.rs"] mod http3; use std::collections::HashMap; use http3::{ decode_capsule, decode_http_datagram, encode_auth_challenge, encode_auth_response, encode_capsule, encode_http_datagram, validate_connect_udp_request, AuthChallenge, ConnectUdpRequest, Http3Error, CONNECT_UDP_CONTEXT_ID, }; #[test] fn validates_extended_connect_request() { validate_connect_udp_request("CONNECT", "connect-udp", "/tunnel", "/tunnel").unwrap(); ConnectUdpRequest::new("/tunnel") .validate("/tunnel") .unwrap(); assert!(matches!( validate_connect_udp_request("POST", "connect-udp", "/tunnel", "/tunnel"), Err(Http3Error::InvalidRequest("method must be CONNECT")) )); assert!(matches!( validate_connect_udp_request("CONNECT", "webtransport", "/tunnel", "/tunnel"), Err(Http3Error::InvalidRequest(":protocol must be connect-udp")) )); assert!(validate_connect_udp_request("CONNECT", "connect-udp", "/other", "/tunnel").is_err()); assert!(validate_connect_udp_request("CONNECT", "connect-udp", "tunnel", "tunnel").is_err()); } #[test] fn http_datagram_uses_context_id_and_preserves_opaque_payload() { for context in [0, 63, 64, 16_383, 16_384, (1 << 30) - 1, 1 << 30] { let encoded = encode_http_datagram(context, b"opaque", 1200).unwrap(); assert_eq!( decode_http_datagram(&encoded, context, 1200).unwrap(), b"opaque"[..] ); } assert!(matches!( decode_http_datagram( &encode_http_datagram(7, b"x", 1200).unwrap(), CONNECT_UDP_CONTEXT_ID, 1200 ), Err(Http3Error::ContextMismatch { .. }) )); assert!(matches!( encode_http_datagram(CONNECT_UDP_CONTEXT_ID, &[0; 1201], 1200), Err(Http3Error::Oversized { length: 1201, maximum: 1200 }) )); } #[test] fn capsule_round_trip_and_truncation_are_checked() { let encoded = encode_capsule(0x2a, b"challenge").unwrap(); assert_eq!( decode_capsule(&encoded).unwrap(), (0x2a, bytes::Bytes::from_static(b"challenge")) ); assert!(matches!( decode_capsule(&encoded[..encoded.len() - 1]), Err(Http3Error::Truncated) )); let mut extra = encoded.to_vec(); extra.push(0); assert!(matches!( decode_capsule(&extra), Err(Http3Error::InvalidCapsule) )); } #[test] fn authentication_is_completed_before_session_activation() { let challenge = AuthChallenge::random(); let nonce = [5u8; 16]; let secret = [7u8; 32]; let challenge_capsule = encode_auth_challenge(&challenge).unwrap(); assert_eq!( decode_capsule(&challenge_capsule).unwrap().0, http3::AUTH_CHALLENGE_CAPSULE ); let response = encode_auth_response(&challenge, "laptop", &nonce, &secret).unwrap(); let mut clients = HashMap::new(); clients.insert("laptop".to_owned(), secret.to_vec()); let client = http3::verify_auth_response(&challenge, &response, &clients).unwrap(); assert_eq!(client.id, "laptop"); assert_eq!(client.nonce, nonce); let bad = encode_auth_response(&challenge, "laptop", &nonce, &[8u8; 32]).unwrap(); assert!(http3::verify_auth_response(&challenge, &bad, &clients).is_err()); assert!(http3::is_auth_accept(&http3::encode_auth_accept().unwrap()).unwrap()); assert!(!http3::is_auth_accept(&http3::encode_auth_reject().unwrap()).unwrap()); }