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; } app.log.log(&format!("Connect: {}", id)); bus.send( packet.from, QRPacket::new(id as u32, QRPacketData::RConn) ).ok(); Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QDisconn => { app.log.log(&format!("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); } } app.send_session_spectators(session_token).await; } // 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; app.log.log("Request: Register"); let mut is_valid = true; let regex = regex::Regex::new(r"^[\p{L}\p{N}\p{Pd}\p{Pc}\p{Sm}\p{Sc}]{1,24}$").unwrap(); let invite = app.invite_tokens.get(&request.code).cloned(); if is_valid && invite.is_none() { response.status = STATUS_BAD_CODE; is_valid = false; } if is_valid && !regex.is_match(&request.handle) { 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); app.log.log(&format!("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); } // Remove invitation from store. if let Some(invite) = invite { if let Some(invite) = app.invites.get(invite as usize).cloned() { app.invite_tokens.unset(&invite.token); } app.invites.remove(invite as usize).ok(); } } } Err(_) => { app.log.log("error: failed to generate salt.") } } } Some(_) => { response.status = STATUS_BAD_HANDLE; app.log.log(&format!("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; app.log.log("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() { app.log.log(&format!("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) { app.log.log(&format!("Authenticated user '{}' id {}", user.handle, uid)); // Generate authentication token and secret response.status = STATUS_OK; response.handle = user.handle.clone(); 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 { app.log.log("notice: password verification failed."); } } } else { app.log.log(&format!("error: user salt id '{}' not found.", user.na_key)); } } else { app.log.log(&format!("error: user with id '{}' not found.", uid)); } } else { app.log.log(&format!("error: user with id '{}' not found.", uid)); } } None => { } } } Some(QRPacket::new(qr.id, QRPacketData::RAuth(response))) } QRPacketData::QAuthResume(request) => { app.log.log("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() { response.handle = user.handle.clone(); 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 => { app.log.log("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) => { app.log.log(&format!("Deauthenticated connection: {}", qr.id)); app.auths.unset(&auth); } None => { } } conn.auth = None; } Some(QRPacket::new(0, QRPacketData::None)) } QRPacketData::QSessionList(request) => { app.log.log("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() }; let mut last_move = 0; let mut index = session.game.history.len(); let mut move_count = 0; while move_count < 4 && index > 0 { let play = session.game.history[index - 1]; if play.source <= 2 { last_move <<= 8; last_move |= 0x80 | play.meta as u32; move_count += 1; } index -= 1; } response.records.push(PacketSessionListResponseRecord { token:session.token, handles:[ dawn_handle, dusk_handle, ], turn:session.game.turn, last_move, 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) => { app.log.log("Request: Session Join"); 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 { app.log.log("User resumes session."); response.status = STATUS_OK; true } else { false } } else { response.status = STATUS_NOAUTH; false } } // Join game as spectator. else { app.log.log("User spectates session."); response.status = STATUS_OK; 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); app.send_session_spectators(request.token).await; } } Some(QRPacket::new(qr.id, QRPacketData::RSessionView(response))) } // SessionResign QRPacketData::QSessionResign(request) => { use game::history::Play; app.log.log("Request: Session Resign"); let mut packets = Vec::::new(); let play = Play { source: 0xF, from: 0, to: 0, meta: 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, test: false, expected: false, }), )); } } } } for packet in packets { app.send_response(packet).await; } Some(QRPacket::new(0, QRPacketData::None)) } // SessionLeave QRPacketData::QSessionLeave => { app.log.log("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 == 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); } } app.send_session_spectators(session_token).await; } Some(QRPacket::new(0, QRPacketData::None)) } // GameState QRPacketData::QGameState(request) => { let mut response = PacketGameStateResponse::new(); app.log.log("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) => { app.log.log("Request: Game Message"); let mut packets = Vec::::new(); let mut response = QRPacketData::None; 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) => { if request.test { let play = Play { source: match request.data { GameMessageData::PlayMove(..) => 0, GameMessageData::PlayDrop(..) => 1, GameMessageData::PlayAlt(..) => 2, _ => 0, }, from, to, meta: 0, }; let text = format!("PLAY {} {} {}", play.source, play.from, play.to); let result = if session.game.play_is_valid(&play) { response = QRPacketData::TestResult(PacketTestResult::new( request.expected, &text, )); request.expected } else { response = QRPacketData::TestResult(PacketTestResult::new( !request.expected, &text, )); !request.expected }; if result { app.log.log(&format!("OK {} {}", request.expected, text)); } else { app.log.log(&format!("NO {} {}", request.expected, text)); } } else 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, meta: 0, }; 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); session.undo = 0; } else { println!("notice: bad play {} {} {}", play.source, play.from, play.to); } } } } } GameMessageData::Undo(turn, _) => { use packet::PacketGameMessage; if session.game.turn > 0 && !session.game.is_complete() { let player = if user_id == Some(session.p_dawn.user) { 1 } else if user_id == Some(session.p_dusk.user) { 2 } else { 0 }; if player != 0 { if turn == session.game.turn && (session.undo & player) == 0 { if session.undo == 0 { // Send undo request to opposing player let packet = GameMessageData::Undo(turn, 0); if player == 1 { for cid in &session.p_dusk.connections { packets.push(QRPacket::new( *cid, QRPacketData::GameMessage(PacketGameMessage::with(packet)) )); } } else { for cid in &session.p_dawn.connections { packets.push(QRPacket::new( *cid, QRPacketData::GameMessage(PacketGameMessage::with(packet)) )); } } session.undo |= player; } else { // Send undo command to clients let revert_count = if session.game.turn > 1 && session.undo == (1 << (session.game.turn & 1)) { 2 } else { 1 }; for _ in 0..revert_count { session.game.history.pop(); app.filesystem.session_history_pop(session.id).ok(); } session.game.apply_history(&session.game.history.clone()).ok(); let packet = GameMessageData::Undo(session.game.turn, 1); // Send undo request to opposing player for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, QRPacketData::GameMessage(PacketGameMessage::with(packet)) )); } send_user_status.push(session.p_dawn.user); send_user_status.push(session.p_dusk.user); session.undo = 0; } } } } } 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) { session.undo = 0; // 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, response)) } } } // Challenge QRPacketData::QChallenge(request) => { app.log.log("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 { app.log.log("notice: duplicate challenge."); } } } } None } // ChallengeAnswer QRPacketData::QChallengeAnswer(request) => { app.log.log("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; // 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:0, }; 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 => { app.log.log("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 => { app.log.log("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))) } // InviteAcquire QRPacketData::QInviteAcquire => { use crate::app::{Invitation, InviteToken}; app.log.log("Request: Invite Acquire"); let mut response = PacketInviteAcquireResponse::new(); if let Some(user_id) = user_id { // Count number of invites acquired by user. let mut count_invites = 0; let invites = app.invites.list(); for id in invites { if let Some(invite) = app.invites.get(id) { if let Some(parent) = invite.parent { if parent == user_id { count_invites += 1; } } } } // Create new invite if below threshold. const MAX_INVITES :usize = 10; if count_invites < MAX_INVITES { let mut token = InviteToken::default(); loop { rng.fill(&mut token).ok(); for byte in &mut token { *byte = *byte % 62; *byte += if *byte < 10 { '0' as u8 } else if *byte < 36 { 'a' as u8 - 10 } else { 'A' as u8 - 36 }; } if app.invite_tokens.get(&token).is_none() { break } } let invite = Invitation { token, parent:Some(user_id), }; let id = app.invites.add(invite); app.invite_tokens.set(&token, id as u32); response.status = STATUS_OK; } else { response.status = STATUS_ERROR; } } else { response.status = STATUS_NOAUTH; } Some(QRPacket::new(qr.id, QRPacketData::RInviteAcquire(response))) } // InviteList QRPacketData::QInviteList => { app.log.log("Request: Invite List"); let mut response = PacketInviteListResponse::new(); if let Some(user_id) = user_id { // Get user's invitations. let invites = app.invites.list(); for id in invites { if let Some(invite) = app.invites.get(id) { if let Some(parent) = invite.parent { if parent == user_id { response.records.push(invite.clone()); } } } } response.status = STATUS_OK; } else { response.status = STATUS_NOAUTH; } Some(QRPacket::new(qr.id, QRPacketData::RInviteList(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; response.undo = session.undo; // Get history response.history = session.game.history.clone(); response }