use bus::Bus; use crate::{ app::{ authentication::Authentication, connection::Connection, user::User, App, session::Session, }, protocol, }; pub async fn thread_system(mut app:App, bus:Bus) { use futures::SinkExt; use protocol::*; use ring::rand::{SecureRandom, SystemRandom}; let rng = SystemRandom::new(); let argon_config = argon2::Config::default(); while let Some(response) = match bus.receive_wait() { Some(packet) => { let qr = packet.data; let mut user_id = None; let mut session_id = None; if let Some(conn) = app.connections.get(qr.id as usize) { session_id = conn.session; if let Some(auth_id) = conn.auth { if let Some(auth) = app.auths.get(&auth_id) { user_id = Some(auth.user); } } } match qr.data { QRPacketData::QConn(request) => { let id = app.connections.add(Connection { bus: request.bus_id, stream: request.stream, auth: None, session: None, }); println!("Connect: {}", id); bus.send( packet.from, QRPacket::new(id as u32, QRPacketData::RConn) ).ok(); Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QDisconn => { // Uninitialize connection if if let Some(conn) = app.connections.get_mut(qr.id as usize) { // Disassociate session if present if let Some(session_token) = conn.session { if let Some(session) = app.sessions.get_mut(&session_token) { if user_id == session.p_dawn.user { session.remove_connection(0, qr.id); } else if user_id == session.p_dusk.user { session.remove_connection(1, qr.id); } else { session.remove_connection(2, qr.id); } } } // Close socket let mut socket = conn.stream.write().await; socket.close().await.ok(); true } else { false } { app.connections.remove(qr.id as usize).ok(); println!("Disconnect: {}", qr.id); } Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QRegister(request) => { let mut response = PacketRegisterResponse::new(); response.status = STATUS_SERVER_ERROR; println!("Request: Register"); let mut is_valid = true; if request.code != crate::config::REGISTER_CODE.as_bytes() { response.status = STATUS_BAD_CODE; is_valid = false; } if is_valid && request.handle.len() == 0 { response.status = STATUS_BAD_HANDLE; is_valid = false; } if is_valid && request.secret.len() == 0 { response.status = STATUS_BAD_SECRET; is_valid = false; } if is_valid { match app.user_handle.get(request.handle.as_bytes()) { None => { let mut salt = [0u8; 16]; match rng.fill(&mut salt) { Ok(_) => { let salt_id = app.filesystem.salt_store(salt).unwrap(); app.salts.set(salt_id as isize, salt); if let Ok(secret) = argon2::hash_raw(&request.secret, &salt, &argon_config) { let user_id = app.filesystem.user_count().unwrap() as u32; // Register user pool id and handle let handle_id = app.filesystem.handle_store(&request.handle, user_id).unwrap(); app.user_handle.set(request.handle.as_bytes(), user_id); let user_data = User { id:user_id, handle_id, handle:request.handle.clone(), secret, na_key:salt_id, }; app.filesystem.user_store(&user_data).ok(); // Create user entry let user_pos = app.users.add(user_data); app.user_id.set(user_id as isize, user_pos); println!("Registered user '{}' @ {} with id {}", request.handle, user_pos, user_id); // Generate authentication token and secret response.status = STATUS_OK; rng.fill(&mut response.secret).ok(); loop { rng.fill(&mut response.token).ok(); if app.auths.get(&response.token).is_none() { app.auths.set(&response.token, Authentication { key:response.token, secret:response.secret, user:user_id, }); break; } } // Attach authentication to connection if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(response.token); } } } Err(_) => { println!("error: failed to generate salt.") } } } Some(_) => { response.status = STATUS_BAD_HANDLE; println!("notice: attempt to register existing handle: '{}'", request.handle); } } } Some(QRPacket::new(qr.id, QRPacketData::RRegister(response))) } QRPacketData::QAuth(request) => { let mut response = PacketAuthResponse::new(); response.status = STATUS_ERROR; println!("Request: Auth"); let mut is_valid = true; if is_valid && request.handle.len() == 0 { response.status = STATUS_BAD_HANDLE; is_valid = false; } if is_valid && request.secret.len() == 0 { response.status = STATUS_BAD_SECRET; is_valid = false; } if is_valid { // Get user data from handle match app.user_handle.get(request.handle.as_bytes()) { Some(uid) => { if let Some(tuid) = app.user_id.get(*uid as isize) { if let Some(user) = app.users.get(*tuid) { // Get user salt if let Some(salt) = app.salts.get(user.na_key as isize) { // Verify salted secret against user data if argon2::verify_raw(&request.secret.as_bytes(), salt, &user.secret, &argon_config).unwrap_or(false) { println!("Authenticated user '{}' id {}", request.handle, *uid); // Generate authentication token and secret response.status = STATUS_OK; rng.fill(&mut response.secret).ok(); loop { rng.fill(&mut response.token).ok(); if app.auths.get(&response.token).is_none() { app.auths.set(&response.token, Authentication { key:response.token, secret:response.secret, user:*uid, }); break; } } // Attach authentication to connection if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(response.token); } } else { println!("notice: password verification failed."); } } else { println!("error: user salt id '{}' not found.", user.na_key); } } else { println!("error: user with id '{}' not found.", uid); } } else { println!("error: user with id '{}' not found.", uid); } } None => { } } } Some(QRPacket::new(qr.id, QRPacketData::RAuth(response))) } QRPacketData::QAuthResume(request) => { let mut response = PacketAuthResumeResponse::new(); response.status = STATUS_ERROR; if let Some(auth) = app.auths.get(&request.token) { // Compare full secret length to reduce time-based attacks. let mut valid = true; for i in 0..16 { valid |= auth.secret[i] == request.secret[i]; } if valid { if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(request.token); response.status = STATUS_OK; } else { response.status = STATUS_SERVER_ERROR; } } } Some(QRPacket::new(qr.id, QRPacketData::RAuthResume(response))) } QRPacketData::QAuthRevoke => { if let Some(conn) = app.connections.get_mut(qr.id as usize) { match conn.auth { Some(auth) => { println!("Deauthenticated connection: {}", qr.id); app.auths.unset(&auth); } None => { } } conn.auth = None; } Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QSessionList(request) => { println!("Request: Session List"); let mut response = PacketSessionListResponse::new(); let mut count = 0; let mut next_id = app.session_time.begin(); while let Some(id) = next_id { let token = app.session_time.get(id).unwrap(); if let Some(session) = app.sessions.get(token) { // Requirements: // - GameState must be None or match state of session. // - Joinable must have either player slot empty. // - IsPlayer must have the current user in either player slot. // - IsLive must have both users connected to the session. let mut valid = match request.game_state { 1 => session.p_dawn.user.is_none() || session.p_dusk.user.is_none(), 2 => session.p_dawn.user.is_some() && session.p_dusk.user.is_some(), 3 => session.game.complete, _ => true, }; valid &= !request.is_player || session.p_dawn.user == user_id || session.p_dusk.user == user_id; valid &= !request.is_live || (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0); if valid { let is_player = user_id.is_some() && (session.p_dawn.user == user_id || session.p_dusk.user == user_id); let dawn_handle = if let Some(uid) = session.p_dawn.user { if let Some(user) = app.get_user_by_id(uid) { user.handle.clone() } else { String::new() } } else { String::new() }; let dusk_handle = if let Some(uid) = session.p_dusk.user { if let Some(user) = app.get_user_by_id(uid) { user.handle.clone() } else { String::new() } } else { String::new() }; response.records.push(PacketSessionListResponseRecord { token:session.token, handles:[ dawn_handle, dusk_handle, ], turn:session.game.turn, last_move:[0; 3], viewers:session.connections.len() as u32, player:is_player, }); count += 1; } } if count >= 60 { break; } next_id = app.session_time.next(id); } Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response))) } QRPacketData::QSessionCreate(_request) => { use crate::app::session::*; println!("Request: Session Create"); let mut response = PacketSessionCreateResponse::new(); response.status = STATUS_ERROR; if let Some(uid) = user_id { // Generate session token let mut token = SessionToken::default(); let mut secret = SessionSecret::default(); loop { rng.fill(&mut token).ok(); if app.sessions.get(&token).is_none() { break; } } rng.fill(&mut secret).ok(); let chain_id = app.session_time.add(token); let mut session = Session { id:0, token, secret, game:game::Game::new(), p_dawn:Player { user:Some(uid), connections:vec![qr.id], }, p_dusk:Player { user:None, connections:Vec::new(), }, connections:Vec::new(), time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64, chain_id, }; session.game.init(); if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.session = Some(session.token); } session.id = app.filesystem.session_store(&session).unwrap(); app.sessions.set(&token, session); app.session_time.set(chain_id, token); // Set player to Dawn. response.mode = 0; response.status = STATUS_OK; response.token = token; } Some(QRPacket::new(qr.id, QRPacketData::RSessionCreate(response))) } QRPacketData::QSessionJoin(request) => { println!("Request: Session Join"); let mut response = PacketSessionJoinResponse::new(); response.status = STATUS_ERROR; response.token = request.token; let mut send_gamestate = false; // Verify that session exists if let Some(session) = app.sessions.get_mut(&request.token) { // Join game as player if if request.join { // Verify client is authenticated if let Some(uid) = user_id { // User must not already be player if session.p_dawn.user != user_id && session.p_dusk.user != user_id { // Add user to session and randomize seats. if if session.p_dusk.user.is_none() { let mut r = [0u8; 1]; rng.fill(&mut r).ok(); if (r[0] & 1) == 0 { session.p_dusk = session.p_dawn.clone(); session.p_dawn.user = Some(uid); session.p_dawn.connections.clear(); response.mode = 0; } else { session.p_dusk.user = Some(uid); response.mode = 1; } true } else { // Session is not empty response.status = STATUS_ERROR; false } { println!("Add user to session."); app.filesystem.session_update(session.id, session).ok(); response.status = STATUS_OK; send_gamestate = true; true } else { false } } // Resume session for player connection else { println!("User resumes session."); response.status = STATUS_OK; response.mode = (session.p_dusk.user == user_id) as u8; true } } else { response.status = STATUS_NOAUTH; false } } // Join game as spectator. else { println!("User spectates session."); response.status = STATUS_OK; response.mode = 2; true } { // Associate session and connection on join if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.session = Some(session.token); } session.add_connection(response.mode, qr.id); } } if send_gamestate { if let Some(session) = app.sessions.get(&request.token) { // Send GameState update to all connections. let mut packets = Vec::new(); let game_state = generate_gamestate(&app, session); for (cid, player) in session.get_connections() { if cid != qr.id { let mut gs = game_state.clone(); gs.player = player; packets.push(QRPacket::new(cid, QRPacketData::RGameState(gs))); } } for packet in packets { app.send_response(packet).await; } } } Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response))) } // SessionRetire QRPacketData::QSessionRetire(request) => { println!("Request: Session Retire"); if let Some(_session) = app.sessions.get_mut(&request.token) { } Some(QRPacket::new(0, QRPacketData::None)) } // SessionLeave QRPacketData::QSessionLeave => { println!("Request: Session Leave"); // Verify that session exists. if let Some(session_token) = session_id { if let Some(session) = app.sessions.get_mut(&session_token) { if user_id.is_some() && user_id == session.p_dawn.user { session.remove_connection(0, qr.id); } else if user_id.is_some() && user_id == session.p_dusk.user { session.remove_connection(1, qr.id); } else { session.remove_connection(2, qr.id); } } } Some(QRPacket::new(0, QRPacketData::None)) } // GameState QRPacketData::QGameState(request) => { let mut response = PacketGameStateResponse::new(); println!("Request: Game State"); if let Some(session) = app.sessions.get(&request.token) { response = generate_gamestate(&app, &session); if user_id.is_some() { if user_id == session.p_dawn.user { response.player = 0; } else if user_id == session.p_dusk.user { response.player = 1; } } } else { response.status = STATUS_ERROR; } Some(QRPacket::new(qr.id, QRPacketData::RGameState(response))) } // GamePlay QRPacketData::QGamePlay(mut request) => { println!("Request: Game Play"); request.status = STATUS_ERROR; let mut packets = Vec::::new(); if let Some(sid) = session_id { if let Some(session) = app.sessions.get_mut(&sid) { if session.p_dawn.user.is_some() && session.p_dusk.user.is_some() { if (user_id == session.p_dawn.user && session.game.turn & 1 == 0) || (user_id == session.p_dusk.user && session.game.turn & 1 == 1) { // Check validation of play if request.turn == session.game.turn { // Update internal representation if session.game.process(&request.play).is_ok() { request.status = STATUS_OK; // Save play to game history. app.filesystem.session_history_push(session.id, request.play).ok(); // Forward play to all clients for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::QGamePlay(request.clone()) )); } } } } } } } if request.status != STATUS_ERROR { for packet in packets { app.send_response(packet).await; } // Updates will have already been sent, so nothing is needed here. Some(QRPacket::new(qr.id, QRPacketData::None)) } else { // Return error status. Some(QRPacket::new(qr.id, QRPacketData::QGamePlay(request))) } } _ => { Some(QRPacket::new(0, QRPacketData::None)) } } } None => None, } { app.send_response(response).await; } } fn generate_gamestate(app:&App, session:&Session) -> protocol::PacketGameStateResponse { use protocol::{PacketGameStateResponse, PacketGameStateResponsePiece}; let mut response = PacketGameStateResponse::new(); response.player = 2; response.turn = session.game.turn; if session.game.history.len() > 0 { response.play = session.game.history[session.game.history.len() - 1]; } // Get Dawn handle if let Some(id) = session.p_dawn.user { if let Some(user) = app.get_user_by_id(id) { response.dawn_handle = user.handle.clone(); } } // Get Dusk handle if let Some(id) = session.p_dusk.user { if let Some(user) = app.get_user_by_id(id) { response.dusk_handle = user.handle.clone(); } } // Get pool sizes response.dawn_pool = session.game.pool[0]; response.dusk_pool = session.game.pool[1]; // Get list of pieces for i in 0..session.game.board.pieces.len() { response.pieces[i] = if let Some(piece) = &session.game.board.pieces[i] { PacketGameStateResponsePiece { valid:true, piece:piece.class, promoted:piece.promoted, player:piece.player, tile:piece.tile, } } else { PacketGameStateResponsePiece::new() }; } response }