From 63c2e2fc3ea299aee31b66d2bdd6ecd871df223a Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 22:44:41 +0100 Subject: v0.3.0: add server core, bcrypt migration and plays audit --- src/bin/server/handler.rs | 252 +++++----------------------------------------- 1 file changed, 27 insertions(+), 225 deletions(-) (limited to 'src/bin') diff --git a/src/bin/server/handler.rs b/src/bin/server/handler.rs index 06ed5cb..86b09cb 100644 --- a/src/bin/server/handler.rs +++ b/src/bin/server/handler.rs @@ -1,228 +1,34 @@ -use zagan_bet::server::database; -use zagan_bet::server::games::registry::get_game_registry; -use zagan_bet::server::games::session::SessionManager; -use zagan_bet::server::games::traits::GameError; use crate::tls::TlsStream; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use rusqlite::Connection; -use std::io::{BufRead, BufReader, Write}; -use std::net::TcpStream; +use zagan_bet::server::core::{ConnectionState, ServerCore}; -static DB_CONN: Lazy> = Lazy::new(|| { - let conn = Connection::open( - std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()), - ) - .unwrap(); - database::init_db(&conn); - Mutex::new(conn) -}); - -static SESSIONS: Lazy = Lazy::new(SessionManager::new); - -pub fn handle_client(stream: TcpStream) { - let client_addr = stream.peer_addr().unwrap().to_string(); - let mut user = "guest".to_string(); - println!("[{} @ {}] Client connected", user, client_addr); - let reader_stream = stream.try_clone().unwrap(); - let reader = BufReader::new(reader_stream); - let mut writer = stream; - for line in reader.lines() { - let line = line.unwrap(); - println!("[{} @ {}] Received: {}", user, client_addr, line); - let response = process_message(&line, &mut user); - writeln!(writer, "{}", response).unwrap(); - println!("[{} @ {}] Sent: {}", user, client_addr, response); - } - println!("[{} @ {}] Client disconnected", user, client_addr); -} - -fn process_message(msg: &str, user: &mut String) -> String { - let parts: Vec<&str> = msg.splitn(5, ' ').collect(); - match parts[0] { - "GAME_LIST" => { - let registry = get_game_registry(); - let games: Vec = registry.list_games(); - if games.is_empty() { - "ERROR No games available".to_string() - } else { - let game_info: Vec = games - .iter() - .filter_map(|gt| { - registry - .get_game(gt) - .map(|g| format!("{}:{}", gt, g.get_name())) - }) - .collect(); - format!("GAME_LIST {}", game_info.join(",")) - } - } - "GAME_INFO" => { - if parts.len() != 2 { - "ERROR Invalid GAME_INFO format. Usage: GAME_INFO ".to_string() - } else { - let registry = get_game_registry(); - match registry.get_game(parts[1]) { - Some(game) => format!( - "GAME_INFO {} {} {}", - parts[1], - game.get_name(), - game.get_description() - ), - None => format!("ERROR Unknown game type: {}", parts[1]), - } - } - } - "PLAY_START" => { - if parts.len() != 3 { - "ERROR Invalid PLAY_START format. Usage: PLAY_START " - .to_string() - } else { - handle_play_start(user, parts[1], parts[2]) - } - } - "REGISTER" => { - if parts.len() != 3 { - "ERROR Invalid REGISTER format".to_string() - } else { - let username = parts[1]; - let hashed_password = parts[2]; - let db = DB_CONN.lock(); - match database::add_user(&db, username, hashed_password) { - Ok(_) => format!("REGISTERED {}", username), - Err(_) => "ERROR User already exists".to_string(), - } - } - } - "AUTH" => { - if parts.len() != 3 { - "ERROR Invalid AUTH format".to_string() - } else { - let username = parts[1]; - let sent_hash = parts[2]; - let db = DB_CONN.lock(); - if let Some(stored_hash) = database::get_hashed_password(&db, username) { - if sent_hash == stored_hash { - *user = username.to_string(); - format!("OK {}", username) - } else { - "ERROR Invalid credentials".to_string() - } - } else { - "ERROR User not found".to_string() - } - } - } - "PING" => "PONG".to_string(), - "INFO" => { - if *user == "guest" { - "ERROR Not logged in".to_string() - } else { - let db = DB_CONN.lock(); - if let (Some(id), Some(balance)) = ( - database::get_account_id(&db, user), - database::get_balance(&db, user), - ) { - format!("INFO {} {} {}", user, id, balance) - } else { - "ERROR Error retrieving user info".to_string() - } - } +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; } - _ => msg.to_string(), - } -} - -fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> String { - if user == "guest" { - return "ERROR Not logged in".to_string(); - } - - let bet_amount: i64 = match bet_amount_str.parse() { - Ok(amount) => amount, - Err(_) => return "ERROR Invalid bet amount".to_string(), }; - if bet_amount <= 0 { - return "ERROR Invalid bet amount".to_string(); - } - let registry = get_game_registry(); - let game = match registry.get_game(game_type) { - Some(game) => game, - None => return format!("ERROR Unknown game type: {}", game_type), - }; - - let db = DB_CONN.lock(); - - // Validate balance inside a read. - let current_balance = match database::get_balance(&db, user) { - Some(balance) => balance, - None => return "ERROR Could not retrieve balance".to_string(), - }; - - if let Err(e) = game.validate_bet(bet_amount as u64, current_balance as u64) { - return match e { - GameError::InsufficientBalance => "ERROR Insufficient balance".to_string(), - GameError::InvalidBet => "ERROR Invalid bet amount".to_string(), - _ => "ERROR Bet validation failed".to_string(), - }; - } - - // Prepare and spin. - let game_state = game.prepare_state(user, bet_amount as u64); - - let new_state = match game.process_action(&game_state, "spin", &[]) { - Ok(state) => state, - Err(e) => return format!("ERROR Failed to spin: {:?}", e), - }; - - let result = match game.calculate_result(&new_state) { - Ok(result) => result, - Err(e) => return format!("ERROR Failed to calculate result: {:?}", e), - }; - - // Create session before touching balance. - let session_id = SESSIONS.create_session(user, game_type.to_string(), new_state.clone()); - - // All balance mutations in a single transaction. - let total_return = result.winnings as i64; - let tx_result = db.execute_batch(&format!( - "UPDATE users SET balance = balance - {bet_amount} WHERE username = '{user}'; - UPDATE users SET balance = balance + {total_return} WHERE username = '{user}';" - )); - - if let Err(e) = tx_result { - let _ = SESSIONS.remove_session(&session_id); - eprintln!("Balance transaction failed for {user}: {e}"); - return "ERROR Failed to update balance".to_string(); - } - - let new_balance = match database::get_balance(&db, user) { - Some(balance) => balance, - None => return "ERROR Could not retrieve new balance".to_string(), + 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; + } }; - let reels = new_state.data.get("reels").cloned().unwrap_or_default(); - let win_type = new_state.data.get("win_type").cloned().unwrap_or_default(); - - format!( - "GAME_RESULT {session_id} {} {} {new_balance} {reels} {win_type}", - result.winnings, result.bet_amount, - ) -} - -pub fn handle_client_tls(mut stream: TlsStream) { - let client_addr = stream.peer_addr().unwrap().to_string(); - let mut user = "guest".to_string(); - println!("[{} @ {}] TLS Client connected", user, client_addr); + let mut state = ConnectionState::default(); + println!("[TLS Client connected] {client_addr}"); loop { let mut line = String::new(); match stream.read_line(&mut line) { Ok(0) => break, Ok(_) => {} - Err(e) => { - eprintln!("Read error: {}", e); + Err(error) => { + eprintln!("Read error: {error}"); break; } } @@ -232,21 +38,17 @@ pub fn handle_client_tls(mut stream: TlsStream) { continue; } - println!("[{} @ {}] Received: {}", user, client_addr, line); - let response = process_message(line, &mut user); - if let Err(e) = stream.write_line(&response) { - eprintln!("Write error: {}", e); + let user = state.user().unwrap_or("guest").to_string(); + println!("[{user} @ {client_addr}] Received: {line}"); + let response = core.handle_message(&mut state, line); + if let Err(error) = stream.write_line(&response) { + eprintln!("Write error: {error}"); break; } - println!("[{} @ {}] Sent: {}", user, client_addr, response); - } - - // Clean up any active game sessions for this user on disconnect. - if user != "guest" { - for session in SESSIONS.get_user_sessions(&user) { - let _ = SESSIONS.remove_session(&session.session_id); - } + let user = state.user().unwrap_or("guest"); + println!("[{user} @ {client_addr}] Sent: {response}"); } - println!("[{} @ {}] TLS Client disconnected", user, client_addr); + core.disconnect(&state); + println!("[TLS Client disconnected] {client_addr}"); } -- cgit v1.2.3