147 lines
9.0 KiB
Rust
147 lines
9.0 KiB
Rust
// file: crates/common/game-realtime-webtransport-lib/tests/robustness.rs
|
|
// version: 2
|
|
|
|
//! Negative and bounded lifecycle tests for the native reliable WebTransport backend.
|
|
|
|
const SHORT_OPERATION_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(50);
|
|
const SMALL_MESSAGE_LIMIT: usize = 32;
|
|
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn outbound_payload_over_the_configured_limit_is_rejected_before_write() {
|
|
let config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT);
|
|
let (_server_connection, client_connection) = establish_backend_pair(config, config).await;
|
|
let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
|
let oversized = game_realtime_transport_lib::TransportMessage::new(vec![7; SMALL_MESSAGE_LIMIT + 1]);
|
|
let result = game_realtime_transport_lib::RealtimeSender::send(&mut client_sender, oversized).await;
|
|
match result {
|
|
Ok(()) => panic!("oversized outbound WebTransport payload was accepted"),
|
|
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn inbound_payload_over_the_configured_limit_is_rejected_before_allocation() {
|
|
let server_config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT);
|
|
let client_config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT + 1);
|
|
let (server_connection, client_connection) = establish_backend_pair(server_config, client_config).await;
|
|
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
|
let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
|
let payload = game_realtime_transport_lib::TransportMessage::new(vec![3; SMALL_MESSAGE_LIMIT + 1]);
|
|
if let Err(error) = game_realtime_transport_lib::RealtimeSender::send(&mut client_sender, payload).await {
|
|
panic!("client failed to send payload allowed by its local bound: {error}");
|
|
}
|
|
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
|
match receive {
|
|
Ok(Ok(value)) => panic!("oversized inbound WebTransport payload produced a successful receive: {value:?}"),
|
|
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
|
Err(_) => panic!("oversized inbound WebTransport payload did not complete within the test timeout"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn explicit_sender_abort_is_observed_as_aborted_receive() {
|
|
let config = game_realtime_webtransport_lib::WebTransportConfig::default();
|
|
let (server_connection, client_connection) = establish_backend_pair(config, config).await;
|
|
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
|
let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
|
if let Err(error) = client_sender.abort(0x41) {
|
|
panic!("client sender abort 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!("reset WebTransport stream produced a successful receive: {value:?}"),
|
|
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted),
|
|
Err(_) => panic!("peer reset was not observed within the test timeout"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn explicit_receiver_abort_makes_the_local_receive_half_terminal() {
|
|
let config = game_realtime_webtransport_lib::WebTransportConfig::default();
|
|
let (server_connection, _client_connection) = establish_backend_pair(config, config).await;
|
|
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
|
if let Err(error) = server_receiver.abort(0x42) {
|
|
panic!("server receiver abort failed: {error}");
|
|
}
|
|
let result = game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver).await;
|
|
match result {
|
|
Ok(value) => panic!("aborted WebTransport receiver produced a successful receive: {value:?}"),
|
|
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted),
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn dropping_sender_without_close_resets_the_stream_instead_of_synthesizing_fin() {
|
|
let config = game_realtime_webtransport_lib::WebTransportConfig::default();
|
|
let (server_connection, client_connection) = establish_backend_pair(config, config).await;
|
|
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
|
let (client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
|
drop(client_sender);
|
|
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
|
match receive {
|
|
Ok(Ok(value)) => panic!("dropped WebTransport sender produced a clean receive result: {value:?}"),
|
|
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted),
|
|
Err(_) => panic!("sender drop reset was not observed within the test timeout"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn primary_stream_accept_honors_the_configured_deadline() {
|
|
let config = game_realtime_webtransport_lib::WebTransportConfig::default().with_primary_stream_timeout(SHORT_OPERATION_TIMEOUT);
|
|
let (server_session, _client_session) = establish_sessions(config, config).await;
|
|
let result = server_session.accept_primary_connection().await;
|
|
match result {
|
|
Ok(_) => panic!("primary stream accept unexpectedly succeeded without a peer-created stream"),
|
|
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Timeout),
|
|
}
|
|
}
|
|
|
|
async fn establish_backend_pair(
|
|
server_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
|
client_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
|
) -> (game_realtime_webtransport_lib::WebTransportConnection, game_realtime_webtransport_lib::WebTransportConnection) {
|
|
let (server_session, client_session) = establish_sessions(server_transport, client_transport).await;
|
|
let client_connection = match client_session.open_primary_connection().await {
|
|
Ok(value) => value,
|
|
Err(error) => panic!("client primary stream open failed: {error}"),
|
|
};
|
|
let server_connection = match server_session.accept_primary_connection().await {
|
|
Ok(value) => value,
|
|
Err(error) => panic!("server primary stream accept failed: {error}"),
|
|
};
|
|
return (server_connection, client_connection);
|
|
}
|
|
|
|
async fn establish_sessions(
|
|
server_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
|
client_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
|
) -> (game_realtime_webtransport_lib::WebTransportSession, game_realtime_webtransport_lib::WebTransportSession) {
|
|
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)
|
|
.with_transport_config(server_transport);
|
|
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://{}/robustness", listener.local_addr());
|
|
let client_config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(endpoint.as_str(), certificate_hash) {
|
|
Ok(value) => value.with_transport_config(client_transport),
|
|
Err(error) => panic!("WebTransport client configuration failed: {error}"),
|
|
};
|
|
let sessions = tokio::time::timeout(TEST_TIMEOUT, async {
|
|
return tokio::join!(listener.accept(), game_realtime_webtransport_lib::connect(&client_config));
|
|
})
|
|
.await;
|
|
return match sessions {
|
|
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"),
|
|
};
|
|
}
|