diff --git a/packages/cultnet-rs/tests/rudp_stray_packets.rs b/packages/cultnet-rs/tests/rudp_stray_packets.rs index 58f491fa..5dc39732 100644 --- a/packages/cultnet-rs/tests/rudp_stray_packets.rs +++ b/packages/cultnet-rs/tests/rudp_stray_packets.rs @@ -50,6 +50,24 @@ fn send_to(socket: &UdpSocket, to: SocketAddr, packet: &CultNetRudpPacket) -> Re Ok(()) } +/// How many datagrams `send_stray` puts on the wire for `kind`. +fn datagrams(kind: Stray) -> u64 { + match kind { + Stray::Malformed => 2, + Stray::ForeignConnectionId => 4, + Stray::UnadmittedSender => 3, + } +} + +/// A packet the sender's own session accepted for sending and its receiver's +/// session refuses: a fragment with no fragment id. +fn poisoned(session: &mut CultNetRudpSession) -> Result { + let mut packet = session.send("schema", b"poison".to_vec(), reliable())?; + packet.fragment_count = 2; + packet.fragment_id = 0; + Ok(packet) +} + fn send_stray(kind: Stray, from: &UdpSocket, to: SocketAddr) -> Result<()> { match kind { Stray::Malformed => { @@ -98,7 +116,7 @@ fn hub_keeps_serving_its_session(kind: Stray) -> Result<()> { send_stray(kind, &stray, hub_addr)?; std::thread::sleep(Duration::from_millis(20)); while hub.receive_event_once()?.is_some() {} - assert!(hub.stats().packets_dropped > 0, "strays are counted"); + assert_eq!(hub.stats().packets_dropped, datagrams(kind), "every stray is counted once"); real.send("schema", b"still here".to_vec())?; std::thread::sleep(Duration::from_millis(20)); @@ -174,7 +192,7 @@ fn client_keeps_its_session(kind: Stray) -> Result<()> { .receive_once()? .expect("the real frame behind the strays"); assert_eq!(frame.payload, b"real"); - assert!(client.stats().packets_dropped > 0, "strays are counted"); + assert_eq!(client.stats().packets_dropped, datagrams(kind), "every stray is counted once"); Ok(()) } @@ -206,7 +224,8 @@ fn server_mode_keeps_its_peer(kind: Stray) -> Result<()> { .receive_once()? .expect("the real frame behind the strays"); assert_eq!(frame.payload, b"real"); - assert!(server.stats().packets_dropped > 0, "strays are counted"); + // Once from an unrelated address, once from the peer's own. + assert_eq!(server.stats().packets_dropped, 2 * datagrams(kind), "every stray is counted once"); Ok(()) } @@ -253,3 +272,111 @@ per_stray!( server_mode_survives_malformed_frames, server_mode_survives_foreign_connection_ids ); + +#[test] +fn hub_drops_a_packet_its_admitted_peer_session_refuses() -> Result<()> { + let mut hub = CultNetRudpServerHub::new(CultNetRudpServerHubOptions::new( + "hub", + socket()?, + CONNECTION_ID, + ))?; + let hub_addr = hub.local_addr()?; + let peer = socket()?; + let mut peer_session = raw_session(CONNECTION_ID); + send_to(&peer, hub_addr, &peer_session.create_connect(0, b"peer".to_vec())?)?; + assert!(matches!( + hub.receive_event_once()?, + Some(CultNetRudpServerEvent::Connected { .. }) + )); + let mut buffer = vec![0_u8; 65_535]; + let (received, _) = peer.recv_from(&mut buffer)?; + peer_session.receive(&decode_rudp_packet(&buffer[..received])?, 0)?; + + send_to(&peer, hub_addr, &poisoned(&mut peer_session)?)?; + assert!(hub.receive_event_once()?.is_none()); + assert_eq!(hub.stats().packets_dropped, 1); + assert_eq!(hub.sessions().len(), 1); + + send_to( + &peer, + hub_addr, + &peer_session.send("schema", b"still here".to_vec(), reliable())?, + )?; + match hub.receive_event_once()? { + Some(CultNetRudpServerEvent::Frame { frame, .. }) => { + assert_eq!(frame.payload, b"still here"); + } + other => panic!("the peer must still be served, got {other:?}"), + } + Ok(()) +} + +#[test] +fn client_drops_a_packet_its_session_refuses() -> Result<()> { + let server = socket()?; + let server_addr = server.local_addr()?; + let mut client = CultNetRudpSocketTransportConnection::new( + CultNetRudpSocketTransportOptions::client("client", socket()?, server_addr, CONNECTION_ID), + )?; + client.connect(b"hello".to_vec())?; + let mut buffer = vec![0_u8; 65_535]; + let (received, client_addr) = server.recv_from(&mut buffer)?; + let mut server_session = raw_session(CONNECTION_ID); + let accept = + server_session.accept_connect(&decode_rudp_packet(&buffer[..received])?, 0, Vec::new())?; + send_to(&server, client_addr, &accept)?; + let _ = client.receive_once()?; + + send_to(&server, client_addr, &poisoned(&mut server_session)?)?; + assert!(client.receive_once()?.is_none()); + assert_eq!(client.stats().packets_dropped, 1); + send_to( + &server, + client_addr, + &server_session.send("schema", b"real".to_vec(), reliable())?, + )?; + assert_eq!(client.receive_once()?.expect("the real frame").payload, b"real"); + Ok(()) +} + +#[test] +fn server_mode_drops_what_is_not_its_peer_or_what_its_session_refuses() -> Result<()> { + let server_socket = socket()?; + let server_addr = server_socket.local_addr()?; + let mut server = CultNetRudpSocketTransportConnection::new( + CultNetRudpSocketTransportOptions::server("server", server_socket, CONNECTION_ID), + )?; + let stray = socket()?; + + // Before any peer: a right-id packet that is not a Connect claims nothing. + send_stray(Stray::UnadmittedSender, &stray, server_addr)?; + assert!(server.receive_once()?.is_none()); + assert_eq!(server.stats().packets_dropped, 3); + + let peer = socket()?; + let mut peer_session = raw_session(CONNECTION_ID); + send_to(&peer, server_addr, &peer_session.create_connect(0, b"peer".to_vec())?)?; + let _ = server.receive_once()?; + let mut buffer = vec![0_u8; 65_535]; + let (received, _) = peer.recv_from(&mut buffer)?; + peer_session.receive(&decode_rudp_packet(&buffer[..received])?, 0)?; + assert_eq!(server.stats().packets_dropped, 3); + + // After the peer is known: right-id packets from any other address. + send_stray(Stray::UnadmittedSender, &stray, server_addr)?; + assert!(server.receive_once()?.is_none()); + assert_eq!(server.stats().packets_dropped, 6); + + // A packet from the peer that the session itself refuses. + send_to(&peer, server_addr, &poisoned(&mut peer_session)?)?; + assert!(server.receive_once()?.is_none()); + assert_eq!(server.stats().packets_dropped, 7); + + send_to( + &peer, + server_addr, + &peer_session.send("schema", b"real".to_vec(), reliable())?, + )?; + assert_eq!(server.receive_once()?.expect("the real frame").payload, b"real"); + Ok(()) +}