diff --git a/crates/lanparty-relay/src/server.rs b/crates/lanparty-relay/src/server.rs index d47f643..03d1130 100644 --- a/crates/lanparty-relay/src/server.rs +++ b/crates/lanparty-relay/src/server.rs @@ -1632,6 +1632,172 @@ mod tests { assert!(sessions.lock().await.is_empty()); } + #[tokio::test] + async fn reconnects_gateway_while_client_stays_joined() { + let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM); + let rooms = Arc::clone(&server.rooms); + let sessions = Arc::clone(&server.sessions); + let server_addr = server.local_addr().unwrap(); + let server_task = tokio::spawn(async move { + let handles = server.accept_many_for_test(3).await.unwrap(); + let mut accepted = Vec::with_capacity(handles.len()); + + for handle in handles { + accepted.push(handle.await.unwrap().unwrap().unwrap()); + } + + server.shutdown("test complete").await; + accepted + }); + let cert_der = certificate.as_ref().to_vec(); + let room = RoomCode::new("TESTROOM").unwrap(); + let client_mac = client_mac(1); + let first_gateway = connect_gateway( + GatewayConfig::new( + server_addr, + "lanparty-relay.local", + cert_der.clone(), + room.clone(), + "eth0", + 1400, + ) + .unwrap(), + ) + .await + .unwrap(); + let client = connect_client( + ClientSessionConfig::new( + server_addr, + "lanparty-relay.local", + cert_der.clone(), + room.clone(), + client_mac, + 1400, + ) + .unwrap(), + ) + .await + .unwrap(); + + let first_gateway_peer_id = first_gateway.welcome().peer_id(); + assert_eq!(first_gateway_peer_id, 1); + assert_eq!(client.welcome().peer_id(), 2); + assert!(client.welcome().gateway_connected()); + assert_eq!(client.welcome().gateway_peer_id(), Some(1)); + + let ControlMessage::PeerJoined(peer) = + tokio::time::timeout(Duration::from_secs(5), first_gateway.recv_control_event()) + .await + .unwrap() + .unwrap() + else { + panic!("expected first gateway to observe client join"); + }; + assert_eq!(peer.peer_id(), client.welcome().peer_id()); + assert_eq!(peer.mac(), Some(client_mac)); + + let ControlMessage::PeerJoined(peer) = + tokio::time::timeout(Duration::from_secs(5), client.recv_control_event()) + .await + .unwrap() + .unwrap() + else { + panic!("expected client to receive first gateway catch-up event"); + }; + assert_eq!(peer.peer_id(), first_gateway_peer_id); + assert_eq!(peer.role(), Role::Gateway); + + first_gateway.shutdown("gateway restart").await; + + let ControlMessage::PeerLeft { peer_id, reason } = + tokio::time::timeout(Duration::from_secs(5), client.recv_control_event()) + .await + .unwrap() + .unwrap() + else { + panic!("expected client to observe first gateway leave"); + }; + assert_eq!(peer_id, first_gateway_peer_id); + assert_eq!(reason, DisconnectReason::Normal); + + let second_gateway = connect_gateway( + GatewayConfig::new( + server_addr, + "lanparty-relay.local", + cert_der, + room.clone(), + "eth0", + 1400, + ) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(second_gateway.welcome().peer_id(), 3); + + let ControlMessage::PeerJoined(peer) = + tokio::time::timeout(Duration::from_secs(5), second_gateway.recv_control_event()) + .await + .unwrap() + .unwrap() + else { + panic!("expected second gateway to receive client catch-up event"); + }; + assert_eq!(peer.peer_id(), client.welcome().peer_id()); + assert_eq!(peer.role(), Role::Client); + assert_eq!(peer.mac(), Some(client_mac)); + + let ControlMessage::PeerJoined(peer) = + tokio::time::timeout(Duration::from_secs(5), client.recv_control_event()) + .await + .unwrap() + .unwrap() + else { + panic!("expected client to observe replacement gateway join"); + }; + assert_eq!(peer.peer_id(), second_gateway.welcome().peer_id()); + assert_eq!(peer.role(), Role::Gateway); + + let client_to_lan = ethernet_frame(gateway_mac(), client_mac); + assert_eq!( + client + .relay_io() + .send_ethernet_with_outcome(&client_to_lan) + .unwrap(), + lanparty_client_core::ClientSendOutcome::Sent + ); + let received = tokio::time::timeout(Duration::from_secs(5), second_gateway.recv_ethernet()) + .await + .unwrap() + .unwrap(); + assert_eq!(received.source_peer_id(), client.welcome().peer_id()); + assert_eq!(received.payload(), client_to_lan.as_slice()); + + let lan_to_client = ethernet_frame(client_mac, gateway_mac()); + second_gateway.send_ethernet(&lan_to_client).unwrap(); + let received = + tokio::time::timeout(Duration::from_secs(5), client.relay_io().recv_ethernet()) + .await + .unwrap() + .unwrap(); + assert_eq!( + received.source_peer_id(), + second_gateway.welcome().peer_id() + ); + assert_eq!(received.payload(), lan_to_client.as_slice()); + + client.shutdown("test client done").await; + second_gateway.shutdown("test gateway done").await; + + let accepted = tokio::time::timeout(Duration::from_secs(5), server_task) + .await + .unwrap() + .unwrap(); + assert_eq!(accepted.len(), 3); + assert_eq!(rooms.lock().await.room_count(), 0); + assert!(sessions.lock().await.is_empty()); + } + #[tokio::test] async fn forwards_graceful_disconnect_reason_to_remaining_peers() { let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM);