summaryrefslogtreecommitdiffstats
path: root/src/bin/server/handler.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/bin/server/handler.rs98
1 files changed, 52 insertions, 46 deletions
diff --git a/src/bin/server/handler.rs b/src/bin/server/handler.rs
index 48e9d3a..5406e85 100644
--- a/src/bin/server/handler.rs
+++ b/src/bin/server/handler.rs
@@ -1,20 +1,25 @@
1use crate::database; 1use crate::database;
2use crate::games::registry::get_game_registry; 2use crate::games::registry::get_game_registry;
3use crate::games::traits::GameError; 3use crate::games::session::SessionManager;
4use crate::games::traits::{GameError, GamePhase};
4use crate::tls::TlsStream; 5use crate::tls::TlsStream;
5use once_cell::sync::Lazy; 6use once_cell::sync::Lazy;
7use parking_lot::Mutex;
6use rusqlite::Connection; 8use rusqlite::Connection;
7use std::io::{BufRead, BufReader, Write}; 9use std::io::{BufRead, BufReader, Write};
8use std::net::TcpStream; 10use std::net::TcpStream;
9use std::sync::Mutex;
10 11
11static DB_CONN: Lazy<Mutex<Connection>> = Lazy::new(|| { 12static DB_CONN: Lazy<Mutex<Connection>> = Lazy::new(|| {
12 let conn = Connection::open(std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string())).unwrap(); 13 let conn = Connection::open(
14 std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()),
15 )
16 .unwrap();
13 database::init_db(&conn); 17 database::init_db(&conn);
14 Mutex::new(conn) 18 Mutex::new(conn)
15}); 19});
16 20
17#[allow(dead_code)] 21static SESSIONS: Lazy<SessionManager> = Lazy::new(SessionManager::new);
22
18pub fn handle_client(stream: TcpStream) { 23pub fn handle_client(stream: TcpStream) {
19 let client_addr = stream.peer_addr().unwrap().to_string(); 24 let client_addr = stream.peer_addr().unwrap().to_string();
20 let mut user = "guest".to_string(); 25 let mut user = "guest".to_string();
@@ -82,7 +87,7 @@ fn process_message(msg: &str, user: &mut String) -> String {
82 } else { 87 } else {
83 let username = parts[1]; 88 let username = parts[1];
84 let hashed_password = parts[2]; 89 let hashed_password = parts[2];
85 let db = DB_CONN.lock().unwrap(); 90 let db = DB_CONN.lock();
86 match database::add_user(&db, username, hashed_password) { 91 match database::add_user(&db, username, hashed_password) {
87 Ok(_) => format!("REGISTERED {}", username), 92 Ok(_) => format!("REGISTERED {}", username),
88 Err(_) => "ERROR User already exists".to_string(), 93 Err(_) => "ERROR User already exists".to_string(),
@@ -95,7 +100,7 @@ fn process_message(msg: &str, user: &mut String) -> String {
95 } else { 100 } else {
96 let username = parts[1]; 101 let username = parts[1];
97 let sent_hash = parts[2]; 102 let sent_hash = parts[2];
98 let db = DB_CONN.lock().unwrap(); 103 let db = DB_CONN.lock();
99 if let Some(stored_hash) = database::get_hashed_password(&db, username) { 104 if let Some(stored_hash) = database::get_hashed_password(&db, username) {
100 if sent_hash == stored_hash { 105 if sent_hash == stored_hash {
101 *user = username.to_string(); 106 *user = username.to_string();
@@ -113,7 +118,7 @@ fn process_message(msg: &str, user: &mut String) -> String {
113 if *user == "guest" { 118 if *user == "guest" {
114 "ERROR Not logged in".to_string() 119 "ERROR Not logged in".to_string()
115 } else { 120 } else {
116 let db = DB_CONN.lock().unwrap(); 121 let db = DB_CONN.lock();
117 if let (Some(id), Some(balance)) = ( 122 if let (Some(id), Some(balance)) = (
118 database::get_account_id(&db, user), 123 database::get_account_id(&db, user),
119 database::get_balance(&db, user), 124 database::get_balance(&db, user),
@@ -133,16 +138,13 @@ fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> Strin
133 return "ERROR Not logged in".to_string(); 138 return "ERROR Not logged in".to_string();
134 } 139 }
135 140
136 let bet_amount: u64 = match bet_amount_str.parse() { 141 let bet_amount: i64 = match bet_amount_str.parse() {
137 Ok(amount) => amount, 142 Ok(amount) => amount,
138 Err(_) => return "ERROR Invalid bet amount".to_string(), 143 Err(_) => return "ERROR Invalid bet amount".to_string(),
139 }; 144 };
140 145 if bet_amount <= 0 {
141 let db = DB_CONN.lock().unwrap(); 146 return "ERROR Invalid bet amount".to_string();
142 let current_balance = match database::get_balance(&db, user) { 147 }
143 Some(balance) => balance as u64,
144 None => return "ERROR Could not retrieve balance".to_string(),
145 };
146 148
147 let registry = get_game_registry(); 149 let registry = get_game_registry();
148 let game = match registry.get_game(game_type) { 150 let game = match registry.get_game(game_type) {
@@ -150,7 +152,15 @@ fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> Strin
150 None => return format!("ERROR Unknown game type: {}", game_type), 152 None => return format!("ERROR Unknown game type: {}", game_type),
151 }; 153 };
152 154
153 if let Err(e) = game.validate_bet(bet_amount, current_balance) { 155 let db = DB_CONN.lock();
156
157 // Validate balance inside a read.
158 let current_balance = match database::get_balance(&db, user) {
159 Some(balance) => balance,
160 None => return "ERROR Could not retrieve balance".to_string(),
161 };
162
163 if let Err(e) = game.validate_bet(bet_amount as u64, current_balance as u64) {
154 return match e { 164 return match e {
155 GameError::InsufficientBalance => "ERROR Insufficient balance".to_string(), 165 GameError::InsufficientBalance => "ERROR Insufficient balance".to_string(),
156 GameError::InvalidBet => "ERROR Invalid bet amount".to_string(), 166 GameError::InvalidBet => "ERROR Invalid bet amount".to_string(),
@@ -158,44 +168,32 @@ fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> Strin
158 }; 168 };
159 } 169 }
160 170
161 let game_state = game.prepare_state(user, bet_amount); 171 // Prepare and spin.
162 172 let game_state = game.prepare_state(user, bet_amount as u64);
163 drop(db);
164 let db = DB_CONN.lock().unwrap();
165 if let Err(_) = db.execute(
166 "UPDATE users SET balance = balance - ?1 WHERE username = ?2",
167 (bet_amount as i64, user),
168 ) {
169 return "ERROR Failed to deduct bet".to_string();
170 }
171 173
172 let new_state = match game.process_action(&game_state, "spin", &[]) { 174 let new_state = match game.process_action(&game_state, "spin", &[]) {
173 Ok(state) => state, 175 Ok(state) => state,
174 Err(e) => { 176 Err(e) => return format!("ERROR Failed to spin: {:?}", e),
175 let _ = db.execute(
176 "UPDATE users SET balance = balance + ?1 WHERE username = ?2",
177 (bet_amount as i64, user),
178 );
179 return format!("ERROR Failed to spin: {:?}", e);
180 }
181 }; 177 };
182 178
183 let result = match game.calculate_result(&new_state) { 179 let result = match game.calculate_result(&new_state) {
184 Ok(result) => result, 180 Ok(result) => result,
185 Err(e) => { 181 Err(e) => return format!("ERROR Failed to calculate result: {:?}", e),
186 let _ = db.execute(
187 "UPDATE users SET balance = balance + ?1 WHERE username = ?2",
188 (bet_amount as i64, user),
189 );
190 return format!("ERROR Failed to calculate result: {:?}", e);
191 }
192 }; 182 };
193 183
194 let total_return = result.bet_amount + result.winnings; 184 // Create session before touching balance.
195 if let Err(_) = db.execute( 185 let session_id = SESSIONS.create_session(user, game_type.to_string(), new_state.clone());
196 "UPDATE users SET balance = balance + ?1 WHERE username = ?2", 186
197 (total_return as i64, user), 187 // All balance mutations in a single transaction.
198 ) { 188 let total_return = bet_amount + result.winnings as i64;
189 let tx_result = db.execute_batch(&format!(
190 "UPDATE users SET balance = balance - {bet_amount} WHERE username = '{user}';
191 UPDATE users SET balance = balance + {total_return} WHERE username = '{user}';"
192 ));
193
194 if let Err(e) = tx_result {
195 let _ = SESSIONS.remove_session(&session_id);
196 eprintln!("Balance transaction failed for {user}: {e}");
199 return "ERROR Failed to update balance".to_string(); 197 return "ERROR Failed to update balance".to_string();
200 } 198 }
201 199
@@ -208,8 +206,8 @@ fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> Strin
208 let win_type = new_state.data.get("win_type").cloned().unwrap_or_default(); 206 let win_type = new_state.data.get("win_type").cloned().unwrap_or_default();
209 207
210 format!( 208 format!(
211 "GAME_RESULT {} {} {} {} {} {}", 209 "GAME_RESULT {session_id} {} {} {new_balance} {reels} {win_type}",
212 result.session_id, result.winnings, result.bet_amount, new_balance, reels, win_type 210 result.winnings, result.bet_amount,
213 ) 211 )
214} 212}
215 213
@@ -242,5 +240,13 @@ pub fn handle_client_tls(mut stream: TlsStream) {
242 } 240 }
243 println!("[{} @ {}] Sent: {}", user, client_addr, response); 241 println!("[{} @ {}] Sent: {}", user, client_addr, response);
244 } 242 }
243
244 // Clean up any active game sessions for this user on disconnect.
245 if user != "guest" {
246 for session in SESSIONS.get_user_sessions(&user) {
247 let _ = SESSIONS.remove_session(&session.session_id);
248 }
249 }
250
245 println!("[{} @ {}] TLS Client disconnected", user, client_addr); 251 println!("[{} @ {}] TLS Client disconnected", user, client_addr);
246} 252}