# HG changeset patch # User alfadur # Date 1554833315 -10800 # Node ID f43ab2bd76ae7da8669f96b6fe7a9b432079284d # Parent bbec6b28d072d4fa2ed7051419783d0d8aceb566 add a thread for internal server IO and implement account checking with it diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/Cargo.toml --- a/rust/hedgewars-server/Cargo.toml Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/Cargo.toml Tue Apr 09 21:08:35 2019 +0300 @@ -12,6 +12,7 @@ [dependencies] rand = "0.6" mio = "0.6" +mio-extras = "2.0.5" slab = "0.4" netbuf = "0.4" nom = { git = "https://github.com/Geal/nom", branch = "5.0" } diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/main.rs --- a/rust/hedgewars-server/src/main.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/main.rs Tue Apr 09 21:08:35 2019 +0300 @@ -40,13 +40,16 @@ for event in events.iter() { if event.readiness() & Ready::readable() == Ready::readable() { match event.token() { - utils::SERVER => hw_network.accept_client(&poll).unwrap(), + utils::SERVER_TOKEN => hw_network.accept_client(&poll).unwrap(), + #[cfg(feature = "official-server")] + utils::IO_TOKEN => hw_network.handle_io_result(), Token(tok) => hw_network.client_readable(&poll, tok).unwrap(), } } if event.readiness() & Ready::writable() == Ready::writable() { match event.token() { - utils::SERVER => unreachable!(), + utils::SERVER_TOKEN => unreachable!(), + utils::IO_TOKEN => unreachable!(), Token(tok) => hw_network.client_writable(&poll, tok).unwrap(), } } diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/protocol/messages.rs --- a/rust/hedgewars-server/src/protocol/messages.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/protocol/messages.rs Tue Apr 09 21:08:35 2019 +0300 @@ -71,8 +71,11 @@ Ping, Pong, Bye(String), + Nick(String), Proto(u16), + AskPassword(String), + ServerAuth(String), LobbyLeft(String, String), LobbyJoined(Vec), @@ -282,6 +285,7 @@ Bye(msg) => msg!["BYE", msg], Nick(nick) => msg!["NICK", nick], Proto(proto) => msg!["PROTO", proto], + AskPassword(salt) => msg!["ASKPASSWORD", salt], ServerAuth(hash) => msg!["SERVER_AUTH", hash], LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg], LobbyJoined(nicks) => construct_message(&["LOBBY:JOINED"], &nicks), diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server.rs --- a/rust/hedgewars-server/src/server.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server.rs Tue Apr 09 21:08:35 2019 +0300 @@ -6,6 +6,7 @@ mod database; mod handlers; pub mod indexslab; +#[cfg(feature = "official-server")] pub mod io; pub mod network; pub mod room; diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/client.rs --- a/rust/hedgewars-server/src/server/client.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/client.rs Tue Apr 09 21:08:35 2019 +0300 @@ -9,6 +9,7 @@ const IS_IN_GAME = 0b0000_1000; const IS_JOINED_MID_GAME = 0b0001_0000; const IS_CHECKER = 0b0010_0000; + const IS_CONTRIBUTOR = 0b0100_0000; const NONE = 0b0000_0000; const DEFAULT = Self::NONE.bits; @@ -66,6 +67,9 @@ pub fn is_checker(&self) -> bool { self.contains(ClientFlags::IS_CHECKER) } + pub fn is_contributor(&self) -> bool { + self.contains(ClientFlags::IS_CONTRIBUTOR) + } pub fn set_is_admin(&mut self, value: bool) { self.set(ClientFlags::IS_ADMIN, value) @@ -85,4 +89,7 @@ pub fn set_is_checker(&mut self, value: bool) { self.set(ClientFlags::IS_CHECKER, value) } + pub fn set_is_contributor(&mut self, value: bool) { + self.set(ClientFlags::IS_CONTRIBUTOR, value) + } } diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/core.rs --- a/rust/hedgewars-server/src/server/core.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/core.rs Tue Apr 09 21:08:35 2019 +0300 @@ -2,13 +2,14 @@ client::HWClient, coretypes::{ClientId, RoomId}, indexslab::IndexSlab, - io::HWServerIO, room::HWRoom, }; use crate::utils; use log::*; use slab; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; use std::{borrow::BorrowMut, iter, num::NonZeroU16}; type Slab = slab::Slab; @@ -16,7 +17,6 @@ pub struct HWAnteClient { pub nick: Option, pub protocol_number: Option, - pub web_password: Option, pub server_salt: String, } @@ -35,20 +35,12 @@ nick: None, protocol_number: None, server_salt: salt, - web_password: None, }; self.clients.insert(client_id, client); } pub fn remove_client(&mut self, client_id: ClientId) -> Option { let mut client = self.clients.remove(client_id); - if let Some(HWAnteClient { - web_password: Some(ref mut password), - .. - }) = client - { - password.replace_range(.., "🦔🦔🦔🦔🦔🦔🦔🦔"); - } client } } @@ -213,3 +205,48 @@ } } } + +pub trait HWServerIO { + fn write_file(&mut self, name: &str, content: &str) -> std::io::Result<()>; + fn read_file(&mut self, name: &str) -> std::io::Result; +} + +pub struct EmptyServerIO {} + +impl EmptyServerIO { + pub fn new() -> Self { + Self {} + } +} + +impl HWServerIO for EmptyServerIO { + fn write_file(&mut self, _name: &str, _content: &str) -> std::io::Result<()> { + Ok(()) + } + + fn read_file(&mut self, _name: &str) -> std::io::Result { + Ok("".to_string()) + } +} + +pub struct FileServerIO {} + +impl FileServerIO { + pub fn new() -> Self { + Self {} + } +} + +impl HWServerIO for FileServerIO { + fn write_file(&mut self, name: &str, content: &str) -> std::io::Result<()> { + let mut writer = OpenOptions::new().create(true).write(true).open(name)?; + writer.write_all(content.as_bytes()) + } + + fn read_file(&mut self, name: &str) -> std::io::Result { + let mut reader = File::open(name)?; + let mut result = String::new(); + reader.read_to_string(&mut result)?; + Ok(result) + } +} diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/database.rs --- a/rust/hedgewars-server/src/server/database.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/database.rs Tue Apr 09 21:08:35 2019 +0300 @@ -1,11 +1,20 @@ use mysql; -use mysql::{error::DriverError, error::Error, params}; +use mysql::{error::DriverError, error::Error, from_row_opt, params}; +use openssl::sha::sha1; + +use super::handlers::AccountInfo; +use crate::server::handlers::Sha1Digest; -struct AccountInfo { - is_registered: bool, - is_admin: bool, - is_contributor: bool, -} +const GET_ACCOUNT_QUERY: &str = + r"SELECT CASE WHEN users.status = 1 THEN users.pass ELSE '' END, + (SELECT COUNT(users_roles.rid) FROM users_roles WHERE users.uid = users_roles.uid AND users_roles.rid = 3), + (SELECT COUNT(users_roles.rid) FROM users_roles WHERE users.uid = users_roles.uid AND users_roles.rid = 13) + FROM users WHERE users.name = :username"; + +const STORE_STATS_QUERY: &str = r"INSERT INTO gameserver_stats + (players, rooms, last_update) + VALUES + (:players, :rooms, UNIX_TIMESTAMP())"; struct ServerStatistics { rooms: u32, @@ -14,47 +23,63 @@ struct Achievements {} -trait DatabaseInterface { - fn check_account(username: &str, password: &str) -> AccountInfo; - fn store_stats(stats: &ServerStatistics) -> Result<(), ()>; - fn store_achievements(achievements: &Achievements) -> Result<(), ()>; - fn get_replay_name(replay_id: u32) -> Result; -} - -struct Database { +pub struct Database { pool: Option, } impl Database { - fn new() -> Self { + pub fn new() -> Self { Self { pool: None } } - fn connect(&mut self, url: &str) -> Result<(), Error> { + pub fn connect(&mut self, url: &str) -> Result<(), Error> { self.pool = Some(mysql::Pool::new(url)?); Ok(()) } - fn check_account(&mut self, username: &str, password: &str) -> AccountInfo { - AccountInfo { - is_registered: false, - is_admin: false, - is_contributor: false, + pub fn get_account( + &mut self, + nick: &str, + protocol: u16, + password_hash: &str, + client_salt: &str, + server_salt: &str, + ) -> Result, Error> { + if let Some(pool) = &self.pool { + if let Some(row) = pool.first_exec(GET_ACCOUNT_QUERY, params! { "username" => nick })? { + let (mut password, is_admin, is_contributor) = + from_row_opt::<(String, i32, i32)>(row)?; + let client_hash = get_hash(protocol, &password, &client_salt, &server_salt); + let server_hash = get_hash(protocol, &password, &server_salt, &client_salt); + password.replace_range(.., "🦔🦔🦔🦔🦔🦔🦔🦔"); + + if server_hash == client_hash { + Ok(Some(AccountInfo { + is_registered: true, + is_admin: is_admin == 1, + is_contributor: is_contributor == 1, + server_hash, + })) + } else { + Ok(None) + } + } else { + Ok(Some(AccountInfo { + is_registered: false, + is_admin: false, + is_contributor: false, + server_hash: Sha1Digest::new([0; 20]), + })) + } + } else { + Err(DriverError::SetupError.into()) } } - fn store_stats(&mut self, stats: &ServerStatistics) -> Result<(), Error> { + pub fn store_stats(&mut self, stats: &ServerStatistics) -> Result<(), Error> { if let Some(pool) = &self.pool { - for mut stmt in pool - .prepare( - r"INSERT INTO gameserver_stats - (players, rooms, last_update) - VALUES - (:players, :rooms, UNIX_TIMESTAMP())", - ) - .into_iter() - { + for mut stmt in pool.prepare(STORE_STATS_QUERY).into_iter() { stmt.execute(params! { "players" => stats.players, "rooms" => stats.rooms, @@ -66,11 +91,19 @@ } } - fn store_achievements(&mut self, achievements: &Achievements) -> Result<(), ()> { + pub fn store_achievements(&mut self, achievements: &Achievements) -> Result<(), ()> { Ok(()) } - fn get_replay_name(&mut self, replay_id: u32) -> Result { + pub fn get_replay_name(&mut self, replay_id: u32) -> Result { Err(()) } } + +fn get_hash(protocol_number: u16, web_password: &str, salt1: &str, salt2: &str) -> Sha1Digest { + let s = format!( + "{}{}{}{}{}", + salt1, salt2, web_password, protocol_number, "!hedgewars" + ); + Sha1Digest::new(sha1(s.as_bytes())) +} diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/handlers.rs --- a/rust/hedgewars-server/src/server/handlers.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/handlers.rs Tue Apr 09 21:08:35 2019 +0300 @@ -1,10 +1,11 @@ use mio; -use std::{io, io::Write}; +use std::{collections::HashMap, io, io::Write}; use super::{ actions::{Destination, DestinationRoom}, core::HWServer, coretypes::ClientId, + room::RoomSave, }; use crate::{ protocol::messages::{HWProtocolMessage, HWServerMessage, HWServerMessage::*}, @@ -22,10 +23,51 @@ mod loggingin; use self::loggingin::LoginResult; +use std::fmt::{Formatter, LowerHex}; + +#[derive(PartialEq)] +pub struct Sha1Digest([u8; 20]); + +impl Sha1Digest { + pub fn new(digest: [u8; 20]) -> Self { + Self(digest) + } +} + +impl LowerHex for Sha1Digest { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +pub struct AccountInfo { + pub is_registered: bool, + pub is_admin: bool, + pub is_contributor: bool, + pub server_hash: Sha1Digest, +} + +pub enum IoTask { + GetAccount { + nick: String, + protocol: u16, + password_hash: String, + client_salt: String, + server_salt: String, + }, +} + +pub enum IoResult { + Account(Option), +} pub struct Response { client_id: ClientId, messages: Vec, + io_tasks: Vec, removed_clients: Vec, } @@ -34,13 +76,14 @@ Self { client_id, messages: vec![], + io_tasks: vec![], removed_clients: vec![], } } #[inline] pub fn is_empty(&self) -> bool { - self.messages.is_empty() && self.removed_clients.is_empty() + self.messages.is_empty() && self.removed_clients.is_empty() && self.io_tasks.is_empty() } #[inline] @@ -58,6 +101,11 @@ self.messages.push(message) } + #[inline] + pub fn request_io(&mut self, task: IoTask) { + self.io_tasks.push(task) + } + pub fn extract_messages<'a, 'b: 'a>( &'b mut self, server: &'a HWServer, @@ -76,6 +124,10 @@ pub fn extract_removed_clients(&mut self) -> impl Iterator + '_ { self.removed_clients.drain(..) } + + pub fn extract_io_tasks(&mut self) -> impl Iterator + '_ { + self.io_tasks.drain(..) + } } impl Extend for Response { @@ -177,3 +229,26 @@ common::remove_client(server, response, "Connection reset".to_string()); } } + +pub fn handle_io_result( + server: &mut HWServer, + client_id: ClientId, + response: &mut Response, + io_result: IoResult, +) { + match io_result { + IoResult::Account(Some(info)) => { + response.add(ServerAuth(format!("{:x}", info.server_hash)).send_self()); + if let Some(client) = server.anteroom.remove_client(client_id) { + server.add_client(client_id, client); + let client = &mut server.clients[client_id]; + client.set_is_admin(info.is_admin); + client.set_is_contributor(info.is_admin) + } + } + IoResult::Account(None) => { + response.add(Error("Authentication failed.".to_string()).send_self()); + response.remove_client(client_id); + } + } +} diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/handlers/loggingin.rs --- a/rust/hedgewars-server/src/server/handlers/loggingin.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/handlers/loggingin.rs Tue Apr 09 21:08:35 2019 +0300 @@ -1,5 +1,6 @@ use mio; +use crate::protocol::messages::HWProtocolMessage::LoadRoom; use crate::{ protocol::messages::{HWProtocolMessage, HWServerMessage::*}, server::{ @@ -16,33 +17,25 @@ num::NonZeroU16, }; -#[derive(PartialEq)] -struct Sha1Digest([u8; 20]); - -impl LowerHex for Sha1Digest { - fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { - for byte in &self.0 { - write!(f, "{:02x}", byte)?; - } - Ok(()) - } -} - -#[cfg(feature = "official-server")] -fn get_hash(protocol_number: u16, web_password: &str, salt1: &str, salt2: &str) -> Sha1Digest { - let s = format!( - "{}{}{}{}{}", - salt1, salt2, web_password, protocol_number, "!hedgewars" - ); - Sha1Digest(sha1(s.as_bytes())) -} - pub enum LoginResult { Unchanged, Complete, Exit, } +fn completion_result(client: &HWAnteClient, response: &mut super::Response) -> LoginResult { + #[cfg(feature = "official-server")] + { + response.add(AskPassword(client.server_salt.clone()).send_self()); + LoginResult::Unchanged + } + + #[cfg(not(feature = "official-server"))] + { + LoginResult::Complete + } +} + pub fn handle( anteroom: &mut HWAnteroom, client_id: ClientId, @@ -68,7 +61,7 @@ response.add(Nick(nick).send_self()); if client.protocol_number.is_some() { - LoginResult::Complete + completion_result(&client, response) } else { LoginResult::Unchanged } @@ -87,7 +80,7 @@ response.add(Proto(proto).send_self()); if client.nick.is_some() { - LoginResult::Complete + completion_result(&client, response) } else { LoginResult::Unchanged } @@ -97,29 +90,23 @@ HWProtocolMessage::Password(hash, salt) => { let client = &anteroom.clients[client_id]; - if let (Some(protocol), Some(password)) = - (client.protocol_number, client.web_password.as_ref()) - { - let client_hash = get_hash(protocol.get(), &password, &salt, &client.server_salt); - let server_hash = get_hash(protocol.get(), &password, &client.server_salt, &salt); - if client_hash == server_hash { - response.add(ServerAuth(format!("{:x}", server_hash)).send_self()); - LoginResult::Complete - } else { - response.add(Bye("No protocol provided.".to_string()).send_self()); - LoginResult::Unchanged - } - } else { - response.add(Bye("Authentication failed.".to_string()).send_self()); - LoginResult::Exit - } + if let (Some(nick), Some(protocol)) = (client.nick.as_ref(), client.protocol_number) { + response.request_io(super::IoTask::GetAccount { + nick: nick.clone(), + protocol: protocol.get(), + server_salt: client.server_salt.clone(), + client_salt: salt, + password_hash: hash, + }); + }; + + LoginResult::Unchanged } #[cfg(feature = "official-server")] HWProtocolMessage::Checker(protocol, nick, password) => { let client = &mut anteroom.clients[client_id]; client.protocol_number = NonZeroU16::new(protocol); client.nick = Some(nick); - client.web_password = Some(password); //client.set_is_checker(true); LoginResult::Complete } diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/io.rs --- a/rust/hedgewars-server/src/server/io.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/io.rs Tue Apr 09 21:08:35 2019 +0300 @@ -1,49 +1,73 @@ use std::{ fs::{File, OpenOptions}, io::{Error, ErrorKind, Read, Result, Write}, + sync::mpsc, + thread, }; -pub trait HWServerIO { - fn write_file(&mut self, name: &str, content: &str) -> Result<()>; - fn read_file(&mut self, name: &str) -> Result; +use crate::server::{ + database::Database, + handlers::{IoResult, IoTask}, +}; +use mio::{Evented, Poll, PollOpt}; +use mio_extras::channel; + +pub type RequestId = u32; + +pub struct IOThread { + core_tx: mpsc::Sender<(RequestId, IoTask)>, + core_rx: channel::Receiver<(RequestId, IoResult)>, } -pub struct EmptyServerIO {} - -impl EmptyServerIO { +impl IOThread { pub fn new() -> Self { - Self {} - } -} + let (core_tx, io_rx) = mpsc::channel(); + let (io_tx, core_rx) = channel::channel(); + + let mut db = Database::new(); + db.connect("localhost"); -impl HWServerIO for EmptyServerIO { - fn write_file(&mut self, _name: &str, _content: &str) -> Result<()> { - Ok(()) + thread::spawn(move || { + while let Ok((request_id, task)) = io_rx.try_recv() { + match task { + IoTask::GetAccount { + nick, + protocol, + password_hash, + client_salt, + server_salt, + } => { + if let Ok(account) = db.get_account( + &nick, + protocol, + &password_hash, + &client_salt, + &server_salt, + ) { + io_tx.send((request_id, IoResult::Account(account))); + } + } + } + } + }); + + Self { core_rx, core_tx } } - fn read_file(&mut self, _name: &str) -> Result { - Ok("".to_string()) + pub fn send(&self, request_id: RequestId, task: IoTask) { + self.core_tx.send((request_id, task)).unwrap(); } -} -pub struct FileServerIO {} + pub fn try_recv(&self) -> Option<(RequestId, IoResult)> { + match self.core_rx.try_recv() { + Ok(result) => Some(result), + Err(mpsc::TryRecvError::Empty) => None, + Err(mpsc::TryRecvError::Disconnected) => unreachable!(), + } + } -impl FileServerIO { - pub fn new() -> Self { - Self {} + pub fn register_rx(&self, poll: &mio::Poll, token: mio::Token) -> Result<()> { + self.core_rx + .register(poll, token, mio::Ready::readable(), PollOpt::edge()) } } - -impl HWServerIO for FileServerIO { - fn write_file(&mut self, name: &str, content: &str) -> Result<()> { - let mut writer = OpenOptions::new().create(true).write(true).open(name)?; - writer.write_all(content.as_bytes()) - } - - fn read_file(&mut self, name: &str) -> Result { - let mut reader = File::open(name)?; - let mut result = String::new(); - reader.read_to_string(&mut result)?; - Ok(result) - } -} diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/server/network.rs --- a/rust/hedgewars-server/src/server/network.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/server/network.rs Tue Apr 09 21:08:35 2019 +0300 @@ -16,11 +16,16 @@ use netbuf; use slab::Slab; -use super::{core::HWServer, coretypes::ClientId, handlers, io::FileServerIO}; +use super::{core::FileServerIO, core::HWServer, coretypes::ClientId, handlers}; use crate::{ protocol::{messages::*, ProtocolDecoder}, utils, }; + +#[cfg(feature = "official-server")] +use super::io::{IOThread, RequestId}; + +use crate::server::handlers::{IoResult, IoTask}; #[cfg(feature = "tls-connections")] use openssl::{ error::ErrorStack, @@ -234,6 +239,56 @@ context: SslContext, } +#[cfg(feature = "official-server")] +pub struct IoLayer { + next_request_id: RequestId, + request_queue: Vec<(RequestId, ClientId)>, + io_thread: IOThread, +} + +#[cfg(feature = "official-server")] +impl IoLayer { + fn new() -> Self { + Self { + next_request_id: 0, + request_queue: vec![], + io_thread: IOThread::new(), + } + } + + fn send(&mut self, client_id: ClientId, task: IoTask) { + let request_id = self.next_request_id; + self.next_request_id += 1; + self.request_queue.push((request_id, client_id)); + self.io_thread.send(request_id, task); + } + + fn try_recv(&mut self) -> Option<(ClientId, IoResult)> { + let (request_id, result) = self.io_thread.try_recv()?; + if let Some(index) = self + .request_queue + .iter() + .position(|(id, _)| *id == request_id) + { + let (_, client_id) = self.request_queue.swap_remove(index); + Some((client_id, result)) + } else { + None + } + } + + fn cancel(&mut self, client_id: ClientId) { + let mut index = 0; + while index < self.request_queue.len() { + if self.request_queue[index].1 == client_id { + self.request_queue.swap_remove(index); + } else { + index += 1; + } + } + } +} + pub struct NetworkLayer { listener: TcpListener, server: HWServer, @@ -242,6 +297,8 @@ pending_cache: Vec<(ClientId, NetworkClientState)>, #[cfg(feature = "tls-connections")] ssl: ServerSsl, + #[cfg(feature = "official-server")] + io: IoLayer, } impl NetworkLayer { @@ -259,6 +316,8 @@ pending_cache, #[cfg(feature = "tls-connections")] ssl: NetworkLayer::create_ssl_context(), + #[cfg(feature = "official-server")] + io: IoLayer::new(), } } @@ -283,10 +342,15 @@ pub fn register_server(&self, poll: &Poll) -> io::Result<()> { poll.register( &self.listener, - utils::SERVER, + utils::SERVER_TOKEN, Ready::readable(), PollOpt::edge(), - ) + )?; + + #[cfg(feature = "official-server")] + self.io.io_thread.register_rx(poll, utils::IO_TOKEN)?; + + Ok(()) } fn deregister_client(&mut self, poll: &Poll, id: ClientId) { @@ -299,6 +363,8 @@ } if client_exists { self.clients.remove(id); + #[cfg(feature = "official-server")] + self.io.cancel(id); } } @@ -326,7 +392,7 @@ client_id } - fn flush_server_messages(&mut self, mut response: handlers::Response, poll: &Poll) { + fn handle_response(&mut self, mut response: handlers::Response, poll: &Poll) { debug!("{} pending server messages", response.len()); let output = response.extract_messages(&mut self.server); for (clients, message) in output { @@ -344,6 +410,22 @@ for client_id in response.extract_removed_clients() { self.deregister_client(poll, client_id); } + + #[cfg(feature = "official-server")] + { + let client_id = response.client_id(); + for task in response.extract_io_tasks() { + self.io.send(client_id, task); + } + } + } + + #[cfg(feature = "official-server")] + pub fn handle_io_result(&mut self) { + if let Some((client_id, result)) = self.io.try_recv() { + let mut response = handlers::Response::new(client_id); + handlers::handle_io_result(&mut self.server, client_id, &mut response, result); + } } fn create_client_socket(&self, socket: TcpStream) -> io::Result { @@ -381,7 +463,7 @@ handlers::handle_client_accept(&mut self.server, client_id, &mut response); if !response.is_empty() { - self.flush_server_messages(response, poll); + self.handle_response(response, poll); } Ok(()) @@ -438,7 +520,7 @@ } if !response.is_empty() { - self.flush_server_messages(response, poll); + self.handle_response(response, poll); } Ok(()) @@ -469,7 +551,7 @@ self.deregister_client(poll, client_id); let mut response = handlers::Response::new(client_id); handlers::handle_client_loss(&mut self.server, client_id, &mut response); - self.flush_server_messages(response, poll); + self.handle_response(response, poll); Ok(()) } diff -r bbec6b28d072 -r f43ab2bd76ae rust/hedgewars-server/src/utils.rs --- a/rust/hedgewars-server/src/utils.rs Tue Apr 09 00:45:14 2019 +0200 +++ b/rust/hedgewars-server/src/utils.rs Tue Apr 09 21:08:35 2019 +0300 @@ -3,7 +3,8 @@ use std::iter::Iterator; pub const PROTOCOL_VERSION: u32 = 3; -pub const SERVER: mio::Token = mio::Token(1_000_000_000); +pub const SERVER_TOKEN: mio::Token = mio::Token(1_000_000_000); +pub const IO_TOKEN: mio::Token = mio::Token(1_000_000_001); pub fn is_name_illegal(name: &str) -> bool { name.len() > 40