use std::time::Duration; use crate::tls::TlsStream; use zagan_bet::protocol::{event_line, parse_request, reply_line}; use zagan_bet::server::core::{ConnectionState, ServerCore}; /// How long the connection waits for a request before advancing timed games. const POLL_INTERVAL: Duration = Duration::from_millis(50); pub fn handle_client_tls(mut stream: TlsStream) { let client_addr = match stream.peer_addr() { Ok(addr) => addr.to_string(), Err(error) => { eprintln!("Could not get peer address: {error}"); return; } }; let db_path = std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()); let mut core = match ServerCore::open(&db_path) { Ok(core) => core, Err(error) => { eprintln!("Failed to open database {db_path}: {error}"); return; } }; if let Err(error) = stream.set_read_timeout(Some(POLL_INTERVAL)) { eprintln!("Could not set read timeout: {error}"); return; } let mut state = ConnectionState::default(); let mut event_seq = 0u64; // Port scanners reach the TLS listener too. Do not write a connection log // until they send a valid frame; v5 drops their HTTP request silently. let mut announced = false; 'connection: loop { let mut frame = String::new(); match stream.read_frame(&mut frame) { Ok(0) => break, Ok(_) => { if !announced { println!("[TLS Client connected] {client_addr}"); announced = true; } let user = state.user().unwrap_or("guest").to_string(); println!("[{user} @ {client_addr}] Received: {frame}"); match parse_request(&frame) { Ok(request) => { let response = core.handle_request(&mut state, &request); let reply = reply_line(request.id, &response); if let Err(error) = stream.write_frame(&reply) { eprintln!("Write error: {error}"); break; } let user = state.user().unwrap_or("guest"); println!("[{user} @ {client_addr}] Sent: {reply}"); } Err(message) => { let reply = reply_line(0, &format!("ERROR {message}")); if let Err(error) = stream.write_frame(&reply) { eprintln!("Write error: {error}"); break; } eprintln!("Malformed request from {client_addr}: {reply}"); } } } Err(error) if matches!( error.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut ) => { // No request yet: fall through and let timed games advance. } Err(error) if error.kind() == std::io::ErrorKind::InvalidData => { // A crawler or port scanner speaking HTTP hits a frame length // far over the cap. Close without a reply or a log line; a // real game client that has already greeted is worth logging. if state.greeted() { eprintln!("Protocol error from {client_addr}: {error}"); } break; } Err(error) => { eprintln!("Read error: {error}"); break; } } for push in core.poll(&state) { event_seq = event_seq.wrapping_add(1); let event = event_line(event_seq, &push); if let Err(error) = stream.write_frame(&event) { eprintln!("Write error: {error}"); break 'connection; } // Ticks are 10 Hz per live round; logging them floods the disk. if !push.starts_with("crash.tick ") { let user = state.user().unwrap_or("guest"); println!("[{user} @ {client_addr}] Pushed: {event}"); } } } core.disconnect(&state); if announced || state.greeted() { println!("[TLS Client disconnected] {client_addr}"); } }