122 lines
4.0 KiB
Rust
122 lines
4.0 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use wgcloak::transport::http3;
|
|
use wgcloak::transport::http3::{
|
|
decode_auth_challenge, 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_auth_challenge(&challenge_capsule).unwrap(),
|
|
challenge
|
|
);
|
|
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());
|
|
}
|
|
|
|
#[test]
|
|
fn datagram_limit_includes_http_context_and_request_stream_overhead() {
|
|
use h3::quic::StreamId;
|
|
|
|
let limit = http3::maximum_application_datagram(
|
|
1200,
|
|
StreamId::try_from(0).unwrap(),
|
|
CONNECT_UDP_CONTEXT_ID,
|
|
1200,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(limit, 1198);
|
|
assert!(matches!(
|
|
http3::maximum_application_datagram(
|
|
2,
|
|
StreamId::try_from(0).unwrap(),
|
|
CONNECT_UDP_CONTEXT_ID,
|
|
1200
|
|
),
|
|
Ok(0)
|
|
));
|
|
}
|