From 5d6b5049c2a34a3f8abe725d4a871ba84a0dd714 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 24 Aug 2026 14:48:12 +0200 Subject: TLS transport and games framework on client and server --- src/bin/server/args.rs | 11 ++- src/bin/server/database.rs | 26 ++++-- src/bin/server/games/mod.rs | 4 + src/bin/server/games/registry.rs | 55 ++++++++++++ src/bin/server/games/session.rs | 77 +++++++++++++++++ src/bin/server/games/slots.rs | 179 +++++++++++++++++++++++++++++++++++++++ src/bin/server/games/traits.rs | 67 +++++++++++++++ src/bin/server/handler.rs | 177 ++++++++++++++++++++++++++++++++++++-- src/bin/server/main.rs | 34 +++++++- src/bin/server/tls.rs | 96 +++++++++++++++++++++ 10 files changed, 709 insertions(+), 17 deletions(-) create mode 100644 src/bin/server/games/mod.rs create mode 100644 src/bin/server/games/registry.rs create mode 100644 src/bin/server/games/session.rs create mode 100644 src/bin/server/games/slots.rs create mode 100644 src/bin/server/games/traits.rs create mode 100644 src/bin/server/tls.rs (limited to 'src/bin/server') diff --git a/src/bin/server/args.rs b/src/bin/server/args.rs index 31c0acc..fc844ea 100644 --- a/src/bin/server/args.rs +++ b/src/bin/server/args.rs @@ -1,12 +1,14 @@ pub struct Args { pub ip: String, pub port: u16, + pub unsafe_mode: bool, } pub fn parse_args() -> Args { let args: Vec = std::env::args().collect(); let mut ip = "127.0.0.1".to_string(); let mut port = 11525; + let mut unsafe_mode = false; let mut i = 1; while i < args.len() { if args[i] == "-p" { @@ -32,10 +34,17 @@ pub fn parse_args() -> Args { eprintln!("-i requires an IP address"); std::process::exit(1); } + } else if args[i] == "--unsafe" { + unsafe_mode = true; + i += 1; } else { eprintln!("Unknown argument: {}", args[i]); std::process::exit(1); } } - Args { ip, port } + Args { + ip, + port, + unsafe_mode, + } } diff --git a/src/bin/server/database.rs b/src/bin/server/database.rs index 0fac387..1653071 100644 --- a/src/bin/server/database.rs +++ b/src/bin/server/database.rs @@ -11,14 +11,22 @@ pub fn init_db() -> Connection { balance INTEGER DEFAULT 1000 )", [], - ).unwrap(); + ) + .unwrap(); // Add column if not exists (for migration) let _ = conn.execute("ALTER TABLE users ADD COLUMN account_id INTEGER UNIQUE", []); - let _ = conn.execute("ALTER TABLE users ADD COLUMN balance INTEGER DEFAULT 1000", []); + let _ = conn.execute( + "ALTER TABLE users ADD COLUMN balance INTEGER DEFAULT 1000", + [], + ); conn } -pub fn add_user(conn: &Connection, username: &str, hashed_password: &str) -> Result<(), Box> { +pub fn add_user( + conn: &Connection, + username: &str, + hashed_password: &str, +) -> Result<(), Box> { loop { let account_id: u32 = rand::random::(); match conn.execute( @@ -33,7 +41,9 @@ pub fn add_user(conn: &Connection, username: &str, hashed_password: &str) -> Res } pub fn get_hashed_password(conn: &Connection, username: &str) -> Option { - let mut stmt = conn.prepare("SELECT hashed_password FROM users WHERE username = ?1").unwrap(); + let mut stmt = conn + .prepare("SELECT hashed_password FROM users WHERE username = ?1") + .unwrap(); let mut rows = stmt.query([username]).unwrap(); if let Ok(Some(row)) = rows.next() { row.get(0).ok() @@ -43,7 +53,9 @@ pub fn get_hashed_password(conn: &Connection, username: &str) -> Option } pub fn get_account_id(conn: &Connection, username: &str) -> Option { - let mut stmt = conn.prepare("SELECT account_id FROM users WHERE username = ?1").unwrap(); + let mut stmt = conn + .prepare("SELECT account_id FROM users WHERE username = ?1") + .unwrap(); let mut rows = stmt.query([username]).unwrap(); if let Ok(Some(row)) = rows.next() { Some(row.get::<_, u32>(0).ok()?) @@ -53,7 +65,9 @@ pub fn get_account_id(conn: &Connection, username: &str) -> Option { } pub fn get_balance(conn: &Connection, username: &str) -> Option { - let mut stmt = conn.prepare("SELECT balance FROM users WHERE username = ?1").unwrap(); + let mut stmt = conn + .prepare("SELECT balance FROM users WHERE username = ?1") + .unwrap(); let mut rows = stmt.query([username]).unwrap(); if let Ok(Some(row)) = rows.next() { Some(row.get::<_, i64>(0).ok()?) diff --git a/src/bin/server/games/mod.rs b/src/bin/server/games/mod.rs new file mode 100644 index 0000000..4c62760 --- /dev/null +++ b/src/bin/server/games/mod.rs @@ -0,0 +1,4 @@ +pub mod registry; +pub mod session; +pub mod slots; +pub mod traits; diff --git a/src/bin/server/games/registry.rs b/src/bin/server/games/registry.rs new file mode 100644 index 0000000..e77a138 --- /dev/null +++ b/src/bin/server/games/registry.rs @@ -0,0 +1,55 @@ +#![allow(dead_code)] + +use crate::games::traits::Game; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +pub type GameRef = Arc; + +pub struct GameRegistry { + games: Arc>>, +} + +impl GameRegistry { + pub fn new() -> Self { + Self { + games: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub fn register_game(&self, game: G) { + let game_type = game.game_type().to_string(); + let mut games = self.games.lock().unwrap(); + games.insert(game_type, Arc::new(game)); + } + + pub fn get_game(&self, game_type: &str) -> Option { + let games = self.games.lock().unwrap(); + games.get(game_type).cloned() + } + + pub fn list_games(&self) -> Vec { + let games = self.games.lock().unwrap(); + games.keys().cloned().collect() + } + + pub fn game_exists(&self, game_type: &str) -> bool { + let games = self.games.lock().unwrap(); + games.contains_key(game_type) + } +} + +// Global registry instance +use crate::games::slots::SlotsGame; +use once_cell::sync::Lazy; + +static GAME_REGISTRY: Lazy = Lazy::new(|| { + let registry = GameRegistry::new(); + // Register games here + registry.register_game(SlotsGame::new()); + registry +}); + +pub fn get_game_registry() -> &'static GameRegistry { + &GAME_REGISTRY +} diff --git a/src/bin/server/games/session.rs b/src/bin/server/games/session.rs new file mode 100644 index 0000000..595a745 --- /dev/null +++ b/src/bin/server/games/session.rs @@ -0,0 +1,77 @@ +#![allow(dead_code)] + +use crate::games::traits::{GameError, GameState}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use uuid::Uuid; + +#[derive(Debug, Clone)] +pub struct GameSession { + pub session_id: String, + pub username: String, + pub game_type: String, + pub bet_amount: u64, + pub state: GameState, +} + +pub struct SessionManager { + sessions: Arc>>, +} + +impl SessionManager { + pub fn new() -> Self { + Self { + sessions: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub fn create_session(&self, username: &str, game_type: String, state: GameState) -> String { + let session_id = Uuid::new_v4().to_string(); + let session = GameSession { + session_id: session_id.clone(), + username: username.to_string(), + game_type, + bet_amount: state.bet_amount, + state, + }; + + let mut sessions = self.sessions.lock().unwrap(); + sessions.insert(session_id.clone(), session); + session_id + } + + pub fn get_session(&self, session_id: &str) -> Result { + let sessions = self.sessions.lock().unwrap(); + sessions + .get(session_id) + .cloned() + .ok_or(GameError::SessionNotFound) + } + + pub fn update_session(&self, session_id: &str, new_state: GameState) -> Result<(), GameError> { + let mut sessions = self.sessions.lock().unwrap(); + if let Some(session) = sessions.get_mut(session_id) { + session.state = new_state; + Ok(()) + } else { + Err(GameError::SessionNotFound) + } + } + + pub fn remove_session(&self, session_id: &str) -> Result<(), GameError> { + let mut sessions = self.sessions.lock().unwrap(); + sessions + .remove(session_id) + .ok_or(GameError::SessionNotFound) + .map(|_| ()) + } + + pub fn get_user_sessions(&self, username: &str) -> Vec { + let sessions = self.sessions.lock().unwrap(); + sessions + .values() + .filter(|s| s.username == username) + .cloned() + .collect() + } +} diff --git a/src/bin/server/games/slots.rs b/src/bin/server/games/slots.rs new file mode 100644 index 0000000..d84e21f --- /dev/null +++ b/src/bin/server/games/slots.rs @@ -0,0 +1,179 @@ +use crate::games::traits::{Game, GameError, GamePhase, GameResult, GameState}; +use rand::Rng; +use std::collections::HashMap; + +pub struct SlotsGame { + symbols: Vec, + payouts: HashMap, +} + +impl SlotsGame { + pub fn new() -> Self { + let symbols = vec!['C', 'L', 'O', 'G', 'D', 'S']; + let mut payouts = HashMap::new(); + + payouts.insert("CCC".to_string(), 5); + payouts.insert("LLL".to_string(), 10); + payouts.insert("OOO".to_string(), 15); + payouts.insert("GGG".to_string(), 20); + payouts.insert("DDD".to_string(), 50); + payouts.insert("SSS".to_string(), 100); + + payouts.insert("CC".to_string(), 2); + payouts.insert("LL".to_string(), 3); + payouts.insert("OO".to_string(), 4); + payouts.insert("GG".to_string(), 5); + payouts.insert("DD".to_string(), 10); + payouts.insert("SS".to_string(), 20); + + Self { symbols, payouts } + } + + fn spin_reels(&self) -> Vec { + let mut rng = rand::thread_rng(); + (0..3) + .map(|_| self.symbols[rng.gen_range(0..self.symbols.len())]) + .collect() + } + + fn calculate_payout(&self, reels: &[char], bet: u64) -> u64 { + let reel_string: String = reels.iter().collect(); + + if let Some(&multiplier) = self.payouts.get(&reel_string) { + return bet * multiplier; + } + + for i in 0..2 { + if reels[i] == reels[i + 1] { + let two_of_kind = format!("{}{}", reels[i], reels[i]); + if let Some(&multiplier) = self.payouts.get(&two_of_kind) { + return bet * multiplier; + } + } + } + + 0 + } +} + +impl Game for SlotsGame { + fn game_type(&self) -> &'static str { + "slots" + } + + fn get_name(&self) -> &'static str { + "Slots" + } + + fn get_description(&self) -> &'static str { + "Spin the reels and match symbols to win!" + } + + fn prepare_state(&self, username: &str, bet_amount: u64) -> GameState { + let mut data = HashMap::new(); + data.insert("username".to_string(), username.to_string()); + data.insert("status".to_string(), "ready".to_string()); + + GameState { + session_id: String::new(), + game_type: "slots".to_string(), + bet_amount, + phase: GamePhase::WaitingToStart, + data, + } + } + + fn process_action( + &self, + state: &GameState, + action: &str, + _params: &[&str], + ) -> Result { + if state.phase == GamePhase::Finished { + return Err(GameError::GameOver); + } + + match action { + "spin" => { + if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Playing { + return Err(GameError::InvalidAction( + "Cannot spin in current phase".to_string(), + )); + } + + let reels = self.spin_reels(); + let reel_string: String = reels.iter().collect(); + + let mut new_data = state.data.clone(); + new_data.insert("reels".to_string(), reel_string.clone()); + new_data.insert("status".to_string(), "spun".to_string()); + + let winnings = self.calculate_payout(&reels, state.bet_amount); + new_data.insert("winnings".to_string(), winnings.to_string()); + + let is_jackpot = reels[0] == reels[1] && reels[1] == reels[2]; + let is_two = reels[0] == reels[1] || reels[1] == reels[2]; + new_data.insert( + "win_type".to_string(), + if is_jackpot { + "jackpot".to_string() + } else if is_two { + "two".to_string() + } else { + "none".to_string() + }, + ); + + Ok(GameState { + session_id: state.session_id.clone(), + game_type: state.game_type.clone(), + bet_amount: state.bet_amount, + phase: GamePhase::Finished, + data: new_data, + }) + } + _ => Err(GameError::InvalidAction(format!( + "Unknown action: {}", + action + ))), + } + } + + fn calculate_result(&self, state: &GameState) -> Result { + if state.phase != GamePhase::Finished { + return Err(GameError::InternalError("Game not finished".to_string())); + } + + let reel_string = state + .data + .get("reels") + .ok_or(GameError::InternalError("No reels found".to_string()))?; + + let reels: Vec = reel_string.chars().collect(); + let winnings = self.calculate_payout(&reels, state.bet_amount); + + let mut result_data = HashMap::new(); + result_data.insert("reels".to_string(), reel_string.clone()); + result_data.insert( + "payout_multiplier".to_string(), + if winnings > 0 && state.bet_amount > 0 { + (winnings / state.bet_amount).to_string() + } else { + "0".to_string() + }, + ); + result_data.insert( + "win_type".to_string(), + state.data.get("win_type").cloned().unwrap_or_default(), + ); + + Ok(GameResult { + session_id: state.session_id.clone(), + game_type: state.game_type.clone(), + bet_amount: state.bet_amount, + winnings, + final_balance: 0, + result_data, + }) + } +} diff --git a/src/bin/server/games/traits.rs b/src/bin/server/games/traits.rs new file mode 100644 index 0000000..d56b334 --- /dev/null +++ b/src/bin/server/games/traits.rs @@ -0,0 +1,67 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum GameError { + InvalidAction(String), + InsufficientBalance, + InvalidBet, + GameOver, + SessionNotFound, + InternalError(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum GamePhase { + WaitingToStart, + Playing, + Finished, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GameState { + pub session_id: String, + pub game_type: String, + pub bet_amount: u64, + pub phase: GamePhase, + pub data: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GameResult { + pub session_id: String, + pub game_type: String, + pub bet_amount: u64, + pub winnings: u64, + pub final_balance: u64, + pub result_data: HashMap, +} + +pub trait Game: Send + Sync { + fn game_type(&self) -> &'static str; + + fn get_name(&self) -> &'static str; + + fn get_description(&self) -> &'static str; + + fn validate_bet(&self, bet_amount: u64, user_balance: u64) -> Result<(), GameError> { + if bet_amount == 0 { + return Err(GameError::InvalidBet); + } + if bet_amount > user_balance { + return Err(GameError::InsufficientBalance); + } + Ok(()) + } + + fn prepare_state(&self, username: &str, bet_amount: u64) -> GameState; + + fn process_action( + &self, + state: &GameState, + action: &str, + params: &[&str], + ) -> Result; + + fn calculate_result(&self, state: &GameState) -> Result; +} diff --git a/src/bin/server/handler.rs b/src/bin/server/handler.rs index 01fd9c9..867910e 100644 --- a/src/bin/server/handler.rs +++ b/src/bin/server/handler.rs @@ -1,12 +1,16 @@ +use crate::database; +use crate::games::registry::get_game_registry; +use crate::games::traits::GameError; +use crate::tls::TlsStream; +use once_cell::sync::Lazy; +use rusqlite::Connection; use std::io::{BufRead, BufReader, Write}; use std::net::TcpStream; use std::sync::Mutex; -use once_cell::sync::Lazy; -use rusqlite::Connection; -use crate::database; static DB_CONN: Lazy> = Lazy::new(|| Mutex::new(database::init_db())); +#[allow(dead_code)] pub fn handle_client(stream: TcpStream) { let client_addr = stream.peer_addr().unwrap().to_string(); let mut user = "guest".to_string(); @@ -25,8 +29,49 @@ pub fn handle_client(stream: TcpStream) { } fn process_message(msg: &str, user: &mut String) -> String { - let parts: Vec<&str> = msg.splitn(3, ' ').collect(); + 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() @@ -65,13 +110,133 @@ fn process_message(msg: &str, user: &mut String) -> String { "ERROR Not logged in".to_string() } else { let db = DB_CONN.lock().unwrap(); - if let (Some(id), Some(balance)) = (database::get_account_id(&db, user), database::get_balance(&db, user)) { + 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() } } } - _ => msg.to_string(), // Echo back for compatibility + _ => 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: u64 = match bet_amount_str.parse() { + Ok(amount) => amount, + Err(_) => return "ERROR Invalid bet amount".to_string(), + }; + + let db = DB_CONN.lock().unwrap(); + let current_balance = match database::get_balance(&db, user) { + Some(balance) => balance as u64, + None => return "ERROR Could not retrieve balance".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), + }; + + if let Err(e) = game.validate_bet(bet_amount, current_balance) { + return match e { + GameError::InsufficientBalance => "ERROR Insufficient balance".to_string(), + GameError::InvalidBet => "ERROR Invalid bet amount".to_string(), + _ => "ERROR Bet validation failed".to_string(), + }; + } + + let game_state = game.prepare_state(user, bet_amount); + + drop(db); + let db = DB_CONN.lock().unwrap(); + if let Err(_) = db.execute( + "UPDATE users SET balance = balance - ?1 WHERE username = ?2", + (bet_amount as i64, user), + ) { + return "ERROR Failed to deduct bet".to_string(); + } + + let new_state = match game.process_action(&game_state, "spin", &[]) { + Ok(state) => state, + Err(e) => { + let _ = db.execute( + "UPDATE users SET balance = balance + ?1 WHERE username = ?2", + (bet_amount as i64, user), + ); + return format!("ERROR Failed to spin: {:?}", e); + } + }; + + let result = match game.calculate_result(&new_state) { + Ok(result) => result, + Err(e) => { + let _ = db.execute( + "UPDATE users SET balance = balance + ?1 WHERE username = ?2", + (bet_amount as i64, user), + ); + return format!("ERROR Failed to calculate result: {:?}", e); + } + }; + + let total_return = result.bet_amount + result.winnings; + if let Err(_) = db.execute( + "UPDATE users SET balance = balance + ?1 WHERE username = ?2", + (total_return as i64, user), + ) { + 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 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 {} {} {} {} {} {}", + result.session_id, result.winnings, result.bet_amount, new_balance, reels, win_type + ) +} + +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); + + loop { + let mut line = String::new(); + match stream.read_line(&mut line) { + Ok(0) => break, + Ok(_) => {} + Err(e) => { + eprintln!("Read error: {}", e); + break; + } + } + + let line = line.trim(); + if line.is_empty() { + 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); + break; + } + println!("[{} @ {}] Sent: {}", user, client_addr, response); } + println!("[{} @ {}] TLS Client disconnected", user, client_addr); } diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index 7693069..4328559 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs @@ -1,8 +1,11 @@ mod args; -mod handler; mod database; +mod games; +mod handler; +mod tls; use std::net::TcpListener; +use std::sync::Arc; use std::thread; fn main() { @@ -14,11 +17,34 @@ fn main() { std::process::exit(1); } }; - println!("Server listening on {}", listener.local_addr().unwrap()); + + let tls = match tls::TlsServer::new("identity.p12", "key.pem", args.unsafe_mode) { + Ok(t) => Arc::new(t), + Err(e) => { + eprintln!("Failed to initialize TLS: {}", e); + std::process::exit(1); + } + }; + + println!( + "Server listening on {} (TLS enabled){}", + listener.local_addr().unwrap(), + if args.unsafe_mode { + " [UNSAFE MODE]" + } else { + "" + } + ); for stream in listener.incoming() { let stream = stream.unwrap(); - thread::spawn(move || { - handler::handle_client(stream); + let tls = tls.clone(); + thread::spawn(move || match tls.accept(stream) { + Ok(tls_stream) => { + handler::handle_client_tls(tls_stream); + } + Err(e) => { + eprintln!("TLS handshake failed: {}", e); + } }); } } diff --git a/src/bin/server/tls.rs b/src/bin/server/tls.rs new file mode 100644 index 0000000..acf3315 --- /dev/null +++ b/src/bin/server/tls.rs @@ -0,0 +1,96 @@ +use std::io::{Read, Write}; +use std::net::TcpStream; + +#[derive(Clone)] +pub struct TlsServer { + acceptor: native_tls::TlsAcceptor, +} + +impl TlsServer { + pub fn new( + cert_path: &str, + _key_path: &str, + unsafe_mode: bool, + ) -> Result> { + let cert_data = if unsafe_mode { + if std::path::Path::new(cert_path).exists() { + std::fs::read(cert_path)? + } else { + return Err(format!( + "Certificate file '{}' not found. In --unsafe mode, you must provide a valid PKCS12 certificate file.\n\ + You can generate one with:\n\ + openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes\n\ + openssl pkcs12 -export -out identity.p12 -inkey key.pem -in cert.pem", + cert_path + ).into()); + } + } else { + std::fs::read(cert_path)? + }; + + let identity = native_tls::Identity::from_pkcs12(&cert_data, "")?; + let acceptor = native_tls::TlsAcceptor::new(identity)?; + + Ok(Self { acceptor }) + } + + pub fn accept(&self, stream: TcpStream) -> Result> { + let stream = self.acceptor.accept(stream)?; + Ok(TlsStream::new(stream)) + } +} + +pub struct TlsStream { + stream: native_tls::TlsStream, +} + +impl TlsStream { + pub fn new(stream: native_tls::TlsStream) -> Self { + Self { stream } + } + + pub fn read_line(&mut self, s: &mut String) -> std::io::Result { + let mut buf = [0u8; 1024]; + loop { + match self.stream.read(&mut buf) { + Ok(0) => return Ok(0), + Ok(n) => { + let chunk = std::str::from_utf8(&buf[..n]).unwrap_or(""); + s.push_str(chunk); + if s.contains('\n') { + return Ok(n); + } + } + Err(e) => return Err(e), + } + } + } + + pub fn write_line(&mut self, msg: &str) -> std::io::Result<()> { + use std::io::Write; + self.stream.write_all(msg.as_bytes())?; + self.stream.write_all(b"\n")?; + self.stream.flush()?; + Ok(()) + } + + pub fn peer_addr(&self) -> std::io::Result { + self.stream.get_ref().peer_addr() + } +} + +impl Read for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.stream.read(buf) + } +} + +impl Write for TlsStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.stream.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.stream.flush() + } +} -- cgit v1.2.3