use bus::Bus; use game::history::Play; use crate::{ config, app::{ authentication::Authentication, connection::Connection, user::{User, UserStatus}, 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(); let mut send_user_status = Vec::::new(); while let Some(packet) = bus.receive_wait() { 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 match qr.data { QRPacketData::QConn(request) => { let id = app.connections.add(Connection { bus: request.bus_id, stream: request.stream, auth: None, session: None, prev:0, next:0, }); if let Some(conn) = app.connections.get_mut(id) { conn.prev = id as u32; conn.next = id as u32; } println!("Connect: {}", id); bus.send( packet.from, QRPacket::new(id as u32, QRPacketData::RConn) ).ok(); Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QDisconn => { println!("Disconnect: {}", qr.id); // Uninitialize connection if if let Some(conn) = app.connections.get(qr.id as usize).cloned() { // Disassociate session if present if let Some(session_token) = conn.session { if let Some(session) = app.sessions.get_mut(&session_token) { if user_id == Some(session.p_dawn.user) { session.remove_connection(0, qr.id); } else if user_id == Some(session.p_dusk.user) { session.remove_connection(1, qr.id); } else { session.remove_connection(2, qr.id); } } } // Remove connection from chain. if let Some(auth_id) = conn.auth { if let Some(auth) = app.auths.get(&auth_id).cloned() { // Update user connection reference. if let Some(user) = app.get_user_by_id_mut(auth.user) { if Some(qr.id) == user.connection { // Set connection to next if exists. if conn.next != qr.id { user.connection = Some(conn.next); } else { user.connection = None; } } } // Link prev and next connections. if conn.next != qr.id { if let Some(prev_conn) = app.connections.get_mut(conn.prev as usize) { prev_conn.next = conn.next; } if let Some(next_conn) = app.connections.get_mut(conn.next as usize) { next_conn.prev = conn.prev; } } } } // Close socket let mut socket = conn.stream.write().await; socket.close().await.ok(); true } else { false } { app.connections.remove(qr.id as usize).ok(); } Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QHello => { let response = PacketHelloResponse { version:config::VERSION.to_string(), }; Some(QRPacket::new(qr.id, QRPacketData::RHello(response))) } 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 { let handle = request.handle.to_lowercase(); let display_name = request.handle.clone(); match app.user_handle.get(handle.as_bytes()) { None => { let mut salt = [0u8; 16]; match rng.fill(&mut salt) { Ok(_) => { let salt_id = app.filesystem.salt_create(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 app.filesystem.handle_create(&handle, user_id).ok(); app.user_handle.set(handle.as_bytes(), user_id); let user_data = User { id:user_id, flags:0, handle:display_name, secret, na_key:salt_id, connection:Some(qr.id), status:UserStatus::new(), challenges:Vec::new(), }; app.filesystem.user_create(&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.to_lowercase().as_bytes()).cloned() { Some(uid) => { if let Some(tuid) = app.user_id.get(uid as isize).cloned() { if let Some(mut user) = app.users.get(tuid).cloned() { // Get user salt if let Some(salt) = app.salts.get(user.na_key as isize).cloned() { // [TEMPORARY] WORKAROUND FOR PASSWORD RESET if user.secret.is_empty() { println!("Password reset: {}", user.handle); if let Ok(secret) = argon2::hash_raw(&request.secret.as_bytes(), &salt, &argon_config) { user.secret = secret; if if let Some(app_user) = app.users.get_mut(tuid) { app_user.secret = user.secret.clone(); true } else { false } { app.filesystem.user_update(uid, &user).ok(); } } } else { // 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 {}", user.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; } } // Mark send status. send_user_status.push(uid); // Attach authentication to connection. if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(response.token); if let Some(cid) = user.connection { conn.prev = cid; } } // Add connection to chain. if let Some(user_cid) = user.connection { if let Some(existing) = app.connections.get(user_cid as usize).cloned() { if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.next = existing.next; } } } else { if let Some(user) = app.users.get_mut(tuid) { user.connection = Some(qr.id); } } } 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) => { println!("Request: Auth Resume"); let mut response = PacketAuthResumeResponse::new(); response.status = STATUS_ERROR; if let Some(auth) = app.auths.get(&request.token).cloned() { // 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 if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(request.token); response.status = STATUS_OK; true } else { response.status = STATUS_SERVER_ERROR; false } { // Add connection to chain. if let Some(user) = app.get_user_by_id(auth.user).cloned() { if let Some(user_cid) = user.connection { if let Some(existing) = app.connections.get(user_cid as usize).cloned() { if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.next = existing.next; } } } else { if let Some(user) = app.get_user_by_id_mut(auth.user) { user.connection = Some(qr.id); } } // Mark send user status. send_user_status.push(auth.user); } } } } Some(QRPacket::new(qr.id, QRPacketData::RAuthResume(response))) } QRPacketData::QAuthRevoke => { println!("Request: Auth Revoke"); // Remove connection from chain. if let Some(conn) = app.connections.get(qr.id as usize).cloned() { if let Some(auth_id) = conn.auth { if let Some(auth) = app.auths.get(&auth_id).cloned() { // Update user connection reference. if let Some(user) = app.get_user_by_id_mut(auth.user) { if Some(qr.id) == user.connection { // Set connection to next if exists. if conn.next != qr.id { user.connection = Some(conn.next); } else { user.connection = None; } } } // Link prev and next connections. if conn.next != qr.id { if let Some(prev_conn) = app.connections.get_mut(conn.prev as usize) { prev_conn.next = conn.next; } if let Some(next_conn) = app.connections.get_mut(conn.next as usize) { next_conn.prev = conn.prev; } } } } } // Remove authentication from connection. 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) { let mut valid = match request.game_state { 1 => !session.game.is_complete(), 2 => !session.game.is_complete(), 3 => session.game.is_complete(), _ => true, }; valid &= !request.is_player || Some(session.p_dawn.user) == user_id || Some(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 player :u8 = if user_id.is_some() { if Some(session.p_dawn.user) == user_id { 1 } else if Some(session.p_dusk.user) == user_id { 2 } else { 0 } } else { 0 }; let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1; let is_complete = (session.game.is_complete() as u8) * (((session.game.turn & 1) == 0) as u8 + 1); let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) { user.handle.clone() } else { String::new() }; let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) { user.handle.clone() } 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_turn, is_complete, }); count += 1; } } if count >= 60 { break; } next_id = app.session_time.next(id); } Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response))) } QRPacketData::QSessionView(request) => { println!("Request: Session Join"); let mut packets = Vec::::new(); let mut response = PacketSessionViewResponse::new(); response.status = STATUS_ERROR; response.token = request.token; // Verify that session exists if let Some(session) = app.sessions.get_mut(&request.token) { response.is_complete = session.game.is_complete(); if user_id == Some(session.p_dawn.user) { response.player = 1; } else if user_id == Some(session.p_dusk.user) { response.player = 2; } // Join game as player if if request.join { // Verify client is authenticated if user_id.is_some() { // Resume session if user is player if Some(session.p_dawn.user) == user_id || Some(session.p_dusk.user) == user_id { println!("User resumes session."); response.status = STATUS_OK; for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage { data: GameMessageData::Online(1 + (Some(session.p_dusk.user) == user_id) as u8, 1), }), )); } true } else { false } } else { response.status = STATUS_NOAUTH; false } } // Join game as spectator. else { println!("User spectates session."); response.status = STATUS_OK; // Add user online packets. for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage { data: GameMessageData::Online(0, 1 + session.spectators() as u32), }), )); } true } { // Associate session and connection on join if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.session = Some(session.token); } let mode = if user_id == Some(session.p_dawn.user) { 0 } else if user_id == Some(session.p_dusk.user) { 1 } else { 2 }; session.add_connection(mode, qr.id); } } // Send notification packets for packet in packets { app.send_response(packet).await; } Some(QRPacket::new(qr.id, QRPacketData::RSessionView(response))) } // SessionResign QRPacketData::QSessionResign(request) => { use game::history::Play; println!("Request: Session Resign"); let mut packets = Vec::::new(); let play = Play { source: 0xF, from: 0, to: 0, }; if let Some(session) = app.sessions.get_mut(&request.token) { if !session.game.is_complete() { if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { session.game.process(&play).ok(); app.filesystem.session_history_push(session.id, play).ok(); send_user_status.push(session.p_dawn.user); send_user_status.push(session.p_dusk.user); for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage { data: GameMessageData::Resign, }), )); } } } } for packet in packets { app.send_response(packet).await; } Some(QRPacket::new(0, QRPacketData::None)) } // SessionLeave QRPacketData::QSessionLeave => { println!("Request: Session Leave"); let mut packets = Vec::::new(); // Verify that session exists. if let Some(session_token) = session_id { if let Some(session) = app.sessions.get_mut(&session_token) { if user_id == Some(session.p_dawn.user) { session.remove_connection(0, qr.id); if session.p_dawn.connections.len() == 0 { for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage { data: GameMessageData::Online(1, 0), }), )); } } } else if user_id == Some(session.p_dusk.user) { session.remove_connection(1, qr.id); if session.p_dusk.connections.len() == 0 { for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage { data: GameMessageData::Online(2, 0), }), )); } } } else { session.remove_connection(2, qr.id); // Add user online packets. for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage { data: GameMessageData::Online(0, session.spectators() as u32), }), )); } } } } for packet in packets { app.send_response(packet).await; } 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.status = STATUS_OK; response = generate_game_state(&app, &session); if user_id == Some(session.p_dawn.user) { response.player = 0; } else if user_id == Some(session.p_dusk.user) { response.player = 1; } } else { response.status = STATUS_ERROR; } Some(QRPacket::new(qr.id, QRPacketData::RGameState(response))) } // GameMessage QRPacketData::GameMessage(request) => { println!("Request: Game Message"); let mut packets = Vec::::new(); if let Some(sid) = session_id { if let Some(session) = app.sessions.get_mut(&sid) { match request.data { GameMessageData::PlayMove(turn, from, to) | GameMessageData::PlayDrop(turn, from, to) | GameMessageData::PlayAlt(turn, from, to) => { println!("HERE"); if !session.game.is_complete() { if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { if turn == session.game.turn { let play = Play { source: match request.data { GameMessageData::PlayMove(..) => 0, GameMessageData::PlayDrop(..) => 1, GameMessageData::PlayAlt(..) => 2, _ => 0, }, from, to, }; println!("play {} {} {}", play.source, play.from, play.to); if session.game.process(&play).is_ok() { // Commit play to history app.filesystem.session_history_push(session.id, play).ok(); // Forward messsage to all clients for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(request.clone()) )); } // Send status to players. send_user_status.push(session.p_dawn.user); send_user_status.push(session.p_dusk.user); } } } } } GameMessageData::Undo(turn, _) => { if !session.game.is_complete() { if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { if turn == session.game.turn { // Request or commit undo } } } } GameMessageData::Resign => { if !session.game.is_complete() { if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { // Forward messsage to all clients for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(request.clone()) )); } } } } GameMessageData::Reaction(_) => { // Forward messsage to all clients for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(request.clone()) )); } } _ => { } } } } match request.data { GameMessageData::Error => { Some(QRPacket::new(qr.id, QRPacketData::GameMessage(request))) } _ => { for packet in packets { app.send_response(packet).await; } // Updates already sent; nothing to do here. Some(QRPacket::new(qr.id, QRPacketData::None)) } } } // Challenge QRPacketData::QChallenge(request) => { println!("Request: Challenge"); if let Some(user_id) = user_id { if let Some(chal_id) = app.user_handle.get(request.handle.to_lowercase().as_bytes()).cloned() { if let Some(chal_user) = app.get_user_by_id_mut(chal_id) { let mut find = false; for ch in &chal_user.challenges { if *ch == user_id { find = true; } } if !find { chal_user.challenges.push(user_id); send_user_status.push(chal_id); } else { println!("notice: duplicate challenge."); } } } } None } // ChallengeAnswer QRPacketData::QChallengeAnswer(request) => { println!("Request: Challenge Answer"); use crate::app::session::{SessionToken, SessionSecret, Player}; let mut response = PacketChallengeAnswerResponse::new(); response.status = STATUS_ERROR; if let Some(user_id) = user_id { if let Some(chal_id) = app.user_handle.get(request.handle.to_lowercase().as_bytes()).copied() { // Check if challenge exists. if let Some(user) = app.get_user_by_id_mut(user_id) { match user.challenges.binary_search(&chal_id) { Ok(pos) => { response.status = if request.answer { STATUS_OK } else { STATUS_REJECT }; user.challenges.remove(pos); } Err(_) => { } } } // Create session if response was positive. if response.status == STATUS_OK { // 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(); // Add session to latest list. let chain_id = app.session_time.add(token); // Choose player seats. let time = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis() as u64; println!("Time {}", time); // Build session. let mut session = Session { id:0, token, secret, game:game::Game::new(), p_dawn:Player { user:if (time & 1) == 0 { user_id } else { chal_id }, connections:Vec::new(), }, p_dusk:Player { user:if (time & 1) == 0 { chal_id } else { user_id }, connections:Vec::new(), }, connections:Vec::new(), time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64, chain_id, undo:None, }; 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_create(&session).unwrap(); app.sessions.set(&token, session); app.session_time.set(chain_id, token); response.token = token; send_user_status.push(chal_id); } send_user_status.push(user_id); } } Some(QRPacket::new(qr.id, QRPacketData::RChallengeAnswer(response))) } // ChallengeList QRPacketData::QChallengeList => { println!("Request: Challenge List"); let mut response = PacketChallengeListResponse::new(); response.status = STATUS_NOAUTH; if let Some(user_id) = user_id { if let Some(user) = app.get_user_by_id(user_id) { response.status = STATUS_OK; for ch_id in &user.challenges { if let Some(target) = app.get_user_by_id(*ch_id) { response.challenges.push(PacketChallengeListData { handle:target.handle.clone(), }); } } } } Some(QRPacket::new(qr.id, QRPacketData::RChallengeList(response))) } // UserList QRPacketData::QUserList => { println!("Request: User List"); let mut response = PacketUserListResponse::new(); response.status = STATUS_NOAUTH; if let Some(user_id) = user_id { for (_, uid) in app.user_id.pairs() { let uid = *uid as u32; if uid != user_id { if let Some(user) = app.get_user_by_id(uid) { response.records.push(PacketUserListData { handle:user.handle.clone(), is_online:user.connection.is_some(), }); } } } } response.records.sort_by(|a, b| { b.is_online.cmp(&a.is_online) .then(a.handle.to_lowercase().cmp(&b.handle.to_lowercase())) }); Some(QRPacket::new(qr.id, QRPacketData::RUserList(response))) } _ => { Some(QRPacket::new(0, QRPacketData::None)) } } { Some(response) => { app.send_response(response).await; } None => { } } for user_id in &send_user_status { app.send_status(*user_id).await; } send_user_status.clear(); } } fn generate_game_state(app:&App, session:&Session) -> protocol::PacketGameStateResponse { use protocol::PacketGameStateResponse; let mut response = PacketGameStateResponse::new(); response.token = session.token; response.player = 2; // Get Dawn handle if let Some(user) = app.get_user_by_id(session.p_dawn.user) { response.dawn_handle = user.handle.clone(); } // Get Dusk handle if let Some(user) = app.get_user_by_id(session.p_dusk.user) { response.dusk_handle = user.handle.clone(); } response.dawn_online = session.p_dawn.connections.len() > 0; response.dusk_online = session.p_dusk.connections.len() > 0; response.spectators = session.connections.len() as u32; // Get history response.history = session.game.history.clone(); response }