638 lines
29 KiB
Rust

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<protocol::QRPacket>)
{
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::<QRPacket>::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
}