summaryrefslogtreecommitdiffstats
path: root/src/bin/server/handler.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/server/handler.rs')
-rw-r--r--src/bin/server/handler.rs252
1 files changed, 27 insertions, 225 deletions
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 @@
1use zagan_bet::server::database;
2use zagan_bet::server::games::registry::get_game_registry;
3use zagan_bet::server::games::session::SessionManager;
4use zagan_bet::server::games::traits::GameError;
5use crate::tls::TlsStream; 1use crate::tls::TlsStream;
6use once_cell::sync::Lazy; 2use zagan_bet::server::core::{ConnectionState, ServerCore};
7use parking_lot::Mutex;
8use rusqlite::Connection;
9use std::io::{BufRead, BufReader, Write};
10use std::net::TcpStream;
11 3
12static DB_CONN: Lazy<Mutex<Connection>> = Lazy::new(|| { 4pub fn handle_client_tls(mut stream: TlsStream) {
13 let conn = Connection::open( 5 let client_addr = match stream.peer_addr() {
14 std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()), 6 Ok(addr) => addr.to_string(),
15 ) 7 Err(error) => {
16 .unwrap(); 8 eprintln!("Could not get peer address: {error}");
17 database::init_db(&conn); 9 return;
18 Mutex::new(conn)
19});
20
21static SESSIONS: Lazy<SessionManager> = Lazy::new(SessionManager::new);
22
23pub fn handle_client(stream: TcpStream) {
24 let client_addr = stream.peer_addr().unwrap().to_string();
25 let mut user = "guest".to_string();
26 println!("[{} @ {}] Client connected", user, client_addr);
27 let reader_stream = stream.try_clone().unwrap();
28 let reader = BufReader::new(reader_stream);
29 let mut writer = stream;
30 for line in reader.lines() {
31 let line = line.unwrap();
32 println!("[{} @ {}] Received: {}", user, client_addr, line);
33 let response = process_message(&line, &mut user);
34 writeln!(writer, "{}", response).unwrap();
35 println!("[{} @ {}] Sent: {}", user, client_addr, response);
36 }
37 println!("[{} @ {}] Client disconnected", user, client_addr);
38}
39
40fn process_message(msg: &str, user: &mut String) -> String {
41 let parts: Vec<&str> = msg.splitn(5, ' ').collect();
42 match parts[0] {
43 "GAME_LIST" => {
44 let registry = get_game_registry();
45 let games: Vec<String> = registry.list_games();
46 if games.is_empty() {
47 "ERROR No games available".to_string()
48 } else {
49 let game_info: Vec<String> = games
50 .iter()
51 .filter_map(|gt| {
52 registry
53 .get_game(gt)
54 .map(|g| format!("{}:{}", gt, g.get_name()))
55 })
56 .collect();
57 format!("GAME_LIST {}", game_info.join(","))
58 }
59 }
60 "GAME_INFO" => {
61 if parts.len() != 2 {
62 "ERROR Invalid GAME_INFO format. Usage: GAME_INFO <game_type>".to_string()
63 } else {
64 let registry = get_game_registry();
65 match registry.get_game(parts[1]) {
66 Some(game) => format!(
67 "GAME_INFO {} {} {}",
68 parts[1],
69 game.get_name(),
70 game.get_description()
71 ),
72 None => format!("ERROR Unknown game type: {}", parts[1]),
73 }
74 }
75 }
76 "PLAY_START" => {
77 if parts.len() != 3 {
78 "ERROR Invalid PLAY_START format. Usage: PLAY_START <game_type> <bet_amount>"
79 .to_string()
80 } else {
81 handle_play_start(user, parts[1], parts[2])
82 }
83 }
84 "REGISTER" => {
85 if parts.len() != 3 {
86 "ERROR Invalid REGISTER format".to_string()
87 } else {
88 let username = parts[1];
89 let hashed_password = parts[2];
90 let db = DB_CONN.lock();
91 match database::add_user(&db, username, hashed_password) {
92 Ok(_) => format!("REGISTERED {}", username),
93 Err(_) => "ERROR User already exists".to_string(),
94 }
95 }
96 }
97 "AUTH" => {
98 if parts.len() != 3 {
99 "ERROR Invalid AUTH format".to_string()
100 } else {
101 let username = parts[1];
102 let sent_hash = parts[2];
103 let db = DB_CONN.lock();
104 if let Some(stored_hash) = database::get_hashed_password(&db, username) {
105 if sent_hash == stored_hash {
106 *user = username.to_string();
107 format!("OK {}", username)
108 } else {
109 "ERROR Invalid credentials".to_string()
110 }
111 } else {
112 "ERROR User not found".to_string()
113 }
114 }
115 }
116 "PING" => "PONG".to_string(),
117 "INFO" => {
118 if *user == "guest" {
119 "ERROR Not logged in".to_string()
120 } else {
121 let db = DB_CONN.lock();
122 if let (Some(id), Some(balance)) = (
123 database::get_account_id(&db, user),
124 database::get_balance(&db, user),
125 ) {
126 format!("INFO {} {} {}", user, id, balance)
127 } else {
128 "ERROR Error retrieving user info".to_string()
129 }
130 }
131 } 10 }
132 _ => msg.to_string(),
133 }
134}
135
136fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> String {
137 if user == "guest" {
138 return "ERROR Not logged in".to_string();
139 }
140
141 let bet_amount: i64 = match bet_amount_str.parse() {
142 Ok(amount) => amount,
143 Err(_) => return "ERROR Invalid bet amount".to_string(),
144 }; 11 };
145 if bet_amount <= 0 {
146 return "ERROR Invalid bet amount".to_string();
147 }
148 12
149 let registry = get_game_registry(); 13 let db_path = std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string());
150 let game = match registry.get_game(game_type) { 14 let mut core = match ServerCore::open(&db_path) {
151 Some(game) => game, 15 Ok(core) => core,
152 None => return format!("ERROR Unknown game type: {}", game_type), 16 Err(error) => {
153 }; 17 eprintln!("Failed to open database {db_path}: {error}");
154 18 return;
155 let db = DB_CONN.lock(); 19 }
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) {
164 return match e {
165 GameError::InsufficientBalance => "ERROR Insufficient balance".to_string(),
166 GameError::InvalidBet => "ERROR Invalid bet amount".to_string(),
167 _ => "ERROR Bet validation failed".to_string(),
168 };
169 }
170
171 // Prepare and spin.
172 let game_state = game.prepare_state(user, bet_amount as u64);
173
174 let new_state = match game.process_action(&game_state, "spin", &[]) {
175 Ok(state) => state,
176 Err(e) => return format!("ERROR Failed to spin: {:?}", e),
177 };
178
179 let result = match game.calculate_result(&new_state) {
180 Ok(result) => result,
181 Err(e) => return format!("ERROR Failed to calculate result: {:?}", e),
182 };
183
184 // Create session before touching balance.
185 let session_id = SESSIONS.create_session(user, game_type.to_string(), new_state.clone());
186
187 // All balance mutations in a single transaction.
188 let total_return = 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}");
197 return "ERROR Failed to update balance".to_string();
198 }
199
200 let new_balance = match database::get_balance(&db, user) {
201 Some(balance) => balance,
202 None => return "ERROR Could not retrieve new balance".to_string(),
203 }; 20 };
204 21
205 let reels = new_state.data.get("reels").cloned().unwrap_or_default(); 22 let mut state = ConnectionState::default();
206 let win_type = new_state.data.get("win_type").cloned().unwrap_or_default(); 23 println!("[TLS Client connected] {client_addr}");
207
208 format!(
209 "GAME_RESULT {session_id} {} {} {new_balance} {reels} {win_type}",
210 result.winnings, result.bet_amount,
211 )
212}
213
214pub fn handle_client_tls(mut stream: TlsStream) {
215 let client_addr = stream.peer_addr().unwrap().to_string();
216 let mut user = "guest".to_string();
217 println!("[{} @ {}] TLS Client connected", user, client_addr);
218 24
219 loop { 25 loop {
220 let mut line = String::new(); 26 let mut line = String::new();
221 match stream.read_line(&mut line) { 27 match stream.read_line(&mut line) {
222 Ok(0) => break, 28 Ok(0) => break,
223 Ok(_) => {} 29 Ok(_) => {}
224 Err(e) => { 30 Err(error) => {
225 eprintln!("Read error: {}", e); 31 eprintln!("Read error: {error}");
226 break; 32 break;
227 } 33 }
228 } 34 }
@@ -232,21 +38,17 @@ pub fn handle_client_tls(mut stream: TlsStream) {
232 continue; 38 continue;
233 } 39 }
234 40
235 println!("[{} @ {}] Received: {}", user, client_addr, line); 41 let user = state.user().unwrap_or("guest").to_string();
236 let response = process_message(line, &mut user); 42 println!("[{user} @ {client_addr}] Received: {line}");
237 if let Err(e) = stream.write_line(&response) { 43 let response = core.handle_message(&mut state, line);
238 eprintln!("Write error: {}", e); 44 if let Err(error) = stream.write_line(&response) {
45 eprintln!("Write error: {error}");
239 break; 46 break;
240 } 47 }
241 println!("[{} @ {}] Sent: {}", user, client_addr, response); 48 let user = state.user().unwrap_or("guest");
242 } 49 println!("[{user} @ {client_addr}] Sent: {response}");
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 } 50 }
250 51
251 println!("[{} @ {}] TLS Client disconnected", user, client_addr); 52 core.disconnect(&state);
53 println!("[TLS Client disconnected] {client_addr}");
252} 54}