0.3.5-alpha.4
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
// file: crates/common/game-realtime-webtransport-lib/unit_tests/config.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn defaults_are_valid_and_preserve_the_one_mib_baseline() {
|
||||
let config = super::WebTransportConfig::default();
|
||||
assert!(config.validate().is_ok());
|
||||
assert_eq!(config.max_message_size(), 1024 * 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_limits_and_deadlines_are_rejected() {
|
||||
let cases = [
|
||||
super::WebTransportConfig::default().with_max_message_size(0),
|
||||
super::WebTransportConfig::default().with_connect_timeout(std::time::Duration::ZERO),
|
||||
super::WebTransportConfig::default().with_primary_stream_timeout(std::time::Duration::ZERO),
|
||||
super::WebTransportConfig::default().with_send_timeout(std::time::Duration::ZERO),
|
||||
];
|
||||
for config in cases {
|
||||
let result = config.validate();
|
||||
match result {
|
||||
Ok(()) => panic!("invalid WebTransport configuration unexpectedly accepted"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// file: crates/common/game-realtime-webtransport-lib/unit_tests/webtransport.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
|
||||
#[test]
|
||||
fn certificate_hash_preserves_exact_sha256_bytes() {
|
||||
@@ -26,19 +28,33 @@ fn client_config_accepts_https_and_rejects_non_secure_schemes() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_header_is_big_endian_and_payload_bound_is_enforced() {
|
||||
let header = match super::frame_header(0x00_01_02_03) {
|
||||
fn frame_header_is_big_endian_and_configured_payload_bound_is_enforced() {
|
||||
let header = match super::frame_header(0x00_01_02_03, 1024 * 1024) {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("valid frame header rejected: {error}"),
|
||||
};
|
||||
assert_eq!(header, [0x00, 0x01, 0x02, 0x03]);
|
||||
let oversized = super::frame_header(super::PRIMARY_FRAME_MAX_PAYLOAD_SIZE + 1);
|
||||
let oversized = super::frame_header(33, 32);
|
||||
match oversized {
|
||||
Ok(_) => panic!("oversized WebTransport frame unexpectedly accepted"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_error_mapping_distinguishes_abort_close_and_protocol_failures() {
|
||||
let stopped = super::map_write_error(web_transport_quinn::WriteError::Stopped(7));
|
||||
assert_eq!(stopped.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted);
|
||||
let reset = super::map_read_error(web_transport_quinn::ReadError::Reset(8));
|
||||
assert_eq!(reset.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted);
|
||||
let closed = super::map_read_error(web_transport_quinn::ReadError::SessionError(web_transport_quinn::SessionError::WebTransportError(
|
||||
web_transport_quinn::WebTransportError::Closed(9, "done".to_owned()),
|
||||
)));
|
||||
assert_eq!(closed.kind(), game_realtime_transport_lib::TransportErrorKind::Closed);
|
||||
let protocol = super::map_read_error(web_transport_quinn::ReadError::IllegalOrderedRead);
|
||||
assert_eq!(protocol.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_loopback_identity_has_sha256_fingerprint() {
|
||||
let identity = match super::WebTransportServerIdentity::generate_loopback() {
|
||||
@@ -61,3 +77,118 @@ fn injected_identity_rejects_empty_certificate_or_key() {
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn cancelled_receive_preserves_partial_frame_state() {
|
||||
let (server_session, client_session) = establish_private_sessions().await;
|
||||
let (mut raw_sender, _raw_receiver) = match client_session.inner.open_bi().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("raw client stream open failed: {error}"),
|
||||
};
|
||||
let (server_sender, server_receiver) = match server_session.inner.accept_bi().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("raw server stream accept failed: {error}"),
|
||||
};
|
||||
let server_connection = super::WebTransportConnection::new(server_session.inner, server_sender, server_receiver, server_session.transport);
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
if let Err(error) = raw_sender.write_all(&[0x00, 0x00]).await {
|
||||
panic!("partial frame header write failed: {error}");
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
let first_receive =
|
||||
tokio::time::timeout(std::time::Duration::from_millis(100), game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
assert!(first_receive.is_err());
|
||||
assert_eq!(server_receiver.header_read, 2);
|
||||
if let Err(error) = raw_sender.write_all(&[0x00, 0x03, 0x10, 0x20, 0x30]).await {
|
||||
panic!("remaining frame write failed: {error}");
|
||||
}
|
||||
let second_receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
match second_receive {
|
||||
Ok(Ok(game_realtime_transport_lib::TransportReceive::Message(message))) => assert_eq!(message.as_bytes(), &[0x10, 0x20, 0x30]),
|
||||
Ok(Ok(value)) => panic!("resumed receive returned unexpected result: {value:?}"),
|
||||
Ok(Err(error)) => panic!("resumed receive failed: {error}"),
|
||||
Err(_) => panic!("resumed receive timed out"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn truncated_frame_payload_is_a_protocol_failure() {
|
||||
let (server_session, client_session) = establish_private_sessions().await;
|
||||
let (mut raw_sender, _raw_receiver) = match client_session.inner.open_bi().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("raw client stream open failed: {error}"),
|
||||
};
|
||||
let (server_sender, server_receiver) = match server_session.inner.accept_bi().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("raw server stream accept failed: {error}"),
|
||||
};
|
||||
let server_connection = super::WebTransportConnection::new(server_session.inner, server_sender, server_receiver, server_session.transport);
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
if let Err(error) = raw_sender.write_all(&[0x00, 0x00, 0x00, 0x03, 0x10]).await {
|
||||
panic!("truncated frame payload write failed: {error}");
|
||||
}
|
||||
if let Err(error) = raw_sender.finish() {
|
||||
panic!("raw client stream finish failed: {error}");
|
||||
}
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("truncated frame payload produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol),
|
||||
Err(_) => panic!("truncated frame payload was not rejected within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn truncated_frame_header_is_a_protocol_failure() {
|
||||
let (server_session, client_session) = establish_private_sessions().await;
|
||||
let (mut raw_sender, _raw_receiver) = match client_session.inner.open_bi().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("raw client stream open failed: {error}"),
|
||||
};
|
||||
let (server_sender, server_receiver) = match server_session.inner.accept_bi().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("raw server stream accept failed: {error}"),
|
||||
};
|
||||
let server_connection = super::WebTransportConnection::new(server_session.inner, server_sender, server_receiver, server_session.transport);
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
if let Err(error) = raw_sender.write_all(&[0x00, 0x00]).await {
|
||||
panic!("partial frame header write failed: {error}");
|
||||
}
|
||||
if let Err(error) = raw_sender.finish() {
|
||||
panic!("raw client stream finish failed: {error}");
|
||||
}
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("truncated frame header produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol),
|
||||
Err(_) => panic!("truncated frame header was not rejected within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn establish_private_sessions() -> (super::WebTransportSession, super::WebTransportSession) {
|
||||
let identity = match super::WebTransportServerIdentity::generate_loopback() {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback identity generation failed: {error}"),
|
||||
};
|
||||
let certificate_hash = identity.certificate_hash().clone();
|
||||
let server_config = super::WebTransportServerConfig::new(std::net::SocketAddr::from(([127, 0, 0, 1], 0)), identity);
|
||||
let mut listener = match super::WebTransportListener::bind(server_config) {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("WebTransport listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("https://{}/private-test", listener.local_addr());
|
||||
let client_config = match super::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(), super::connect(&client_config));
|
||||
})
|
||||
.await;
|
||||
return 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 private loopback establishment timed out"),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user