# HG changeset patch # User unc0rr # Date 1544478286 -3600 # Node ID 06672690d71b8ef831a6daf9bf7a752e2cab2c00 # Parent 6843c4551cdeebfc2bea07a992a71a7d9c679e6b Move rust server into rust folder diff -r 6843c4551cde -r 06672690d71b gameServer2/Cargo.toml --- a/gameServer2/Cargo.toml Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -[package] -edition = "2018" -name = "hedgewars-server" -version = "0.0.1" -authors = [ "Andrey Korotaev " ] - -[features] -official-server = ["openssl"] -tls-connections = ["openssl"] -default = [] - -[dependencies] -rand = "0.5" -mio = "0.6" -slab = "0.4" -netbuf = "0.4" -nom = "4.1" -env_logger = "0.6" -log = "0.4" -base64 = "0.10" -bitflags = "1.0" -serde = "1.0" -serde_yaml = "0.8" -serde_derive = "1.0" -openssl = { version = "0.10", optional = true } - -[dev-dependencies] -proptest = "0.8" \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b gameServer2/src/main.rs --- a/gameServer2/src/main.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -#![allow(unused_imports)] -#![deny(bare_trait_objects)] - -//use std::io::*; -//use rand::Rng; -//use std::cmp::Ordering; -use mio::net::*; -use mio::*; -use log::*; - -mod utils; -mod server; -mod protocol; - -use crate::server::network::NetworkLayer; -use std::time::Duration; - -fn main() { - env_logger::init(); - - info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION); - - let address = "0.0.0.0:46631".parse().unwrap(); - let listener = TcpListener::bind(&address).unwrap(); - - let poll = Poll::new().unwrap(); - let mut hw_network = NetworkLayer::new(listener, 1024, 512); - hw_network.register_server(&poll).unwrap(); - - let mut events = Events::with_capacity(1024); - - loop { - let timeout = if hw_network.has_pending_operations() { - Some(Duration::from_millis(1)) - } else { - None - }; - poll.poll(&mut events, timeout).unwrap(); - - for event in events.iter() { - if event.readiness() & Ready::readable() == Ready::readable() { - match event.token() { - utils::SERVER => hw_network.accept_client(&poll).unwrap(), - Token(tok) => hw_network.client_readable(&poll, tok).unwrap(), - } - } - if event.readiness() & Ready::writable() == Ready::writable() { - match event.token() { - utils::SERVER => unreachable!(), - Token(tok) => hw_network.client_writable(&poll, tok).unwrap(), - } - } -// if event.kind().is_hup() || event.kind().is_error() { -// match event.token() { -// utils::SERVER => unreachable!(), -// Token(tok) => server.client_error(&poll, tok).unwrap(), -// } -// } - } - hw_network.on_idle(&poll).unwrap(); - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/protocol.rs --- a/gameServer2/src/protocol.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -use netbuf; -use std::{ - io::{Read, Result} -}; -use nom::{ - IResult, Err -}; - -pub mod messages; -#[cfg(test)] -pub mod test; -mod parser; - -pub struct ProtocolDecoder { - buf: netbuf::Buf, - consumed: usize, -} - -impl ProtocolDecoder { - pub fn new() -> ProtocolDecoder { - ProtocolDecoder { - buf: netbuf::Buf::new(), - consumed: 0, - } - } - - pub fn read_from(&mut self, stream: &mut R) -> Result { - self.buf.read_from(stream) - } - - pub fn extract_messages(&mut self) -> Vec { - let parse_result = parser::extract_messages(&self.buf[..]); - match parse_result { - Ok((tail, msgs)) => { - self.consumed = self.buf.len() - self.consumed - tail.len(); - msgs - }, - Err(Err::Incomplete(_)) => unreachable!(), - Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(), - } - } - - pub fn sweep(&mut self) { - self.buf.consume(self.consumed); - self.consumed = 0; - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/protocol/messages.rs --- a/gameServer2/src/protocol/messages.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,316 +0,0 @@ -use crate::server::coretypes::{ - ServerVar, GameCfg, TeamInfo, - HedgehogInfo, VoteType -}; -use std::{ops, convert::From, iter::once}; - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum HWProtocolMessage { - // core - Ping, - Pong, - Quit(Option), - //Cmd(String, Vec), - Global(String), - Watch(String), - ToggleServerRegisteredOnly, - SuperPower, - Info(String), - // not entered state - Nick(String), - Proto(u16), - Password(String, String), - Checker(u16, String, String), - // lobby - List, - Chat(String), - CreateRoom(String, Option), - JoinRoom(String, Option), - Follow(String), - Rnd(Vec), - Kick(String), - Ban(String, String, u32), - BanIP(String, String, u32), - BanNick(String, String, u32), - BanList, - Unban(String), - SetServerVar(ServerVar), - GetServerVar, - RestartServer, - Stats, - // in room - Part(Option), - Cfg(GameCfg), - AddTeam(Box), - RemoveTeam(String), - SetHedgehogsNumber(String, u8), - SetTeamColor(String, u8), - ToggleReady, - StartGame, - EngineMessage(String), - RoundFinished, - ToggleRestrictJoin, - ToggleRestrictTeams, - ToggleRegisteredOnly, - RoomName(String), - Delegate(String), - TeamChat(String), - MaxTeams(u8), - Fix, - Unfix, - Greeting(String), - CallVote(Option), - Vote(bool), - ForceVote(bool), - Save(String, String), - Delete(String), - SaveRoom(String), - LoadRoom(String), - Malformed, - Empty, -} - -#[derive(Debug)] -pub enum HWServerMessage { - Ping, - Pong, - Bye(String), - Nick(String), - Proto(u16), - ServerAuth(String), - LobbyLeft(String, String), - LobbyJoined(Vec), - ChatMsg {nick: String, msg: String}, - ClientFlags(String, Vec), - Rooms(Vec), - RoomAdd(Vec), - RoomJoined(Vec), - RoomLeft(String, String), - RoomRemove(String), - RoomUpdated(String, Vec), - TeamAdd(Vec), - TeamRemove(String), - TeamAccepted(String), - TeamColor(String, u8), - HedgehogsNumber(String, u8), - ConfigEntry(String, Vec), - Kicked, - RunGame, - ForwardEngineMessage(Vec), - RoundFinished, - - ServerMessage(String), - Notice(String), - Warning(String), - Error(String), - Connected(u32), - Unreachable, - - //Deprecated messages - LegacyReady(bool, Vec) -} - -pub fn server_chat(msg: String) -> HWServerMessage { - HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg } -} - -impl GameCfg { - pub fn to_protocol(&self) -> (String, Vec) { - use crate::server::coretypes::GameCfg::*; - match self { - FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]), - MapType(t) => ("MAP".to_string(), vec![t.to_string()]), - MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]), - MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]), - Seed(s) => ("SEED".to_string(), vec![s.to_string()]), - Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]), - - Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]), - Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]), - Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]), - Scheme(n, s) => ("SCHEME".to_string(), { - let mut v = vec![n.to_string()]; - v.extend(s.clone().into_iter()); - v - }), - Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]), - Theme(t) => ("THEME".to_string(), vec![t.to_string()]), - DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()]) - } - } - - pub fn to_server_msg(&self) -> HWServerMessage { - use self::HWServerMessage::ConfigEntry; - let (name, args) = self.to_protocol(); - HWServerMessage::ConfigEntry(name, args) - } -} - -macro_rules! const_braces { - ($e: expr) => { "{}\n" } -} - -macro_rules! msg { - [$($part: expr),*] => { - format!(concat!($(const_braces!($part)),*, "\n"), $($part),*); - }; -} - -#[cfg(test)] -macro_rules! several { - [$part: expr] => { once($part) }; - [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) }; -} - -impl HWProtocolMessage { - /** Converts the message to a raw `String`, which can be sent over the network. - * - * This is the inverse of the `message` parser. - */ - #[cfg(test)] - pub(crate) fn to_raw_protocol(&self) -> String { - use self::HWProtocolMessage::*; - match self { - Ping => msg!["PING"], - Pong => msg!["PONG"], - Quit(None) => msg!["QUIT"], - Quit(Some(msg)) => msg!["QUIT", msg], - Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)], - Watch(name) => msg!["CMD", format!("WATCH {}", name)], - ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"], - SuperPower => msg!["CMD", "SUPER_POWER"], - Info(info) => msg!["CMD", format!("INFO {}", info)], - Nick(nick) => msg!("NICK", nick), - Proto(version) => msg!["PROTO", version], - Password(p, s) => msg!["PASSWORD", p, s], - Checker(i, n, p) => msg!["CHECKER", i, n, p], - List => msg!["LIST"], - Chat(msg) => msg!["CHAT", msg], - CreateRoom(name, None) => msg!["CREATE_ROOM", name], - CreateRoom(name, Some(password)) => - msg!["CREATE_ROOM", name, password], - JoinRoom(name, None) => msg!["JOIN_ROOM", name], - JoinRoom(name, Some(password)) => - msg!["JOIN_ROOM", name, password], - Follow(name) => msg!["FOLLOW", name], - Rnd(args) => if args.is_empty() { - msg!["CMD", "RND"] - } else { - msg!["CMD", format!("RND {}", args.join(" "))] - }, - Kick(name) => msg!["KICK", name], - Ban(name, reason, time) => msg!["BAN", name, reason, time], - BanIP(ip, reason, time) => msg!["BAN_IP", ip, reason, time], - BanNick(nick, reason, time) => - msg!("BAN_NICK", nick, reason, time), - BanList => msg!["BANLIST"], - Unban(name) => msg!["UNBAN", name], - //SetServerVar(ServerVar), ??? - GetServerVar => msg!["GET_SERVER_VAR"], - RestartServer => msg!["CMD", "RESTART_SERVER YES"], - Stats => msg!["CMD", "STATS"], - Part(None) => msg!["PART"], - Part(Some(msg)) => msg!["PART", msg], - Cfg(config) => { - let (name, args) = config.to_protocol(); - msg!["CFG", name, args.join("\n")] - }, - AddTeam(info) => - msg!["ADD_TEAM", info.name, info.color, info.grave, info.fort, - info.voice_pack, info.flag, info.difficulty, - info.hedgehogs.iter() - .flat_map(|h| several![&h.name[..], &h.hat[..]]) - .collect::>().join("\n")], - RemoveTeam(name) => msg!["REMOVE_TEAM", name], - SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number], - SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color], - ToggleReady => msg!["TOGGLE_READY"], - StartGame => msg!["START_GAME"], - EngineMessage(msg) => msg!["EM", msg], - RoundFinished => msg!["ROUNDFINISHED"], - ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"], - ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"], - ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"], - RoomName(name) => msg!["ROOM_NAME", name], - Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)], - TeamChat(msg) => msg!["TEAMCHAT", msg], - MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)] , - Fix => msg!["CMD", "FIX"], - Unfix => msg!["CMD", "UNFIX"], - Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)], - //CallVote(Option<(String, Option)>) =>, ?? - Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg {"YES"} else {"NO"})], - ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg {"YES"} else {"NO"})], - Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)], - Delete(name) => msg!["CMD", format!("DELETE {}", name)], - SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)], - LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)], - Malformed => msg!["A", "QUICK", "BROWN", "HOG", "JUMPS", "OVER", "THE", "LAZY", "DOG"], - Empty => msg![""], - _ => panic!("Protocol message not yet implemented") - } - } -} - -fn construct_message(header: &[&str], msg: &[String]) -> String { - let mut v: Vec<_> = header.iter().cloned().collect(); - v.extend(msg.iter().map(|s| &s[..])); - v.push("\n"); - v.join("\n") -} - -impl HWServerMessage { - pub fn to_raw_protocol(&self) -> String { - use self::HWServerMessage::*; - match self { - Ping => msg!["PING"], - Pong => msg!["PONG"], - Connected(protocol_version) => msg![ - "CONNECTED", - "Hedgewars server https://www.hedgewars.org/", - protocol_version], - Bye(msg) => msg!["BYE", msg], - Nick(nick) => msg!["NICK", nick], - Proto(proto) => msg!["PROTO", proto], - ServerAuth(hash) => msg!["SERVER_AUTH", hash], - LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg], - LobbyJoined(nicks) => - construct_message(&["LOBBY:JOINED"], &nicks), - ClientFlags(flags, nicks) => - construct_message(&["CLIENT_FLAGS", flags], &nicks), - Rooms(info) => - construct_message(&["ROOMS"], &info), - RoomAdd(info) => - construct_message(&["ROOM", "ADD"], &info), - RoomJoined(nicks) => - construct_message(&["JOINED"], &nicks), - RoomLeft(nick, msg) => msg!["LEFT", nick, msg], - RoomRemove(name) => msg!["ROOM", "DEL", name], - RoomUpdated(name, info) => - construct_message(&["ROOM", "UPD", name], &info), - TeamAdd(info) => - construct_message(&["ADD_TEAM"], &info), - TeamRemove(name) => msg!["REMOVE_TEAM", name], - TeamAccepted(name) => msg!["TEAM_ACCEPTED", name], - TeamColor(name, color) => msg!["TEAM_COLOR", name, color], - HedgehogsNumber(name, number) => msg!["HH_NUM", name, number], - ConfigEntry(name, values) => - construct_message(&["CFG", name], &values), - Kicked => msg!["KICKED"], - RunGame => msg!["RUN_GAME"], - ForwardEngineMessage(em) => - construct_message(&["EM"], &em), - RoundFinished => msg!["ROUND_FINISHED"], - ChatMsg {nick, msg} => msg!["CHAT", nick, msg], - ServerMessage(msg) => msg!["SERVER_MESSAGE", msg], - Notice(msg) => msg!["NOTICE", msg], - Warning(msg) => msg!["WARNING", msg], - Error(msg) => msg!["ERROR", msg], - - LegacyReady(is_ready, nicks) => - construct_message(&[if *is_ready {"READY"} else {"NOT_READY"}], &nicks), - - _ => msg!["ERROR", "UNIMPLEMENTED"], - } - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/protocol/parser.rs --- a/gameServer2/src/protocol/parser.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -/** The parsers for the chat and multiplayer protocol. The main parser is `message`. - * # Protocol - * All messages consist of `\n`-separated strings. The end of a message is - * indicated by a double newline - `\n\n`. - * - * For example, a nullary command like PING will be actually sent as `PING\n\n`. - * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`. - */ - -use nom::*; - -use std::{ - str, str::FromStr, - ops::Range -}; -use super::{ - messages::{HWProtocolMessage, HWProtocolMessage::*} -}; -#[cfg(test)] -use { - super::test::gen_proto_msg, - proptest::{proptest, proptest_helper} -}; -use crate::server::coretypes::{ - HedgehogInfo, TeamInfo, GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM -}; - -named!(end_of_message, tag!("\n\n")); -named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); -named!( a_line<&[u8], String>, map!(str_line, String::from)); -named!(cmd_arg<&[u8], String>, - map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from)); -named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str)); -named!(u16_line<&[u8], u16>, map_res!(str_line, FromStr::from_str)); -named!(u32_line<&[u8], u32>, map_res!(str_line, FromStr::from_str)); -named!(yes_no_line<&[u8], bool>, alt!( - do_parse!(tag_no_case!("YES") >> (true)) - | do_parse!(tag_no_case!("NO") >> (false)))); -named!(opt_param<&[u8], Option >, alt!( - do_parse!(peek!(tag!("\n\n")) >> (None)) - | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string()))))); -named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" "))); -named!(opt_space_param<&[u8], Option >, alt!( - do_parse!(peek!(tag!("\n\n")) >> (None)) - | do_parse!(spaces >> s: str_line >> (Some(s.to_string()))))); -named!(hog_line<&[u8], HedgehogInfo>, - do_parse!(name: str_line >> eol >> hat: str_line >> - (HedgehogInfo{name: name.to_string(), hat: hat.to_string()}))); -named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>, - do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >> - h3: hog_line >> eol >> h4: hog_line >> eol >> - h5: hog_line >> eol >> h6: hog_line >> eol >> - h7: hog_line >> eol >> h8: hog_line >> - ([h1, h2, h3, h4, h5, h6, h7, h8]))); -named!(voting<&[u8], VoteType>, alt!( - do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >> - (VoteType::Kick(n))) - | do_parse!(tag_no_case!("MAP") >> - n: opt!(preceded!(spaces, a_line)) >> - (VoteType::Map(n))) - | do_parse!(tag_no_case!("PAUSE") >> - (VoteType::Pause)) - | do_parse!(tag_no_case!("NEWSEED") >> - (VoteType::NewSeed)) - | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >> - (VoteType::HedgehogsPerTeam(n))))); - -/** Recognizes messages which do not take any parameters */ -named!(basic_message<&[u8], HWProtocolMessage>, alt!( - do_parse!(tag!("PING") >> (Ping)) - | do_parse!(tag!("PONG") >> (Pong)) - | do_parse!(tag!("LIST") >> (List)) - | do_parse!(tag!("BANLIST") >> (BanList)) - | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar)) - | do_parse!(tag!("TOGGLE_READY") >> (ToggleReady)) - | do_parse!(tag!("START_GAME") >> (StartGame)) - | do_parse!(tag!("ROUNDFINISHED") >> _m: opt_param >> (RoundFinished)) - | do_parse!(tag!("TOGGLE_RESTRICT_JOINS") >> (ToggleRestrictJoin)) - | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS") >> (ToggleRestrictTeams)) - | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly)) -)); - -/** Recognizes messages which take exactly one parameter */ -named!(one_param_message<&[u8], HWProtocolMessage>, alt!( - do_parse!(tag!("NICK") >> eol >> n: a_line >> (Nick(n))) - | do_parse!(tag!("INFO") >> eol >> n: a_line >> (Info(n))) - | do_parse!(tag!("CHAT") >> eol >> m: a_line >> (Chat(m))) - | do_parse!(tag!("PART") >> msg: opt_param >> (Part(msg))) - | do_parse!(tag!("FOLLOW") >> eol >> n: a_line >> (Follow(n))) - | do_parse!(tag!("KICK") >> eol >> n: a_line >> (Kick(n))) - | do_parse!(tag!("UNBAN") >> eol >> n: a_line >> (Unban(n))) - | do_parse!(tag!("EM") >> eol >> m: a_line >> (EngineMessage(m))) - | do_parse!(tag!("TEAMCHAT") >> eol >> m: a_line >> (TeamChat(m))) - | do_parse!(tag!("ROOM_NAME") >> eol >> n: a_line >> (RoomName(n))) - | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n))) - - | do_parse!(tag!("PROTO") >> eol >> d: u16_line >> (Proto(d))) - - | do_parse!(tag!("QUIT") >> msg: opt_param >> (Quit(msg))) -)); - -/** Recognizes messages preceded with CMD */ -named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!( - do_parse!(tag_no_case!("STATS") >> (Stats)) - | do_parse!(tag_no_case!("FIX") >> (Fix)) - | do_parse!(tag_no_case!("UNFIX") >> (Unfix)) - | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer)) - | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly)) - | do_parse!(tag_no_case!("SUPER_POWER") >> (SuperPower)) - | do_parse!(tag_no_case!("PART") >> m: opt_space_param >> (Part(m))) - | do_parse!(tag_no_case!("QUIT") >> m: opt_space_param >> (Quit(m))) - | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line >> (Delegate(n))) - | do_parse!(tag_no_case!("SAVE") >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l))) - | do_parse!(tag_no_case!("DELETE") >> spaces >> n: a_line >> (Delete(n))) - | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line >> (SaveRoom(r))) - | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line >> (LoadRoom(r))) - | do_parse!(tag_no_case!("GLOBAL") >> spaces >> m: a_line >> (Global(m))) - | do_parse!(tag_no_case!("WATCH") >> spaces >> i: a_line >> (Watch(i))) - | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line >> (Greeting(m))) - | do_parse!(tag_no_case!("VOTE") >> spaces >> m: yes_no_line >> (Vote(m))) - | do_parse!(tag_no_case!("FORCE") >> spaces >> m: yes_no_line >> (ForceVote(m))) - | do_parse!(tag_no_case!("INFO") >> spaces >> n: a_line >> (Info(n))) - | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n))) - | do_parse!(tag_no_case!("CALLVOTE") >> - v: opt!(preceded!(spaces, voting)) >> (CallVote(v))) - | do_parse!( - tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >> - v: str_line >> - (Rnd(v.split_whitespace().map(String::from).collect()))) -))); - -named!(complex_message<&[u8], HWProtocolMessage>, alt!( - do_parse!(tag!("PASSWORD") >> eol >> - p: a_line >> eol >> - s: a_line >> - (Password(p, s))) - | do_parse!(tag!("CHECKER") >> eol >> - i: u16_line >> eol >> - n: a_line >> eol >> - p: a_line >> - (Checker(i, n, p))) - | do_parse!(tag!("CREATE_ROOM") >> eol >> - n: a_line >> - p: opt_param >> - (CreateRoom(n, p))) - | do_parse!(tag!("JOIN_ROOM") >> eol >> - n: a_line >> - p: opt_param >> - (JoinRoom(n, p))) - | do_parse!(tag!("ADD_TEAM") >> eol >> - name: a_line >> eol >> - color: u8_line >> eol >> - grave: a_line >> eol >> - fort: a_line >> eol >> - voice_pack: a_line >> eol >> - flag: a_line >> eol >> - difficulty: u8_line >> eol >> - hedgehogs: _8_hogs >> - (AddTeam(Box::new(TeamInfo{ - name, color, grave, fort, - voice_pack, flag, difficulty, - hedgehogs, hedgehogs_number: 0 - })))) - | do_parse!(tag!("HH_NUM") >> eol >> - n: a_line >> eol >> - c: u8_line >> - (SetHedgehogsNumber(n, c))) - | do_parse!(tag!("TEAM_COLOR") >> eol >> - n: a_line >> eol >> - c: u8_line >> - (SetTeamColor(n, c))) - | do_parse!(tag!("BAN") >> eol >> - n: a_line >> eol >> - r: a_line >> eol >> - t: u32_line >> - (Ban(n, r, t))) - | do_parse!(tag!("BAN_IP") >> eol >> - n: a_line >> eol >> - r: a_line >> eol >> - t: u32_line >> - (BanIP(n, r, t))) - | do_parse!(tag!("BAN_NICK") >> eol >> - n: a_line >> eol >> - r: a_line >> eol >> - t: u32_line >> - (BanNick(n, r, t))) -)); - -named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!( - do_parse!(tag!("THEME") >> eol >> - name: a_line >> - (GameCfg::Theme(name))) - | do_parse!(tag!("SCRIPT") >> eol >> - name: a_line >> - (GameCfg::Script(name))) - | do_parse!(tag!("AMMO") >> eol >> - name: a_line >> - value: opt_param >> - (GameCfg::Ammo(name, value))) - | do_parse!(tag!("SCHEME") >> eol >> - name: a_line >> - values: opt!(preceded!(eol, separated_list!(eol, a_line))) >> - (GameCfg::Scheme(name, values.unwrap_or_default()))) - | do_parse!(tag!("FEATURE_SIZE") >> eol >> - value: u32_line >> - (GameCfg::FeatureSize(value))) - | do_parse!(tag!("MAP") >> eol >> - value: a_line >> - (GameCfg::MapType(value))) - | do_parse!(tag!("MAPGEN") >> eol >> - value: u32_line >> - (GameCfg::MapGenerator(value))) - | do_parse!(tag!("MAZE_SIZE") >> eol >> - value: u32_line >> - (GameCfg::MazeSize(value))) - | do_parse!(tag!("SEED") >> eol >> - value: a_line >> - (GameCfg::Seed(value))) - | do_parse!(tag!("TEMPLATE") >> eol >> - value: u32_line >> - (GameCfg::Template(value))) - | do_parse!(tag!("DRAWNMAP") >> eol >> - value: a_line >> - (GameCfg::DrawnMap(value))) -), Cfg))); - -named!(malformed_message<&[u8], HWProtocolMessage>, - do_parse!(separated_list!(eol, a_line) >> (Malformed))); - -named!(empty_message<&[u8], HWProtocolMessage>, - do_parse!(alt!(end_of_message | eol) >> (Empty))); - -named!(message<&[u8], HWProtocolMessage>, alt!(terminated!( - alt!( - basic_message - | one_param_message - | cmd_message - | complex_message - | cfg_message - ), end_of_message - ) - | terminated!(malformed_message, end_of_message) - | empty_message - ) -); - -named!(pub extract_messages<&[u8], Vec >, many0!(complete!(message))); - -#[cfg(test)] -proptest! { - #[test] - fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { - println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); - assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone()))) - } -} - -#[test] -fn parse_test() { - assert_eq!(message(b"PING\n\n"), Ok((&b""[..], Ping))); - assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame))); - assert_eq!(message(b"NICK\nit's me\n\n"), Ok((&b""[..], Nick("it's me".to_string())))); - assert_eq!(message(b"PROTO\n51\n\n"), Ok((&b""[..], Proto(51)))); - assert_eq!(message(b"QUIT\nbye-bye\n\n"), Ok((&b""[..], Quit(Some("bye-bye".to_string()))))); - assert_eq!(message(b"QUIT\n\n"), Ok((&b""[..], Quit(None)))); - assert_eq!(message(b"CMD\nwatch demo\n\n"), Ok((&b""[..], Watch("demo".to_string())))); - assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77)))); - - assert_eq!(message(b"CMD\nPART\n\n"), Ok((&b""[..], Part(None)))); - assert_eq!(message(b"CMD\nPART _msg_\n\n"), Ok((&b""[..], Part(Some("_msg_".to_string()))))); - - assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![])))); - assert_eq!( - message(b"CMD\nRND A B\n\n"), - Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")]))) - ); - - assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), Ok((&b""[..], vec![Malformed]))); - - assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), Ok((&b"P"[..], vec![Ping, Ping]))); - assert_eq!(extract_messages(b"SING\n\nPING\n\n"), Ok((&b""[..], vec![Malformed, Ping]))); - assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), Ok((&b""[..], vec![Empty, Empty, Ping]))); - assert_eq!(extract_messages(b"\n\n\nPING\n\n"), Ok((&b""[..], vec![Empty, Empty, Ping]))); -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/protocol/test.rs --- a/gameServer2/src/protocol/test.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -use proptest::{ - test_runner::{TestRunner, Reason}, - arbitrary::{any, any_with, Arbitrary, StrategyFor}, - strategy::{Strategy, BoxedStrategy, Just, Map} -}; - -use crate::server::coretypes::{GameCfg, TeamInfo, HedgehogInfo}; - -use super::messages::{ - HWProtocolMessage, HWProtocolMessage::* -}; - -// Due to inability to define From between Options -trait Into2: Sized { fn into2(self) -> T; } -impl Into2 for T { fn into2(self) -> T { self } } -impl Into2> for Vec { - fn into2(self) -> Vec { - self.into_iter().map(|x| x.0).collect() - } -} -impl Into2 for Ascii { fn into2(self) -> String { self.0 } } -impl Into2> for Option{ - fn into2(self) -> Option { self.map(|x| {x.0}) } -} - -macro_rules! proto_msg_case { - ($val: ident()) => - (Just($val)); - ($val: ident($arg: ty)) => - (any::<$arg>().prop_map(|v| {$val(v.into2())})); - ($val: ident($arg1: ty, $arg2: ty)) => - (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())})); - ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => - (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())})); -} - -macro_rules! proto_msg_match { - ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => ( - match $var { - $($num => (proto_msg_case!($constr $res)).boxed()),*, - _ => Just($default).boxed() - } - ) -} - -/// Wrapper type for generating non-empty strings -#[derive(Debug)] -struct Ascii(String); - -impl Arbitrary for Ascii { - type Parameters = ::Parameters; - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - "[a-zA-Z0-9]+".prop_map(Ascii).boxed() - } - - type Strategy = BoxedStrategy; -} - -impl Arbitrary for GameCfg { - type Parameters = (); - - fn arbitrary_with(_args: ::Parameters) -> ::Strategy { - use crate::server::coretypes::GameCfg::*; - (0..10).no_shrink().prop_flat_map(|i| { - proto_msg_match!(i, def = FeatureSize(0), - 0 => FeatureSize(u32), - 1 => MapType(Ascii), - 2 => MapGenerator(u32), - 3 => MazeSize(u32), - 4 => Seed(Ascii), - 5 => Template(u32), - 6 => Ammo(Ascii, Option), - 7 => Scheme(Ascii, Vec), - 8 => Script(Ascii), - 9 => Theme(Ascii), - 10 => DrawnMap(Ascii)) - }).boxed() - } - - type Strategy = BoxedStrategy; -} - -impl Arbitrary for TeamInfo { - type Parameters = (); - - fn arbitrary_with(_args: ::Parameters) -> ::Strategy { - ("[a-z]+", 0u8..127u8, "[a-z]+", "[a-z]+", "[a-z]+", "[a-z]+", 0u8..127u8) - .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| { - fn hog(n: u8) -> HedgehogInfo { - HedgehogInfo { name: format!("hog{}", n), hat: format!("hat{}", n)} - } - let hedgehogs = [hog(1), hog(2), hog(3), hog(4), hog(5), hog(6), hog(7), hog(8)]; - TeamInfo { - name, color, grave, fort, - voice_pack, flag,difficulty, - hedgehogs, hedgehogs_number: 0 - } - }).boxed() - } - - type Strategy = BoxedStrategy; -} - -pub fn gen_proto_msg() -> BoxedStrategy where { - let res = (0..58).no_shrink().prop_flat_map(|i| { - proto_msg_match!(i, def = Malformed, - 0 => Ping(), - 1 => Pong(), - 2 => Quit(Option), - //3 => Cmd - 4 => Global(Ascii), - 5 => Watch(Ascii), - 6 => ToggleServerRegisteredOnly(), - 7 => SuperPower(), - 8 => Info(Ascii), - 9 => Nick(Ascii), - 10 => Proto(u16), - 11 => Password(Ascii, Ascii), - 12 => Checker(u16, Ascii, Ascii), - 13 => List(), - 14 => Chat(Ascii), - 15 => CreateRoom(Ascii, Option), - 16 => JoinRoom(Ascii, Option), - 17 => Follow(Ascii), - 18 => Rnd(Vec), - 19 => Kick(Ascii), - 20 => Ban(Ascii, Ascii, u32), - 21 => BanIP(Ascii, Ascii, u32), - 22 => BanNick(Ascii, Ascii, u32), - 23 => BanList(), - 24 => Unban(Ascii), - //25 => SetServerVar(ServerVar), - 26 => GetServerVar(), - 27 => RestartServer(), - 28 => Stats(), - 29 => Part(Option), - 30 => Cfg(GameCfg), - 31 => AddTeam(Box), - 32 => RemoveTeam(Ascii), - 33 => SetHedgehogsNumber(Ascii, u8), - 34 => SetTeamColor(Ascii, u8), - 35 => ToggleReady(), - 36 => StartGame(), - 37 => EngineMessage(Ascii), - 38 => RoundFinished(), - 39 => ToggleRestrictJoin(), - 40 => ToggleRestrictTeams(), - 41 => ToggleRegisteredOnly(), - 42 => RoomName(Ascii), - 43 => Delegate(Ascii), - 44 => TeamChat(Ascii), - 45 => MaxTeams(u8), - 46 => Fix(), - 47 => Unfix(), - 48 => Greeting(Ascii), - //49 => CallVote(Option<(String, Option)>), - 50 => Vote(bool), - 51 => ForceVote(bool), - 52 => Save(Ascii, Ascii), - 53 => Delete(Ascii), - 54 => SaveRoom(Ascii), - 55 => LoadRoom(Ascii), - 56 => Malformed(), - 57 => Empty() - )}); - res.boxed() -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server.rs --- a/gameServer2/src/server.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -pub mod core; -pub mod client; -pub mod io; -pub mod room; -pub mod network; -pub mod coretypes; -mod actions; -mod handlers; diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/actions.rs --- a/gameServer2/src/server/actions.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,631 +0,0 @@ -use std::{ - io, io::Write, - iter::once, - mem::replace -}; -use super::{ - core::HWServer, - room::{GameInfo, RoomFlags}, - client::HWClient, - coretypes::{ClientId, RoomId, GameCfg, VoteType}, - room::HWRoom, - handlers -}; -use crate::{ - protocol::messages::{ - HWProtocolMessage, - HWServerMessage, - HWServerMessage::*, - server_chat - }, - utils::to_engine_msg -}; -use rand::{thread_rng, Rng, distributions::Uniform}; - -pub enum Destination { - ToId(ClientId), - ToSelf, - ToAll { - room_id: Option, - protocol: Option, - skip_self: bool - } -} - -pub struct PendingMessage { - pub destination: Destination, - pub message: HWServerMessage -} - -impl PendingMessage { - pub fn send(message: HWServerMessage, client_id: ClientId) -> PendingMessage { - PendingMessage{ destination: Destination::ToId(client_id), message} - } - - pub fn send_self(message: HWServerMessage) -> PendingMessage { - PendingMessage{ destination: Destination::ToSelf, message } - } - - pub fn send_all(message: HWServerMessage) -> PendingMessage { - let destination = Destination::ToAll { - room_id: None, - protocol: None, - skip_self: false, - }; - PendingMessage{ destination, message } - } - - pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage { - if let Destination::ToAll {ref mut room_id, ..} = self.destination { - *room_id = Some(clients_room_id) - } - self - } - - pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage { - if let Destination::ToAll {ref mut protocol, ..} = self.destination { - *protocol = Some(protocol_number) - } - self - } - - pub fn but_self(mut self) -> PendingMessage { - if let Destination::ToAll {ref mut skip_self, ..} = self.destination { - *skip_self = true - } - self - } - - pub fn action(self) -> Action { Send(self) } -} - -impl Into for PendingMessage { - fn into(self) -> Action { self.action() } -} - -impl HWServerMessage { - pub fn send(self, client_id: ClientId) -> PendingMessage { PendingMessage::send(self, client_id) } - pub fn send_self(self) -> PendingMessage { PendingMessage::send_self(self) } - pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) } -} - -pub enum Action { - Send(PendingMessage), - RemoveClient, - ByeClient(String), - ReactProtocolMessage(HWProtocolMessage), - CheckRegistered, - JoinLobby, - AddRoom(String, Option), - RemoveRoom(RoomId), - MoveToRoom(RoomId), - MoveToLobby(String), - ChangeMaster(RoomId, Option), - RemoveTeam(String), - RemoveClientTeams, - SendRoomUpdate(Option), - StartRoomGame(RoomId), - SendTeamRemovalMessage(String), - FinishRoomGame(RoomId), - SendRoomData{to: ClientId, teams: bool, config: bool, flags: bool}, - AddVote{vote: bool, is_forced: bool}, - ApplyVoting(VoteType, RoomId), - Warn(String), - ProtocolError(String) -} - -use self::Action::*; - -pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) { - match action { - Send(msg) => server.send(client_id, &msg.destination, msg.message), - ByeClient(msg) => { - let c = &server.clients[client_id]; - let nick = c.nick.clone(); - - if let Some(id) = c.room_id{ - if id != server.lobby_id { - server.react(client_id, vec![ - MoveToLobby(format!("quit: {}", msg.clone()))]); - } - } - - server.react(client_id, vec![ - LobbyLeft(nick, msg.clone()).send_all().action(), - Bye(msg).send_self().action(), - RemoveClient]); - }, - RemoveClient => { - server.removed_clients.push(client_id); - if server.clients.contains(client_id) { - server.clients.remove(client_id); - } - }, - ReactProtocolMessage(msg) => - handlers::handle(server, client_id, msg), - CheckRegistered => { - let client = &server.clients[client_id]; - if client.protocol_number > 0 && client.nick != "" { - let has_nick_clash = server.clients.iter().any( - |(id, c)| id != client_id && c.nick == client.nick); - - let actions = if !client.is_checker() && has_nick_clash { - if client.protocol_number < 38 { - vec![ByeClient("Nickname is already in use".to_string())] - } else { - server.clients[client_id].nick.clear(); - vec![Notice("NickAlreadyInUse".to_string()).send_self().action()] - } - } else { - vec![JoinLobby] - }; - server.react(client_id, actions); - } - }, - JoinLobby => { - server.clients[client_id].room_id = Some(server.lobby_id); - - let mut lobby_nicks = Vec::new(); - for (_, c) in server.clients.iter() { - if c.room_id.is_some() { - lobby_nicks.push(c.nick.clone()); - } - } - let joined_msg = LobbyJoined(lobby_nicks); - - let everyone_msg = LobbyJoined(vec![server.clients[client_id].nick.clone()]); - let flags_msg = ClientFlags( - "+i".to_string(), - server.clients.iter() - .filter(|(_, c)| c.room_id.is_some()) - .map(|(_, c)| c.nick.clone()) - .collect()); - let server_msg = ServerMessage("\u{1f994} is watching".to_string()); - let rooms_msg = Rooms(server.rooms.iter() - .filter(|(id, _)| *id != server.lobby_id) - .flat_map(|(_, r)| - r.info(r.master_id.map(|id| &server.clients[id]))) - .collect()); - server.react(client_id, vec![ - everyone_msg.send_all().but_self().action(), - joined_msg.send_self().action(), - flags_msg.send_self().action(), - server_msg.send_self().action(), - rooms_msg.send_self().action(), - ]); - }, - AddRoom(name, password) => { - let room_id = server.add_room();; - - let r = &mut server.rooms[room_id]; - let c = &mut server.clients[client_id]; - r.master_id = Some(c.id); - r.name = name; - r.password = password; - r.protocol_number = c.protocol_number; - - let actions = vec![ - RoomAdd(r.info(Some(&c))).send_all() - .with_protocol(r.protocol_number).action(), - MoveToRoom(room_id)]; - - server.react(client_id, actions); - }, - RemoveRoom(room_id) => { - let r = &mut server.rooms[room_id]; - let actions = vec![RoomRemove(r.name.clone()).send_all() - .with_protocol(r.protocol_number).action()]; - server.rooms.remove(room_id); - server.react(client_id, actions); - } - MoveToRoom(room_id) => { - let r = &mut server.rooms[room_id]; - let c = &mut server.clients[client_id]; - r.players_number += 1; - c.room_id = Some(room_id); - - let is_master = r.master_id == Some(c.id); - c.set_is_master(is_master); - c.set_is_ready(is_master); - c.set_is_joined_mid_game(false); - - if is_master { - r.ready_players_number += 1; - } - - let mut v = vec![ - RoomJoined(vec![c.nick.clone()]).send_all().in_room(room_id).action(), - ClientFlags("+i".to_string(), vec![c.nick.clone()]).send_all().action(), - SendRoomUpdate(None)]; - - if !r.greeting.is_empty() { - v.push(ChatMsg {nick: "[greeting]".to_string(), msg: r.greeting.clone()} - .send_self().action()); - } - - if !c.is_master() { - let team_names: Vec<_>; - if let Some(ref mut info) = r.game_info { - c.set_is_in_game(true); - c.set_is_joined_mid_game(true); - - { - let teams = info.client_teams(c.id); - c.teams_in_game = teams.clone().count() as u8; - c.clan = teams.clone().next().map(|t| t.color); - team_names = teams.map(|t| t.name.clone()).collect(); - } - - if !team_names.is_empty() { - info.left_teams.retain(|name| - !team_names.contains(&name)); - info.teams_in_game += team_names.len() as u8; - r.teams = info.teams_at_start.iter() - .filter(|(_, t)| !team_names.contains(&t.name)) - .cloned().collect(); - } - } else { - team_names = Vec::new(); - } - - v.push(SendRoomData{ to: client_id, teams: true, config: true, flags: true}); - - if let Some(ref info) = r.game_info { - v.push(RunGame.send_self().action()); - v.push(ClientFlags("+g".to_string(), vec![c.nick.clone()]) - .send_all().in_room(r.id).action()); - v.push(ForwardEngineMessage( - vec![to_engine_msg("e$spectate 1".bytes())]) - .send_self().action()); - v.push(ForwardEngineMessage(info.msg_log.clone()) - .send_self().action()); - - for name in &team_names { - v.push(ForwardEngineMessage( - vec![to_engine_msg(once(b'G').chain(name.bytes()))]) - .send_all().in_room(r.id).action()); - } - if info.is_paused { - v.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) - .send_all().in_room(r.id).action()) - } - } - } - server.react(client_id, v); - } - SendRoomData {to, teams, config, flags} => { - let mut actions = Vec::new(); - let room_id = server.clients[client_id].room_id; - if let Some(r) = room_id.and_then(|id| server.rooms.get(id)) { - if config { - actions.push(ConfigEntry("FULLMAPCONFIG".to_string(), r.map_config()) - .send(to).action()); - for cfg in r.game_config() { - actions.push(cfg.to_server_msg().send(to).action()); - } - } - if teams { - let current_teams = match r.game_info { - Some(ref info) => &info.teams_at_start, - None => &r.teams - }; - for (owner_id, team) in current_teams.iter() { - actions.push(TeamAdd(HWRoom::team_info(&server.clients[*owner_id], &team)) - .send(to).action()); - actions.push(TeamColor(team.name.clone(), team.color) - .send(to).action()); - actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number) - .send(to).action()); - } - } - if flags { - if let Some(id) = r.master_id { - actions.push(ClientFlags("+h".to_string(), vec![server.clients[id].nick.clone()]) - .send(to).action()); - } - let nicks: Vec<_> = server.clients.iter() - .filter(|(_, c)| c.room_id == Some(r.id) && c.is_ready()) - .map(|(_, c)| c.nick.clone()).collect(); - if !nicks.is_empty() { - actions.push(ClientFlags("+r".to_string(), nicks) - .send(to).action()); - } - } - } - server.react(client_id, actions); - } - AddVote{vote, is_forced} => { - let mut actions = Vec::new(); - if let Some(r) = server.room(client_id) { - let mut result = None; - if let Some(ref mut voting) = r.voting { - if is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) { - actions.push(server_chat("Your vote has been counted.".to_string()) - .send_self().action()); - voting.votes.push((client_id, vote)); - let i = voting.votes.iter(); - let pro = i.clone().filter(|(_, v)| *v).count(); - let contra = i.filter(|(_, v)| !*v).count(); - let success_quota = voting.voters.len() / 2 + 1; - if is_forced && vote || pro >= success_quota { - result = Some(true); - } else if is_forced && !vote || contra > voting.voters.len() - success_quota { - result = Some(false); - } - } else { - actions.push(server_chat("You already have voted.".to_string()) - .send_self().action()); - } - } else { - actions.push(server_chat("There's no voting going on.".to_string()) - .send_self().action()); - } - - if let Some(res) = result { - actions.push(server_chat("Voting closed.".to_string()) - .send_all().in_room(r.id).action()); - let voting = replace(&mut r.voting, None).unwrap(); - if res { - actions.push(ApplyVoting(voting.kind, r.id)); - } - } - } - - server.react(client_id, actions); - } - ApplyVoting(kind, room_id) => { - let mut actions = Vec::new(); - let mut id = client_id; - match kind { - VoteType::Kick(nick) => { - if let Some(c) = server.find_client(&nick) { - if c.room_id == Some(room_id) { - id = c.id; - actions.push(Kicked.send_self().action()); - actions.push(MoveToLobby("kicked".to_string())); - } - } - }, - VoteType::Map(None) => (), - VoteType::Map(Some(name)) => { - if let Some(location) = server.rooms[room_id].load_config(&name) { - actions.push(server_chat(location.to_string()) - .send_all().in_room(room_id).action()); - actions.push(SendRoomUpdate(None)); - for (_, c) in server.clients.iter() { - if c.room_id == Some(room_id) { - actions.push(SendRoomData{ - to: c.id, teams: false, - config: true, flags: false}) - } - } - } - }, - VoteType::Pause => { - if let Some(ref mut info) = server.rooms[room_id].game_info { - info.is_paused = !info.is_paused; - actions.push(server_chat("Pause toggled.".to_string()) - .send_all().in_room(room_id).action()); - actions.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) - .send_all().in_room(room_id).action()); - } - }, - VoteType::NewSeed => { - let seed = thread_rng().gen_range(0, 1_000_000_000).to_string(); - let cfg = GameCfg::Seed(seed); - actions.push(cfg.to_server_msg().send_all().in_room(room_id).action()); - server.rooms[room_id].set_config(cfg); - }, - VoteType::HedgehogsPerTeam(number) => { - let r = &mut server.rooms[room_id]; - let nicks = r.set_hedgehogs_number(number); - actions.extend(nicks.into_iter().map(|n| - HedgehogsNumber(n, number).send_all().in_room(room_id).action() - )); - }, - } - server.react(id, actions); - } - MoveToLobby(msg) => { - let mut actions = Vec::new(); - let lobby_id = server.lobby_id; - if let (c, Some(r)) = server.client_and_room(client_id) { - r.players_number -= 1; - if c.is_ready() && r.ready_players_number > 0 { - r.ready_players_number -= 1; - } - if c.is_master() && (r.players_number > 0 || r.is_fixed()) { - actions.push(ChangeMaster(r.id, None)); - } - actions.push(ClientFlags("-i".to_string(), vec![c.nick.clone()]) - .send_all().action()); - } - server.react(client_id, actions); - actions = Vec::new(); - - if let (c, Some(r)) = server.client_and_room(client_id) { - c.room_id = Some(lobby_id); - if r.players_number == 0 && !r.is_fixed() { - actions.push(RemoveRoom(r.id)); - } else { - actions.push(RemoveClientTeams); - actions.push(RoomLeft(c.nick.clone(), msg) - .send_all().in_room(r.id).but_self().action()); - actions.push(SendRoomUpdate(Some(r.name.clone()))); - } - } - server.react(client_id, actions) - } - ChangeMaster(room_id, new_id) => { - let mut actions = Vec::new(); - let room_client_ids = server.room_clients(room_id); - let new_id = if server.room(client_id).map(|r| r.is_fixed()).unwrap_or(false) { - new_id - } else { - new_id.or_else(|| - room_client_ids.iter().find(|id| **id != client_id).cloned()) - }; - let new_nick = new_id.map(|id| server.clients[id].nick.clone()); - - if let (c, Some(r)) = server.client_and_room(client_id) { - match r.master_id { - Some(id) if id == c.id => { - c.set_is_master(false); - r.master_id = None; - actions.push(ClientFlags("-h".to_string(), vec![c.nick.clone()]) - .send_all().in_room(r.id).action()); - } - Some(_) => unreachable!(), - None => {} - } - r.master_id = new_id; - if !r.is_fixed() && c.protocol_number < 42 { - r.name.replace_range(.., new_nick.as_ref().map_or("[]", String::as_str)); - } - r.set_join_restriction(false); - r.set_team_add_restriction(false); - let is_fixed = r.is_fixed(); - r.set_unregistered_players_restriction(is_fixed); - if let Some(nick) = new_nick { - actions.push(ClientFlags("+h".to_string(), vec![nick]) - .send_all().in_room(r.id).action()); - } - } - if let Some(id) = new_id { - server.clients[id].set_is_master(true) - } - server.react(client_id, actions); - } - RemoveTeam(name) => { - let mut actions = Vec::new(); - if let (c, Some(r)) = server.client_and_room(client_id) { - r.remove_team(&name); - if let Some(ref mut info) = r.game_info { - info.left_teams.push(name.clone()); - } - actions.push(TeamRemove(name.clone()).send_all().in_room(r.id).action()); - actions.push(SendRoomUpdate(None)); - if r.game_info.is_some() && c.is_in_game() { - actions.push(SendTeamRemovalMessage(name)); - } - } - server.react(client_id, actions); - }, - RemoveClientTeams => { - if let (c, Some(r)) = server.client_and_room(client_id) { - let actions = r.client_teams(c.id).map(|t| RemoveTeam(t.name.clone())).collect(); - server.react(client_id, actions); - } - } - SendRoomUpdate(old_name) => { - if let (c, Some(r)) = server.client_and_room(client_id) { - let name = old_name.unwrap_or_else(|| r.name.clone()); - let actions = vec![RoomUpdated(name, r.info(Some(&c))) - .send_all().with_protocol(r.protocol_number).action()]; - server.react(client_id, actions); - } - }, - StartRoomGame(room_id) => { - let actions = { - let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server.clients.iter() - .map(|(id, c)| (id, c.nick.clone())).unzip(); - let room = &mut server.rooms[room_id]; - - if !room.has_multiple_clans() { - vec![Warn("The game can't be started with less than two clans!".to_string())] - } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { - vec![Warn("Not all players are ready".to_string())] - } else if room.game_info.is_some() { - vec![Warn("The game is already in progress".to_string())] - } else { - room.start_round(); - for id in room_clients { - let c = &mut server.clients[id]; - c.set_is_in_game(false); - c.team_indices = room.client_team_indices(c.id); - } - vec![RunGame.send_all().in_room(room.id).action(), - SendRoomUpdate(None), - ClientFlags("+g".to_string(), room_nicks) - .send_all().in_room(room.id).action()] - } - }; - server.react(client_id, actions); - } - SendTeamRemovalMessage(team_name) => { - let mut actions = Vec::new(); - if let Some(r) = server.room(client_id) { - if let Some(ref mut info) = r.game_info { - let msg = once(b'F').chain(team_name.bytes()); - actions.push(ForwardEngineMessage(vec![to_engine_msg(msg)]). - send_all().in_room(r.id).but_self().action()); - info.teams_in_game -= 1; - if info.teams_in_game == 0 { - actions.push(FinishRoomGame(r.id)); - } - let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes())); - if let Some(m) = &info.sync_msg { - info.msg_log.push(m.clone()); - } - if info.sync_msg.is_some() { - info.sync_msg = None - } - info.msg_log.push(remove_msg.clone()); - actions.push(ForwardEngineMessage(vec![remove_msg]) - .send_all().in_room(r.id).but_self().action()); - } - } - server.react(client_id, actions); - } - FinishRoomGame(room_id) => { - let mut actions = Vec::new(); - - let r = &mut server.rooms[room_id]; - r.ready_players_number = 1; - actions.push(SendRoomUpdate(None)); - actions.push(RoundFinished.send_all().in_room(r.id).action()); - - if let Some(info) = replace(&mut r.game_info, None) { - for (_, c) in server.clients.iter() { - if c.room_id == Some(room_id) && c.is_joined_mid_game() { - actions.push(SendRoomData{ - to: c.id, teams: false, - config: true, flags: false}); - for name in &info.left_teams { - actions.push(TeamRemove(name.clone()) - .send(c.id).action()); - } - } - } - } - - let nicks: Vec<_> = server.clients.iter_mut() - .filter(|(_, c)| c.room_id == Some(room_id)) - .map(|(_, c)| { - c.set_is_ready(c.is_master()); - c.set_is_joined_mid_game(false); - c - }).filter_map(|c| if !c.is_master() { - Some(c.nick.clone()) - } else { - None - }).collect(); - - if !nicks.is_empty() { - let msg = if r.protocol_number < 38 { - LegacyReady(false, nicks) - } else { - ClientFlags("-r".to_string(), nicks) - }; - actions.push(msg.send_all().in_room(room_id).action()); - } - server.react(client_id, actions); - } - Warn(msg) => { - run_action(server, client_id, Warning(msg).send_self().action()); - } - ProtocolError(msg) => { - run_action(server, client_id, Error(msg).send_self().action()) - } - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/client.rs --- a/gameServer2/src/server/client.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -use super::coretypes::ClientId; -use bitflags::*; - -bitflags!{ - pub struct ClientFlags: u8 { - const IS_ADMIN = 0b0000_0001; - const IS_MASTER = 0b0000_0010; - const IS_READY = 0b0000_0100; - const IS_IN_GAME = 0b0000_1000; - const IS_JOINED_MID_GAME = 0b0001_0000; - const IS_CHECKER = 0b0010_0000; - - const NONE = 0b0000_0000; - const DEFAULT = Self::NONE.bits; - } -} - -pub struct HWClient { - pub id: ClientId, - pub room_id: Option, - pub nick: String, - pub web_password: String, - pub server_salt: String, - pub protocol_number: u16, - pub flags: ClientFlags, - pub teams_in_game: u8, - pub team_indices: Vec, - pub clan: Option -} - -impl HWClient { - pub fn new(id: ClientId, salt: String) -> HWClient { - HWClient { - id, - room_id: None, - nick: String::new(), - web_password: String::new(), - server_salt: salt, - protocol_number: 0, - flags: ClientFlags::DEFAULT, - teams_in_game: 0, - team_indices: Vec::new(), - clan: None, - } - } - - fn contains(& self, mask: ClientFlags) -> bool { - self.flags.contains(mask) - } - - fn set(&mut self, mask: ClientFlags, value: bool) { - self.flags.set(mask, value); - } - - pub fn is_admin(&self)-> bool { self.contains(ClientFlags::IS_ADMIN) } - pub fn is_master(&self)-> bool { self.contains(ClientFlags::IS_MASTER) } - pub fn is_ready(&self)-> bool { self.contains(ClientFlags::IS_READY) } - pub fn is_in_game(&self)-> bool { self.contains(ClientFlags::IS_IN_GAME) } - pub fn is_joined_mid_game(&self)-> bool { self.contains(ClientFlags::IS_JOINED_MID_GAME) } - pub fn is_checker(&self)-> bool { self.contains(ClientFlags::IS_CHECKER) } - - pub fn set_is_admin(&mut self, value: bool) { self.set(ClientFlags::IS_ADMIN, value) } - pub fn set_is_master(&mut self, value: bool) { self.set(ClientFlags::IS_MASTER, value) } - pub fn set_is_ready(&mut self, value: bool) { self.set(ClientFlags::IS_READY, value) } - pub fn set_is_in_game(&mut self, value: bool) { self.set(ClientFlags::IS_IN_GAME, value) } - pub fn set_is_joined_mid_game(&mut self, value: bool) { self.set(ClientFlags::IS_JOINED_MID_GAME, value) } - pub fn set_is_checker(&mut self, value: bool) { self.set(ClientFlags::IS_CHECKER, value) } -} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/core.rs --- a/gameServer2/src/server/core.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,158 +0,0 @@ -use slab; -use crate::utils; -use super::{ - io::HWServerIO, - client::HWClient, room::HWRoom, actions, handlers, - coretypes::{ClientId, RoomId}, - actions::{Destination, PendingMessage} -}; -use crate::protocol::messages::*; -use rand::{RngCore, thread_rng}; -use base64::{encode}; -use log::*; - -type Slab = slab::Slab; - -pub struct HWServer { - pub clients: Slab, - pub rooms: Slab, - pub lobby_id: RoomId, - pub output: Vec<(Vec, HWServerMessage)>, - pub removed_clients: Vec, - pub io: Box -} - -impl HWServer { - pub fn new(clients_limit: usize, rooms_limit: usize, io: Box) -> HWServer { - let rooms = Slab::with_capacity(rooms_limit); - let clients = Slab::with_capacity(clients_limit); - let mut server = HWServer { - clients, rooms, - lobby_id: 0, - output: vec![], - removed_clients: vec![], - io - }; - server.lobby_id = server.add_room(); - server - } - - pub fn add_client(&mut self) -> ClientId { - let key: ClientId; - { - let entry = self.clients.vacant_entry(); - key = entry.key(); - let mut salt = [0u8; 18]; - thread_rng().fill_bytes(&mut salt); - - let client = HWClient::new(entry.key(), encode(&salt)); - entry.insert(client); - } - self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION)); - key - } - - pub fn client_lost(&mut self, client_id: ClientId) { - actions::run_action(self, client_id, - actions::Action::ByeClient("Connection reset".to_string())); - } - - pub fn add_room(&mut self) -> RoomId { - let entry = self.rooms.vacant_entry(); - let key = entry.key(); - let room = HWRoom::new(entry.key()); - entry.insert(room); - key - } - - pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) { - debug!("Handling message {:?} for client {}", msg, client_id); - if self.clients.contains(client_id) { - handlers::handle(self, client_id, msg); - } - } - - fn get_recipients(&self, client_id: ClientId, destination: &Destination) -> Vec { - let mut ids = match *destination { - Destination::ToSelf => vec![client_id], - Destination::ToId(id) => vec![id], - Destination::ToAll {room_id: Some(id), ..} => - self.room_clients(id), - Destination::ToAll {protocol: Some(proto), ..} => - self.protocol_clients(proto), - Destination::ToAll {..} => - self.clients.iter().map(|(id, _)| id).collect::>() - }; - if let Destination::ToAll {skip_self: true, ..} = destination { - if let Some(index) = ids.iter().position(|id| *id == client_id) { - ids.remove(index); - } - } - ids - } - - pub fn send(&mut self, client_id: ClientId, destination: &Destination, message: HWServerMessage) { - let ids = self.get_recipients(client_id, &destination); - self.output.push((ids, message)); - } - - pub fn react(&mut self, client_id: ClientId, actions: Vec) { - for action in actions { - actions::run_action(self, client_id, action); - } - } - - pub fn lobby(&self) -> &HWRoom { &self.rooms[self.lobby_id] } - - pub fn has_room(&self, name: &str) -> bool { - self.rooms.iter().any(|(_, r)| r.name == name) - } - - pub fn find_room(&self, name: &str) -> Option<&HWRoom> { - self.rooms.iter().find_map(|(_, r)| Some(r).filter(|r| r.name == name)) - } - - pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HWRoom> { - self.rooms.iter_mut().find_map(|(_, r)| Some(r).filter(|r| r.name == name)) - } - - pub fn find_client(&self, nick: &str) -> Option<&HWClient> { - self.clients.iter().find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) - } - - pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> { - self.clients.iter_mut().find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) - } - - pub fn select_clients(&self, f: F) -> Vec - where F: Fn(&(usize, &HWClient)) -> bool { - self.clients.iter().filter(f) - .map(|(_, c)| c.id).collect() - } - - pub fn room_clients(&self, room_id: RoomId) -> Vec { - self.select_clients(|(_, c)| c.room_id == Some(room_id)) - } - - pub fn protocol_clients(&self, protocol: u16) -> Vec { - self.select_clients(|(_, c)| c.protocol_number == protocol) - } - - pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec { - let room_id = self.clients[self_id].room_id; - self.select_clients(|(id, c)| *id != self_id && c.room_id == room_id ) - } - - pub fn client_and_room(&mut self, client_id: ClientId) -> (&mut HWClient, Option<&mut HWRoom>) { - let c = &mut self.clients[client_id]; - if let Some(room_id) = c.room_id { - (c, Some(&mut self.rooms[room_id])) - } else { - (c, None) - } - } - - pub fn room(&mut self, client_id: ClientId) -> Option<&mut HWRoom> { - self.client_and_room(client_id).1 - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/coretypes.rs --- a/gameServer2/src/server/coretypes.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -pub type ClientId = usize; -pub type RoomId = usize; - -pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8; - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum ServerVar { - MOTDNew(String), - MOTDOld(String), - LatestProto(u32), -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum GameCfg { - FeatureSize(u32), - MapType(String), - MapGenerator(u32), - MazeSize(u32), - Seed(String), - Template(u32), - - Ammo(String, Option), - Scheme(String, Vec), - Script(String), - Theme(String), - DrawnMap(String) -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct TeamInfo { - pub name: String, - pub color: u8, - pub grave: String, - pub fort: String, - pub voice_pack: String, - pub flag: String, - pub difficulty: u8, - pub hedgehogs_number: u8, - pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize], -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct HedgehogInfo { - pub name: String, - pub hat: String, -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum VoteType { - Kick(String), - Map(Option), - Pause, - NewSeed, - HedgehogsPerTeam(u8) -} - -#[derive(Clone, Debug)] -pub struct Voting { - pub ttl: u32, - pub voters: Vec, - pub votes: Vec<(ClientId, bool)>, - pub kind: VoteType -} - -impl Voting { - pub fn new(kind: VoteType, voters: Vec) -> Voting { - Voting { - kind, voters, ttl: 2, - votes: Vec::new() - } - } -} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/handlers.rs --- a/gameServer2/src/server/handlers.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -use mio; -use std::{io, io::Write}; - -use super::{ - core::HWServer, - actions::{Action, Action::*}, - coretypes::ClientId -}; -use crate::{ - protocol::messages::{ - HWProtocolMessage, - HWServerMessage::* - } -}; -use log::*; - -mod loggingin; -mod lobby; -mod inroom; -mod common; -mod checker; - -pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) { - match message { - HWProtocolMessage::Ping => - server.react(client_id, vec![Pong.send_self().action()]), - HWProtocolMessage::Quit(Some(msg)) => - server.react(client_id, vec![ByeClient("User quit: ".to_string() + &msg)]), - HWProtocolMessage::Quit(None) => - server.react(client_id, vec![ByeClient("User quit".to_string())]), - HWProtocolMessage::Malformed => warn!("Malformed/unknown message"), - HWProtocolMessage::Empty => warn!("Empty message"), - _ => { - match server.clients[client_id].room_id { - None => - loggingin::handle(server, client_id, message), - Some(id) if id == server.lobby_id => - lobby::handle(server, client_id, message), - Some(id) => - inroom::handle(server, client_id, id, message) - } - }, - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/handlers/checker.rs --- a/gameServer2/src/server/handlers/checker.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -use mio; -use log::*; - -use crate::{ - server::{ - core::HWServer, - coretypes::ClientId, - }, - protocol::messages::{ - HWProtocolMessage - }, -}; - -pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) { - match message { - _ => warn!("Unknown command"), - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/handlers/common.rs --- a/gameServer2/src/server/handlers/common.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -use crate::{ - server::{actions::Action, core::HWServer}, - protocol::messages::{ - HWProtocolMessage::{self, Rnd}, HWServerMessage::{self, ChatMsg}, - } -}; -use rand::{self, Rng, thread_rng}; - -pub fn rnd_reply(options: &[String]) -> HWServerMessage { - let mut rng = thread_rng(); - let reply = if options.is_empty() { - (*rng.choose(&["heads", "tails"]).unwrap()).to_owned() - } else { - rng.choose(&options).unwrap().clone() - }; - - ChatMsg { - nick: "[random]".to_owned(), - msg: reply.clone(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::protocol::messages::HWServerMessage::ChatMsg; - use crate::server::actions::{ - Action::{self, Send}, PendingMessage, - }; - - fn reply2string(r: HWServerMessage) -> String { - match r { - ChatMsg { msg: p, .. } => String::from(p), - _ => panic!("expected a ChatMsg"), - } - } - - fn run_handle_test(opts: Vec) { - let opts2 = opts.clone(); - for opt in opts { - while reply2string(rnd_reply(&opts2)) != opt {} - } - } - - /// This test terminates almost surely. - #[test] - fn test_handle_rnd_empty() { - run_handle_test(vec![]) - } - - /// This test terminates almost surely. - #[test] - fn test_handle_rnd_nonempty() { - run_handle_test(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]) - } - - /// This test terminates almost surely (strong law of large numbers) - #[test] - fn test_distribution() { - let eps = 0.000001; - let lim = 0.5; - let opts = vec![0.to_string(), 1.to_string()]; - let mut ones = 0; - let mut tries = 0; - - while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps { - tries += 1; - if reply2string(rnd_reply(&opts)) == 1.to_string() { - ones += 1; - } - } - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/handlers/inroom.rs --- a/gameServer2/src/server/handlers/inroom.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,486 +0,0 @@ -use mio; - -use crate::{ - server::{ - coretypes::{ - ClientId, RoomId, Voting, VoteType, GameCfg, - MAX_HEDGEHOGS_PER_TEAM - }, - core::HWServer, - room::{HWRoom, RoomFlags}, - actions::{Action, Action::*} - }, - protocol::messages::{ - HWProtocolMessage, - HWServerMessage::*, - server_chat - }, - utils::is_name_illegal -}; -use std::{ - mem::swap -}; -use base64::{encode, decode}; -use super::common::rnd_reply; -use log::*; - -#[derive(Clone)] -struct ByMsg<'a> { - messages: &'a[u8] -} - -impl <'a> Iterator for ByMsg<'a> { - type Item = &'a[u8]; - - fn next(&mut self) -> Option<::Item> { - if let Some(size) = self.messages.get(0) { - let (msg, next) = self.messages.split_at(*size as usize + 1); - self.messages = next; - Some(msg) - } else { - None - } - } -} - -fn by_msg(source: &[u8]) -> ByMsg { - ByMsg {messages: source} -} - -const VALID_MESSAGES: &[u8] = - b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A"; -const NON_TIMED_MESSAGES: &[u8] = b"M#hb"; - -#[cfg(canhazslicepatterns)] -fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool { - match msg { - [size, typ, body..] => VALID_MESSAGES.contains(typ) - && match body { - [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => - team_indices.contains(team), - _ => *typ != b'h' - }, - _ => false - } -} - -fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool { - if let Some(typ) = msg.get(1) { - VALID_MESSAGES.contains(typ) - } else { - false - } -} - -fn is_msg_empty(msg: &[u8]) -> bool { - msg.get(1).filter(|t| **t == b'+').is_some() -} - -fn is_msg_timed(msg: &[u8]) -> bool { - msg.get(1).filter(|t| !NON_TIMED_MESSAGES.contains(t)).is_some() -} - -fn voting_description(kind: &VoteType) -> String { - format!("New voting started: {}", match kind { - VoteType::Kick(nick) => format!("kick {}", nick), - VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()), - VoteType::Pause => "pause".to_string(), - VoteType::NewSeed => "new seed".to_string(), - VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number) - }) -} - -fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags { - use crate::protocol::messages::HWProtocolMessage::*; - match msg { - ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN, - ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD, - ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, - _ => RoomFlags::empty() - } -} - -pub fn handle(server: &mut HWServer, client_id: ClientId, room_id: RoomId, message: HWProtocolMessage) { - use crate::protocol::messages::HWProtocolMessage::*; - match message { - Part(None) => server.react(client_id, vec![ - MoveToLobby("part".to_string())]), - Part(Some(msg)) => server.react(client_id, vec![ - MoveToLobby(format!("part: {}", msg))]), - Chat(msg) => { - let actions = { - let c = &mut server.clients[client_id]; - let chat_msg = ChatMsg {nick: c.nick.clone(), msg}; - vec![chat_msg.send_all().in_room(room_id).but_self().action()] - }; - server.react(client_id, actions); - }, - Fix => { - if let (c, Some(r)) = server.client_and_room(client_id) { - if c.is_admin() { r.set_is_fixed(true) } - } - } - Unfix => { - if let (c, Some(r)) = server.client_and_room(client_id) { - if c.is_admin() { r.set_is_fixed(false) } - } - } - Greeting(text) => { - if let (c, Some(r)) = server.client_and_room(client_id) { - if c.is_admin() || c.is_master() && !r.is_fixed() { - r.greeting = text - } - } - } - RoomName(new_name) => { - let actions = - if is_name_illegal(&new_name) { - vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] - } else if server.rooms[room_id].is_fixed() { - vec![Warn("Access denied.".to_string())] - } else if server.has_room(&new_name) { - vec![Warn("A room with the same name already exists.".to_string())] - } else { - let mut old_name = new_name.clone(); - swap(&mut server.rooms[room_id].name, &mut old_name); - vec![SendRoomUpdate(Some(old_name))] - }; - server.react(client_id, actions); - }, - ToggleReady => { - if let (c, Some(r)) = server.client_and_room(client_id) { - let flags = if c.is_ready() { - r.ready_players_number -= 1; - "-r" - } else { - r.ready_players_number += 1; - "+r" - }; - - let msg = if c.protocol_number < 38 { - LegacyReady(c.is_ready(), vec![c.nick.clone()]) - } else { - ClientFlags(flags.to_string(), vec![c.nick.clone()]) - }; - - let mut v = vec![msg.send_all().in_room(r.id).action()]; - - if r.is_fixed() && r.ready_players_number == r.players_number { - v.push(StartRoomGame(r.id)) - } - - c.set_is_ready(!c.is_ready()); - server.react(client_id, v); - } - } - AddTeam(info) => { - let mut actions = Vec::new(); - if let (c, Some(r)) = server.client_and_room(client_id) { - if r.teams.len() >= r.team_limit as usize { - actions.push(Warn("Too many teams!".to_string())) - } else if r.addable_hedgehogs() == 0 { - actions.push(Warn("Too many hedgehogs!".to_string())) - } else if r.find_team(|t| t.name == info.name) != None { - actions.push(Warn("There's already a team with same name in the list.".to_string())) - } else if r.game_info.is_some() { - actions.push(Warn("Joining not possible: Round is in progress.".to_string())) - } else if r.is_team_add_restricted() { - actions.push(Warn("This room currently does not allow adding new teams.".to_string())); - } else { - let team = r.add_team(c.id, *info, c.protocol_number < 42); - c.teams_in_game += 1; - c.clan = Some(team.color); - actions.push(TeamAccepted(team.name.clone()) - .send_self().action()); - actions.push(TeamAdd(HWRoom::team_info(&c, team)) - .send_all().in_room(room_id).but_self().action()); - actions.push(TeamColor(team.name.clone(), team.color) - .send_all().in_room(room_id).action()); - actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number) - .send_all().in_room(room_id).action()); - actions.push(SendRoomUpdate(None)); - } - } - server.react(client_id, actions); - }, - RemoveTeam(name) => { - let mut actions = Vec::new(); - if let (c, Some(r)) = server.client_and_room(client_id) { - match r.find_team_owner(&name) { - None => - actions.push(Warn("Error: The team you tried to remove does not exist.".to_string())), - Some((id, _)) if id != client_id => - actions.push(Warn("You can't remove a team you don't own.".to_string())), - Some((_, name)) => { - c.teams_in_game -= 1; - c.clan = r.find_team_color(c.id); - actions.push(Action::RemoveTeam(name.to_string())); - } - } - }; - server.react(client_id, actions); - }, - SetHedgehogsNumber(team_name, number) => { - if let (c, Some(r)) = server.client_and_room(client_id) { - let addable_hedgehogs = r.addable_hedgehogs(); - let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { - if !c.is_master() { - vec![ProtocolError("You're not the room master!".to_string())] - } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM - || number > addable_hedgehogs + team.hedgehogs_number { - vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number) - .send_self().action()] - } else { - team.hedgehogs_number = number; - vec![HedgehogsNumber(team.name.clone(), number) - .send_all().in_room(room_id).but_self().action()] - } - } else { - vec![(Warn("No such team.".to_string()))] - }; - server.react(client_id, actions); - } - }, - SetTeamColor(team_name, color) => { - if let (c, Some(r)) = server.client_and_room(client_id) { - let mut owner_id = None; - let actions = if let Some((owner, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { - if !c.is_master() { - vec![ProtocolError("You're not the room master!".to_string())] - } else if false { - Vec::new() - } else { - owner_id = Some(owner); - team.color = color; - vec![TeamColor(team.name.clone(), color) - .send_all().in_room(room_id).but_self().action()] - } - } else { - vec![(Warn("No such team.".to_string()))] - }; - - if let Some(id) = owner_id { - server.clients[id].clan = Some(color); - } - - server.react(client_id, actions); - }; - }, - Cfg(cfg) => { - if let (c, Some(r)) = server.client_and_room(client_id) { - let actions = if r.is_fixed() { - vec![Warn("Access denied.".to_string())] - } else if !c.is_master() { - vec![ProtocolError("You're not the room master!".to_string())] - } else { - let cfg = match cfg { - GameCfg::Scheme(name, mut values) => { - if c.protocol_number == 49 && values.len() >= 2 { - let mut s = "X".repeat(50); - s.push_str(&values.pop().unwrap()); - values.push(s); - } - GameCfg::Scheme(name, values) - } - cfg => cfg - }; - - let v = vec![cfg.to_server_msg() - .send_all().in_room(r.id).but_self().action()]; - r.set_config(cfg); - v - }; - server.react(client_id, actions); - } - } - Save(name, location) => { - let actions = vec![server_chat(format!("Room config saved as {}", name)) - .send_all().in_room(room_id).action()]; - server.rooms[room_id].save_config(name, location); - server.react(client_id, actions); - } - SaveRoom(filename) => { - if server.clients[client_id].is_admin() { - let actions = match server.rooms[room_id].get_saves() { - Ok(text) => match server.io.write_file(&filename, &text) { - Ok(_) => vec![server_chat("Room configs saved successfully.".to_string()) - .send_self().action()], - Err(e) => { - warn!("Error while writing the config file \"{}\": {}", filename, e); - vec![Warn("Unable to save the room configs.".to_string())] - } - } - Err(e) => { - warn!("Error while serializing the room configs: {}", e); - vec![Warn("Unable to serialize the room configs.".to_string())] - } - }; - server.react(client_id, actions); - } - } - LoadRoom(filename) => { - if server.clients[client_id].is_admin() { - let actions = match server.io.read_file(&filename) { - Ok(text) => match server.rooms[room_id].set_saves(&text) { - Ok(_) => vec![server_chat("Room configs loaded successfully.".to_string()) - .send_self().action()], - Err(e) => { - warn!("Error while deserializing the room configs: {}", e); - vec![Warn("Unable to deserialize the room configs.".to_string())] - } - } - Err(e) => { - warn!("Error while reading the config file \"{}\": {}", filename, e); - vec![Warn("Unable to load the room configs.".to_string())] - } - }; - server.react(client_id, actions); - } - } - Delete(name) => { - let actions = if !server.rooms[room_id].delete_config(&name) { - vec![Warn(format!("Save doesn't exist: {}", name))] - } else { - vec![server_chat(format!("Room config {} has been deleted", name)) - .send_all().in_room(room_id).action()] - }; - server.react(client_id, actions); - } - CallVote(None) => { - server.react(client_id, vec![ - server_chat("Available callvote commands: kick , map , pause, newseed, hedgehogs ".to_string()) - .send_self().action()]) - } - CallVote(Some(kind)) => { - let is_in_game = server.rooms[room_id].game_info.is_some(); - let error = match &kind { - VoteType::Kick(nick) => { - if server.find_client(&nick).filter(|c| c.room_id == Some(room_id)).is_some() { - None - } else { - Some("/callvote kick: No such user!".to_string()) - } - }, - VoteType::Map(None) => { - let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect(); - if names.is_empty() { - Some("/callvote map: No maps saved in this room!".to_string()) - } else { - Some(format!("Available maps: {}", names.join(", "))) - } - }, - VoteType::Map(Some(name)) => { - if server.rooms[room_id].saves.get(&name[..]).is_some() { - None - } else { - Some("/callvote map: No such map!".to_string()) - } - }, - VoteType::Pause => { - if is_in_game { - None - } else { - Some("/callvote pause: No game in progress!".to_string()) - } - }, - VoteType::NewSeed => { - None - }, - VoteType::HedgehogsPerTeam(number) => { - match number { - 1...MAX_HEDGEHOGS_PER_TEAM => None, - _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()) - } - }, - }; - match error { - None => { - let msg = voting_description(&kind); - let voting = Voting::new(kind, server.room_clients(client_id)); - server.rooms[room_id].voting = Some(voting); - server.react(client_id, vec![ - server_chat(msg).send_all().in_room(room_id).action(), - AddVote{ vote: true, is_forced: false}]); - } - Some(msg) => { - server.react(client_id, vec![ - server_chat(msg).send_self().action()]) - } - } - } - Vote(vote) => { - server.react(client_id, vec![AddVote{ vote, is_forced: false }]); - } - ForceVote(vote) => { - let is_forced = server.clients[client_id].is_admin(); - server.react(client_id, vec![AddVote{ vote, is_forced }]); - } - ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => { - if server.clients[client_id].is_master() { - server.rooms[room_id].flags.toggle(room_message_flag(&message)); - } - server.react(client_id, vec![SendRoomUpdate(None)]); - } - StartGame => { - server.react(client_id, vec![StartRoomGame(room_id)]); - } - EngineMessage(em) => { - let mut actions = Vec::new(); - if let (c, Some(r)) = server.client_and_room(client_id) { - if c.teams_in_game > 0 { - let decoding = decode(&em[..]).unwrap(); - let messages = by_msg(&decoding); - let valid = messages.filter(|m| is_msg_valid(m, &c.team_indices)); - let non_empty = valid.clone().filter(|m| !is_msg_empty(m)); - let sync_msg = valid.clone().filter(|m| is_msg_timed(m)) - .last().map(|m| if is_msg_empty(m) {Some(encode(m))} else {None}); - - let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::>()); - if !em_response.is_empty() { - actions.push(ForwardEngineMessage(vec![em_response]) - .send_all().in_room(r.id).but_self().action()); - } - let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::>()); - if let Some(ref mut info) = r.game_info { - if !em_log.is_empty() { - info.msg_log.push(em_log); - } - if let Some(msg) = sync_msg { - info.sync_msg = msg; - } - } - } - } - server.react(client_id, actions) - } - RoundFinished => { - let mut actions = Vec::new(); - if let (c, Some(r)) = server.client_and_room(client_id) { - if c.is_in_game() { - c.set_is_in_game(false); - actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]). - send_all().in_room(r.id).action()); - if r.game_info.is_some() { - for team in r.client_teams(c.id) { - actions.push(SendTeamRemovalMessage(team.name.clone())); - } - } - } - } - server.react(client_id, actions) - }, - Rnd(v) => { - let result = rnd_reply(&v); - let mut echo = vec!["/rnd".to_string()]; - echo.extend(v.into_iter()); - let chat_msg = ChatMsg { - nick: server.clients[client_id].nick.clone(), - msg: echo.join(" ") - }; - server.react(client_id, vec![ - chat_msg.send_all().in_room(room_id).action(), - result.send_all().in_room(room_id).action()]) - }, - _ => warn!("Unimplemented!") - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/handlers/lobby.rs --- a/gameServer2/src/server/handlers/lobby.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -use mio; - -use crate::{ - server::{ - core::HWServer, - coretypes::ClientId, - actions::{Action, Action::*} - }, - protocol::messages::{ - HWProtocolMessage, - HWServerMessage::* - }, - utils::is_name_illegal -}; -use super::common::rnd_reply; -use log::*; - -pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) { - use crate::protocol::messages::HWProtocolMessage::*; - match message { - CreateRoom(name, password) => { - let actions = - if is_name_illegal(&name) { - vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] - } else if server.has_room(&name) { - vec![Warn("A room with the same name already exists.".to_string())] - } else { - let flags_msg = ClientFlags( - "+hr".to_string(), - vec![server.clients[client_id].nick.clone()]); - vec![AddRoom(name, password), - flags_msg.send_self().action()] - }; - server.react(client_id, actions); - }, - Chat(msg) => { - let actions = vec![ChatMsg {nick: server.clients[client_id].nick.clone(), msg} - .send_all().in_room(server.lobby_id).but_self().action()]; - server.react(client_id, actions); - }, - JoinRoom(name, _password) => { - let room = server.rooms.iter().find(|(_, r)| r.name == name); - let room_id = room.map(|(_, r)| r.id); - let nicks = server.clients.iter() - .filter(|(_, c)| c.room_id == room_id) - .map(|(_, c)| c.nick.clone()) - .collect(); - let c = &mut server.clients[client_id]; - - let actions = if let Some((_, r)) = room { - if c.protocol_number != r.protocol_number { - vec![Warn("Room version incompatible to your Hedgewars version!".to_string())] - } else if r.is_join_restricted() { - vec![Warn("Access denied. This room currently doesn't allow joining.".to_string())] - } else if r.players_number == u8::max_value() { - vec![Warn("This room is already full".to_string())] - } else { - vec![MoveToRoom(r.id), - RoomJoined(nicks).send_self().action()] - } - } else { - vec![Warn("No such room.".to_string())] - }; - server.react(client_id, actions); - }, - Rnd(v) => { - server.react(client_id, vec![rnd_reply(&v).send_self().action()]); - }, - List => warn!("Deprecated LIST message received"), - _ => warn!("Incorrect command in lobby state"), - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/handlers/loggingin.rs --- a/gameServer2/src/server/handlers/loggingin.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -use mio; - -use crate::{ - server::{ - client::HWClient, - core::HWServer, - coretypes::ClientId, - actions::{Action, Action::*} - }, - protocol::messages::{ - HWProtocolMessage, HWServerMessage::* - }, - utils::is_name_illegal -}; -#[cfg(feature = "official-server")] -use openssl::sha::sha1; -use std::fmt::{Formatter, LowerHex}; -use log::*; - -#[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(client: &HWClient, salt1: &str, salt2: &str) -> Sha1Digest { - let s = format!("{}{}{}{}{}", salt1, salt2, - client.web_password, client.protocol_number, "!hedgewars"); - Sha1Digest(sha1(s.as_bytes())) -} - -pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) { - match message { - HWProtocolMessage::Nick(nick) => { - let client = &mut server.clients[client_id]; - debug!("{} {}", nick, is_name_illegal(&nick)); - let actions = if client.room_id != None { - unreachable!() - } - else if !client.nick.is_empty() { - vec![ProtocolError("Nickname already provided.".to_string())] - } - else if is_name_illegal(&nick) { - vec![ByeClient("Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] - } - else { - client.nick = nick.clone(); - vec![Nick(nick).send_self().action(), - CheckRegistered] - }; - - server.react(client_id, actions); - } - HWProtocolMessage::Proto(proto) => { - let client = &mut server.clients[client_id]; - let actions = if client.protocol_number != 0 { - vec![ProtocolError("Protocol already known.".to_string())] - } - else if proto == 0 { - vec![ProtocolError("Bad number.".to_string())] - } - else { - client.protocol_number = proto; - vec![Proto(proto).send_self().action(), - CheckRegistered] - }; - server.react(client_id, actions); - } - #[cfg(feature = "official-server")] - HWProtocolMessage::Password(hash, salt) => { - let c = &server.clients[client_id]; - - let client_hash = get_hash(c, &salt, &c.server_salt); - let server_hash = get_hash(c, &c.server_salt, &salt); - let actions = if client_hash == server_hash { - vec![ServerAuth(format!("{:x}", server_hash)).send_self().action(), - JoinLobby] - } else { - vec![ByeClient("Authentication failed".to_string())] - }; - server.react(client_id, actions); - } - #[cfg(feature = "official-server")] - HWProtocolMessage::Checker(protocol, nick, password) => { - let c = &mut server.clients[client_id]; - c.nick = nick; - c.web_password = password; - c.set_is_checker(true); - } - _ => warn!("Incorrect command in logging-in state"), - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/io.rs --- a/gameServer2/src/server/io.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -use std::{ - fs::{File, OpenOptions}, - io::{Read, Write, Result, Error, ErrorKind} -}; - -pub trait HWServerIO { - fn write_file(&mut self, name: &str, content: &str) -> Result<()>; - fn read_file(&mut self, name: &str) -> Result; -} - -pub struct EmptyServerIO {} - -impl EmptyServerIO { - pub fn new() -> Self { - Self {} - } -} - -impl HWServerIO for EmptyServerIO { - fn write_file(&mut self, _name: &str, _content: &str) -> Result<()> { - Ok(()) - } - - fn read_file(&mut self, _name: &str) -> 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) -> 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) - } -} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/network.rs --- a/gameServer2/src/server/network.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,444 +0,0 @@ -extern crate slab; - -use std::{ - io, io::{Error, ErrorKind, Read, Write}, - net::{SocketAddr, IpAddr, Ipv4Addr}, - collections::HashSet, - mem::{swap, replace} -}; - -use mio::{ - net::{TcpStream, TcpListener}, - Poll, PollOpt, Ready, Token -}; -use netbuf; -use slab::Slab; -use log::*; - -use crate::{ - utils, - protocol::{ProtocolDecoder, messages::*} -}; -use super::{ - io::FileServerIO, - core::{HWServer}, - coretypes::ClientId -}; -#[cfg(feature = "tls-connections")] -use openssl::{ - ssl::{ - SslMethod, SslContext, Ssl, SslContextBuilder, - SslVerifyMode, SslFiletype, SslOptions, - SslStreamBuilder, HandshakeError, MidHandshakeSslStream, SslStream - }, - error::ErrorStack -}; - -const MAX_BYTES_PER_READ: usize = 2048; - -#[derive(Hash, Eq, PartialEq, Copy, Clone)] -pub enum NetworkClientState { - Idle, - NeedsWrite, - NeedsRead, - Closed, -} - -type NetworkResult = io::Result<(T, NetworkClientState)>; - -#[cfg(not(feature = "tls-connections"))] -pub enum ClientSocket { - Plain(TcpStream) -} - -#[cfg(feature = "tls-connections")] -pub enum ClientSocket { - SslHandshake(Option>), - SslStream(SslStream) -} - -impl ClientSocket { - fn inner(&self) -> &TcpStream { - #[cfg(not(feature = "tls-connections"))] - match self { - ClientSocket::Plain(stream) => stream, - } - - #[cfg(feature = "tls-connections")] - match self { - ClientSocket::SslHandshake(Some(builder)) => builder.get_ref(), - ClientSocket::SslHandshake(None) => unreachable!(), - ClientSocket::SslStream(ssl_stream) => ssl_stream.get_ref() - } - } -} - -pub struct NetworkClient { - id: ClientId, - socket: ClientSocket, - peer_addr: SocketAddr, - decoder: ProtocolDecoder, - buf_out: netbuf::Buf -} - -impl NetworkClient { - pub fn new(id: ClientId, socket: ClientSocket, peer_addr: SocketAddr) -> NetworkClient { - NetworkClient { - id, socket, peer_addr, - decoder: ProtocolDecoder::new(), - buf_out: netbuf::Buf::new() - } - } - - #[cfg(feature = "tls-connections")] - fn handshake_impl(&mut self, handshake: MidHandshakeSslStream) -> io::Result { - match handshake.handshake() { - Ok(stream) => { - self.socket = ClientSocket::SslStream(stream); - debug!("TLS handshake with {} ({}) completed", self.id, self.peer_addr); - Ok(NetworkClientState::Idle) - } - Err(HandshakeError::WouldBlock(new_handshake)) => { - self.socket = ClientSocket::SslHandshake(Some(new_handshake)); - Ok(NetworkClientState::Idle) - } - Err(HandshakeError::Failure(new_handshake)) => { - self.socket = ClientSocket::SslHandshake(Some(new_handshake)); - debug!("TLS handshake with {} ({}) failed", self.id, self.peer_addr); - Err(Error::new(ErrorKind::Other, "Connection failure")) - } - Err(HandshakeError::SetupFailure(_)) => unreachable!() - } - } - - fn read_impl(decoder: &mut ProtocolDecoder, source: &mut R, - id: ClientId, addr: &SocketAddr) -> NetworkResult> { - let mut bytes_read = 0; - let result = loop { - match decoder.read_from(source) { - Ok(bytes) => { - debug!("Client {}: read {} bytes", id, bytes); - bytes_read += bytes; - if bytes == 0 { - let result = if bytes_read == 0 { - info!("EOF for client {} ({})", id, addr); - (Vec::new(), NetworkClientState::Closed) - } else { - (decoder.extract_messages(), NetworkClientState::NeedsRead) - }; - break Ok(result); - } - else if bytes_read >= MAX_BYTES_PER_READ { - break Ok((decoder.extract_messages(), NetworkClientState::NeedsRead)) - } - } - Err(ref error) if error.kind() == ErrorKind::WouldBlock => { - let messages = if bytes_read == 0 { - Vec::new() - } else { - decoder.extract_messages() - }; - break Ok((messages, NetworkClientState::Idle)); - } - Err(error) => - break Err(error) - } - }; - decoder.sweep(); - result - } - - pub fn read(&mut self) -> NetworkResult> { - #[cfg(not(feature = "tls-connections"))] - match self.socket { - ClientSocket::Plain(ref mut stream) => - NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr), - } - - #[cfg(feature = "tls-connections")] - match self.socket { - ClientSocket::SslHandshake(ref mut handshake_opt) => { - let handshake = std::mem::replace(handshake_opt, None).unwrap(); - Ok((Vec::new(), self.handshake_impl(handshake)?)) - }, - ClientSocket::SslStream(ref mut stream) => - NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr) - } - } - - fn write_impl(buf_out: &mut netbuf::Buf, destination: &mut W) -> NetworkResult<()> { - let result = loop { - match buf_out.write_to(destination) { - Ok(bytes) if buf_out.is_empty() || bytes == 0 => - break Ok(((), NetworkClientState::Idle)), - Ok(_) => (), - Err(ref error) if error.kind() == ErrorKind::Interrupted - || error.kind() == ErrorKind::WouldBlock => { - break Ok(((), NetworkClientState::NeedsWrite)); - }, - Err(error) => - break Err(error) - } - }; - result - } - - pub fn write(&mut self) -> NetworkResult<()> { - let result = { - #[cfg(not(feature = "tls-connections"))] - match self.socket { - ClientSocket::Plain(ref mut stream) => - NetworkClient::write_impl(&mut self.buf_out, stream) - } - - #[cfg(feature = "tls-connections")] { - match self.socket { - ClientSocket::SslHandshake(ref mut handshake_opt) => { - let handshake = std::mem::replace(handshake_opt, None).unwrap(); - Ok(((), self.handshake_impl(handshake)?)) - } - ClientSocket::SslStream(ref mut stream) => - NetworkClient::write_impl(&mut self.buf_out, stream) - } - } - }; - - self.socket.inner().flush()?; - result - } - - pub fn send_raw_msg(&mut self, msg: &[u8]) { - self.buf_out.write_all(msg).unwrap(); - } - - pub fn send_string(&mut self, msg: &str) { - self.send_raw_msg(&msg.as_bytes()); - } - - pub fn send_msg(&mut self, msg: &HWServerMessage) { - self.send_string(&msg.to_raw_protocol()); - } -} - -#[cfg(feature = "tls-connections")] -struct ServerSsl { - context: SslContext -} - -pub struct NetworkLayer { - listener: TcpListener, - server: HWServer, - clients: Slab, - pending: HashSet<(ClientId, NetworkClientState)>, - pending_cache: Vec<(ClientId, NetworkClientState)>, - #[cfg(feature = "tls-connections")] - ssl: ServerSsl -} - -impl NetworkLayer { - pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer { - let server = HWServer::new(clients_limit, rooms_limit, Box::new(FileServerIO::new())); - let clients = Slab::with_capacity(clients_limit); - let pending = HashSet::with_capacity(2 * clients_limit); - let pending_cache = Vec::with_capacity(2 * clients_limit); - - NetworkLayer { - listener, server, clients, pending, pending_cache, - #[cfg(feature = "tls-connections")] - ssl: NetworkLayer::create_ssl_context() - } - } - - #[cfg(feature = "tls-connections")] - fn create_ssl_context() -> ServerSsl { - let mut builder = SslContextBuilder::new(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - builder.set_read_ahead(true); - builder.set_certificate_file("ssl/cert.pem", SslFiletype::PEM).unwrap(); - builder.set_private_key_file("ssl/key.pem", SslFiletype::PEM).unwrap(); - builder.set_options(SslOptions::NO_COMPRESSION); - builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap(); - ServerSsl { context: builder.build() } - } - - pub fn register_server(&self, poll: &Poll) -> io::Result<()> { - poll.register(&self.listener, utils::SERVER, Ready::readable(), - PollOpt::edge()) - } - - fn deregister_client(&mut self, poll: &Poll, id: ClientId) { - let mut client_exists = false; - if let Some(ref client) = self.clients.get(id) { - poll.deregister(client.socket.inner()) - .expect("could not deregister socket"); - info!("client {} ({}) removed", client.id, client.peer_addr); - client_exists = true; - } - if client_exists { - self.clients.remove(id); - } - } - - fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: ClientSocket, addr: SocketAddr) { - poll.register(client_socket.inner(), Token(id), - Ready::readable() | Ready::writable(), - PollOpt::edge()) - .expect("could not register socket with event loop"); - - let entry = self.clients.vacant_entry(); - let client = NetworkClient::new(id, client_socket, addr); - info!("client {} ({}) added", client.id, client.peer_addr); - entry.insert(client); - } - - fn flush_server_messages(&mut self) { - debug!("{} pending server messages", self.server.output.len()); - for (clients, message) in self.server.output.drain(..) { - debug!("Message {:?} to {:?}", message, clients); - let msg_string = message.to_raw_protocol(); - for client_id in clients { - if let Some(client) = self.clients.get_mut(client_id) { - client.send_string(&msg_string); - self.pending.insert((client_id, NetworkClientState::NeedsWrite)); - } - } - } - } - - fn create_client_socket(&self, socket: TcpStream) -> io::Result { - #[cfg(not(feature = "tls-connections"))] { - Ok(ClientSocket::Plain(socket)) - } - - #[cfg(feature = "tls-connections")] { - let ssl = Ssl::new(&self.ssl.context).unwrap(); - let mut builder = SslStreamBuilder::new(ssl, socket); - builder.set_accept_state(); - match builder.handshake() { - Ok(stream) => - Ok(ClientSocket::SslStream(stream)), - Err(HandshakeError::WouldBlock(stream)) => - Ok(ClientSocket::SslHandshake(Some(stream))), - Err(e) => { - debug!("OpenSSL handshake failed: {}", e); - Err(Error::new(ErrorKind::Other, "Connection failure")) - } - } - } - } - - pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> { - let (client_socket, addr) = self.listener.accept()?; - info!("Connected: {}", addr); - - let client_id = self.server.add_client(); - self.register_client(poll, client_id, self.create_client_socket(client_socket)?, addr); - self.flush_server_messages(); - - Ok(()) - } - - fn operation_failed(&mut self, poll: &Poll, client_id: ClientId, error: &Error, msg: &str) -> io::Result<()> { - let addr = if let Some(ref mut client) = self.clients.get_mut(client_id) { - client.peer_addr - } else { - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0) - }; - debug!("{}({}): {}", msg, addr, error); - self.client_error(poll, client_id) - } - - pub fn client_readable(&mut self, poll: &Poll, - client_id: ClientId) -> io::Result<()> { - let messages = - if let Some(ref mut client) = self.clients.get_mut(client_id) { - client.read() - } else { - warn!("invalid readable client: {}", client_id); - Ok((Vec::new(), NetworkClientState::Idle)) - }; - - match messages { - Ok((messages, state)) => { - for message in messages { - self.server.handle_msg(client_id, message); - } - match state { - NetworkClientState::NeedsRead => { - self.pending.insert((client_id, state)); - }, - NetworkClientState::Closed => - self.client_error(&poll, client_id)?, - _ => {} - }; - } - Err(e) => self.operation_failed( - poll, client_id, &e, - "Error while reading from client socket")? - } - - self.flush_server_messages(); - - if !self.server.removed_clients.is_empty() { - let ids: Vec<_> = self.server.removed_clients.drain(..).collect(); - for client_id in ids { - self.deregister_client(poll, client_id); - } - } - - Ok(()) - } - - pub fn client_writable(&mut self, poll: &Poll, - client_id: ClientId) -> io::Result<()> { - let result = - if let Some(ref mut client) = self.clients.get_mut(client_id) { - client.write() - } else { - warn!("invalid writable client: {}", client_id); - Ok(((), NetworkClientState::Idle)) - }; - - match result { - Ok(((), state)) if state == NetworkClientState::NeedsWrite => { - self.pending.insert((client_id, state)); - }, - Ok(_) => {} - Err(e) => self.operation_failed( - poll, client_id, &e, - "Error while writing to client socket")? - } - - Ok(()) - } - - pub fn client_error(&mut self, poll: &Poll, - client_id: ClientId) -> io::Result<()> { - self.deregister_client(poll, client_id); - self.server.client_lost(client_id); - - Ok(()) - } - - pub fn has_pending_operations(&self) -> bool { - !self.pending.is_empty() - } - - pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> { - if self.has_pending_operations() { - let mut cache = replace(&mut self.pending_cache, Vec::new()); - cache.extend(self.pending.drain()); - for (id, state) in cache.drain(..) { - match state { - NetworkClientState::NeedsRead => - self.client_readable(poll, id)?, - NetworkClientState::NeedsWrite => - self.client_writable(poll, id)?, - _ => {} - } - } - swap(&mut cache, &mut self.pending_cache); - } - Ok(()) - } -} diff -r 6843c4551cde -r 06672690d71b gameServer2/src/server/room.rs --- a/gameServer2/src/server/room.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,391 +0,0 @@ -use std::{ - iter, collections::HashMap -}; -use crate::server::{ - coretypes::{ - ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting, - MAX_HEDGEHOGS_PER_TEAM - }, - client::{HWClient} -}; -use bitflags::*; -use serde::{Serialize, Deserialize}; -use serde_derive::{Serialize, Deserialize}; -use serde_yaml; - -const MAX_TEAMS_IN_ROOM: u8 = 8; -const MAX_HEDGEHOGS_IN_ROOM: u8 = - MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM; - -#[derive(Clone, Serialize, Deserialize)] -struct Ammo { - name: String, - settings: Option -} - -#[derive(Clone, Serialize, Deserialize)] -struct Scheme { - name: String, - settings: Vec -} - -#[derive(Clone, Serialize, Deserialize)] -struct RoomConfig { - feature_size: u32, - map_type: String, - map_generator: u32, - maze_size: u32, - seed: String, - template: u32, - - ammo: Ammo, - scheme: Scheme, - script: String, - theme: String, - drawn_map: Option -} - -impl RoomConfig { - fn new() -> RoomConfig { - RoomConfig { - feature_size: 12, - map_type: "+rnd+".to_string(), - map_generator: 0, - maze_size: 0, - seed: "seed".to_string(), - template: 0, - - ammo: Ammo {name: "Default".to_string(), settings: None }, - scheme: Scheme {name: "Default".to_string(), settings: Vec::new() }, - script: "Normal".to_string(), - theme: "\u{1f994}".to_string(), - drawn_map: None - } - } -} - -fn client_teams_impl(teams: &[(ClientId, TeamInfo)], client_id: ClientId) - -> impl Iterator + Clone -{ - teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t) -} - -fn map_config_from(c: &RoomConfig) -> Vec { - vec![c.feature_size.to_string(), c.map_type.to_string(), - c.map_generator.to_string(), c.maze_size.to_string(), - c.seed.to_string(), c.template.to_string()] -} - -fn game_config_from(c: &RoomConfig) -> Vec { - use crate::server::coretypes::GameCfg::*; - let mut v = vec![ - Ammo(c.ammo.name.to_string(), c.ammo.settings.clone()), - Scheme(c.scheme.name.to_string(), c.scheme.settings.clone()), - Script(c.script.to_string()), - Theme(c.theme.to_string())]; - if let Some(ref m) = c.drawn_map { - v.push(DrawnMap(m.to_string())) - } - v -} - -pub struct GameInfo { - pub teams_in_game: u8, - pub teams_at_start: Vec<(ClientId, TeamInfo)>, - pub left_teams: Vec, - pub msg_log: Vec, - pub sync_msg: Option, - pub is_paused: bool, - config: RoomConfig -} - -impl GameInfo { - fn new(teams: Vec<(ClientId, TeamInfo)>, config: RoomConfig) -> GameInfo { - GameInfo { - left_teams: Vec::new(), - msg_log: Vec::new(), - sync_msg: None, - is_paused: false, - teams_in_game: teams.len() as u8, - teams_at_start: teams, - config - } - } - - pub fn client_teams(&self, client_id: ClientId) -> impl Iterator + Clone { - client_teams_impl(&self.teams_at_start, client_id) - } -} - -#[derive(Serialize, Deserialize)] -pub struct RoomSave { - pub location: String, - config: RoomConfig -} - -bitflags!{ - pub struct RoomFlags: u8 { - const FIXED = 0b0000_0001; - const RESTRICTED_JOIN = 0b0000_0010; - const RESTRICTED_TEAM_ADD = 0b0000_0100; - const RESTRICTED_UNREGISTERED_PLAYERS = 0b0000_1000; - } -} - -pub struct HWRoom { - pub id: RoomId, - pub master_id: Option, - pub name: String, - pub password: Option, - pub greeting: String, - pub protocol_number: u16, - pub flags: RoomFlags, - - pub players_number: u8, - pub default_hedgehog_number: u8, - pub team_limit: u8, - pub ready_players_number: u8, - pub teams: Vec<(ClientId, TeamInfo)>, - config: RoomConfig, - pub voting: Option, - pub saves: HashMap, - pub game_info: Option -} - -impl HWRoom { - pub fn new(id: RoomId) -> HWRoom { - HWRoom { - id, - master_id: None, - name: String::new(), - password: None, - greeting: "".to_string(), - flags: RoomFlags::empty(), - protocol_number: 0, - players_number: 0, - default_hedgehog_number: 4, - team_limit: MAX_TEAMS_IN_ROOM, - ready_players_number: 0, - teams: Vec::new(), - config: RoomConfig::new(), - voting: None, - saves: HashMap::new(), - game_info: None - } - } - - pub fn hedgehogs_number(&self) -> u8 { - self.teams.iter().map(|(_, t)| t.hedgehogs_number).sum() - } - - pub fn addable_hedgehogs(&self) -> u8 { - MAX_HEDGEHOGS_IN_ROOM - self.hedgehogs_number() - } - - pub fn add_team(&mut self, owner_id: ClientId, mut team: TeamInfo, preserve_color: bool) -> &TeamInfo { - if !preserve_color { - team.color = iter::repeat(()).enumerate() - .map(|(i, _)| i as u8).take(u8::max_value() as usize + 1) - .find(|i| self.teams.iter().all(|(_, t)| t.color != *i)) - .unwrap_or(0u8) - }; - team.hedgehogs_number = if self.teams.is_empty() { - self.default_hedgehog_number - } else { - self.teams[0].1.hedgehogs_number.min(self.addable_hedgehogs()) - }; - self.teams.push((owner_id, team)); - &self.teams.last().unwrap().1 - } - - pub fn remove_team(&mut self, name: &str) { - if let Some(index) = self.teams.iter().position(|(_, t)| t.name == name) { - self.teams.remove(index); - } - } - - pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec { - let mut names = Vec::new(); - let teams = match self.game_info { - Some(ref mut info) => &mut info.teams_at_start, - None => &mut self.teams - }; - - if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM { - for (_, team) in teams.iter_mut() { - team.hedgehogs_number = n; - names.push(team.name.clone()) - }; - self.default_hedgehog_number = n; - } - names - } - - pub fn find_team_and_owner_mut(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)> - where F: Fn(&TeamInfo) -> bool { - self.teams.iter_mut().find(|(_, t)| f(t)).map(|(id, t)| (*id, t)) - } - - pub fn find_team(&self, f: F) -> Option<&TeamInfo> - where F: Fn(&TeamInfo) -> bool { - self.teams.iter().find_map(|(_, t)| Some(t).filter(|t| f(&t))) - } - - pub fn client_teams(&self, client_id: ClientId) -> impl Iterator { - client_teams_impl(&self.teams, client_id) - } - - pub fn client_team_indices(&self, client_id: ClientId) -> Vec { - self.teams.iter().enumerate() - .filter(move |(_, (id, _))| *id == client_id) - .map(|(i, _)| i as u8).collect() - } - - pub fn find_team_owner(&self, team_name: &str) -> Option<(ClientId, &str)> { - self.teams.iter().find(|(_, t)| t.name == team_name) - .map(|(id, t)| (*id, &t.name[..])) - } - - pub fn find_team_color(&self, owner_id: ClientId) -> Option { - self.client_teams(owner_id).nth(0).map(|t| t.color) - } - - pub fn has_multiple_clans(&self) -> bool { - self.teams.iter().min_by_key(|(_, t)| t.color) != - self.teams.iter().max_by_key(|(_, t)| t.color) - } - - pub fn set_config(&mut self, cfg: GameCfg) { - let c = &mut self.config; - match cfg { - FeatureSize(s) => c.feature_size = s, - MapType(t) => c.map_type = t, - MapGenerator(g) => c.map_generator = g, - MazeSize(s) => c.maze_size = s, - Seed(s) => c.seed = s, - Template(t) => c.template = t, - - Ammo(n, s) => c.ammo = Ammo {name: n, settings: s}, - Scheme(n, s) => c.scheme = Scheme {name: n, settings: s}, - Script(s) => c.script = s, - Theme(t) => c.theme = t, - DrawnMap(m) => c.drawn_map = Some(m) - }; - } - - pub fn start_round(&mut self) { - if self.game_info.is_none() { - self.game_info = Some(GameInfo::new( - self.teams.clone(), self.config.clone())); - } - } - - pub fn is_fixed(&self) -> bool { - self.flags.contains(RoomFlags::FIXED) - } - pub fn is_join_restricted(&self) -> bool { - self.flags.contains(RoomFlags::RESTRICTED_JOIN) - } - pub fn is_team_add_restricted(&self) -> bool { - self.flags.contains(RoomFlags::RESTRICTED_TEAM_ADD) - } - pub fn are_unregistered_players_restricted(&self) -> bool { - self.flags.contains(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS) - } - - pub fn set_is_fixed(&mut self, value: bool) { - self.flags.set(RoomFlags::FIXED, value) - } - pub fn set_join_restriction(&mut self, value: bool) { - self.flags.set(RoomFlags::RESTRICTED_JOIN, value) - } - pub fn set_team_add_restriction(&mut self, value: bool) { - self.flags.set(RoomFlags::RESTRICTED_TEAM_ADD, value) - } - pub fn set_unregistered_players_restriction(&mut self, value: bool) { - self.flags.set(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, value) - } - - fn flags_string(&self) -> String { - let mut result = "-".to_string(); - if self.game_info.is_some() { result += "g" } - if self.password.is_some() { result += "p" } - if self.is_join_restricted() { result += "j" } - if self.are_unregistered_players_restricted() { - result += "r" - } - result - } - - pub fn info(&self, master: Option<&HWClient>) -> Vec { - let c = &self.config; - vec![ - self.flags_string(), - self.name.clone(), - self.players_number.to_string(), - self.teams.len().to_string(), - master.map_or("[]", |c| &c.nick).to_string(), - c.map_type.to_string(), - c.script.to_string(), - c.scheme.name.to_string(), - c.ammo.name.to_string() - ] - } - - pub fn map_config(&self) -> Vec { - match self.game_info { - Some(ref info) => map_config_from(&info.config), - None => map_config_from(&self.config) - } - } - - pub fn game_config(&self) -> Vec { - match self.game_info { - Some(ref info) => game_config_from(&info.config), - None => game_config_from(&self.config) - } - } - - pub fn save_config(&mut self, name: String, location: String) { - self.saves.insert(name, RoomSave { location, config: self.config.clone() }); - } - - pub fn load_config(&mut self, name: &str) -> Option<&str> { - if let Some(save) = self.saves.get(name) { - self.config = save.config.clone(); - Some(&save.location[..]) - } else { - None - } - } - - pub fn delete_config(&mut self, name: &str) -> bool { - self.saves.remove(name).is_some() - } - - pub fn get_saves(&self) -> Result { - serde_yaml::to_string(&(&self.greeting, &self.saves)) - } - - pub fn set_saves(&mut self, text: &str) -> Result<(), serde_yaml::Error> { - serde_yaml::from_str::<(String, HashMap)>(text).map(|(greeting, saves)| { - self.greeting = greeting; - self.saves = saves; - }) - } - - pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec { - let mut info = vec![ - team.name.clone(), - team.grave.clone(), - team.fort.clone(), - team.voice_pack.clone(), - team.flag.clone(), - owner.nick.clone(), - team.difficulty.to_string()]; - let hogs = team.hedgehogs.iter().flat_map(|h| - iter::once(h.name.clone()).chain(iter::once(h.hat.clone()))); - info.extend(hogs); - info - } -} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b gameServer2/src/utils.rs --- a/gameServer2/src/utils.rs Mon Dec 10 21:23:52 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -use std::iter::Iterator; -use mio; -use base64::{encode}; - -pub const PROTOCOL_VERSION : u32 = 3; -pub const SERVER: mio::Token = mio::Token(1_000_000_000); - -pub fn is_name_illegal(name: &str ) -> bool{ - name.len() > 40 || - name.trim().is_empty() || - name.chars().any(|c| - "$()*+?[]^{|}\x7F".contains(c) || - '\x00' <= c && c <= '\x1F') -} - -pub fn to_engine_msg(msg: T) -> String - where T: Iterator + Clone -{ - let mut tmp = Vec::new(); - tmp.push(msg.clone().count() as u8); - tmp.extend(msg); - encode(&tmp) -} - -pub fn protocol_version_string(protocol_number: u16) -> &'static str { - match protocol_number { - 17 => "0.9.7-dev", - 19 => "0.9.7", - 20 => "0.9.8-dev", - 21 => "0.9.8", - 22 => "0.9.9-dev", - 23 => "0.9.9", - 24 => "0.9.10-dev", - 25 => "0.9.10", - 26 => "0.9.11-dev", - 27 => "0.9.11", - 28 => "0.9.12-dev", - 29 => "0.9.12", - 30 => "0.9.13-dev", - 31 => "0.9.13", - 32 => "0.9.14-dev", - 33 => "0.9.14", - 34 => "0.9.15-dev", - 35 => "0.9.14.1", - 37 => "0.9.15", - 38 => "0.9.16-dev", - 39 => "0.9.16", - 40 => "0.9.17-dev", - 41 => "0.9.17", - 42 => "0.9.18-dev", - 43 => "0.9.18", - 44 => "0.9.19-dev", - 45 => "0.9.19", - 46 => "0.9.20-dev", - 47 => "0.9.20", - 48 => "0.9.21-dev", - 49 => "0.9.21", - 50 => "0.9.22-dev", - 51 => "0.9.22", - 52 => "0.9.23-dev", - 53 => "0.9.23", - 54 => "0.9.24-dev", - 55 => "0.9.24", - 56 => "0.9.25-dev", - _ => "Unknown" - } -} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/Cargo.toml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/Cargo.toml Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,28 @@ +[package] +edition = "2018" +name = "hedgewars-server" +version = "0.0.1" +authors = [ "Andrey Korotaev " ] + +[features] +official-server = ["openssl"] +tls-connections = ["openssl"] +default = [] + +[dependencies] +rand = "0.5" +mio = "0.6" +slab = "0.4" +netbuf = "0.4" +nom = "4.1" +env_logger = "0.6" +log = "0.4" +base64 = "0.10" +bitflags = "1.0" +serde = "1.0" +serde_yaml = "0.8" +serde_derive = "1.0" +openssl = { version = "0.10", optional = true } + +[dev-dependencies] +proptest = "0.8" \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/main.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/main.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,62 @@ +#![allow(unused_imports)] +#![deny(bare_trait_objects)] + +//use std::io::*; +//use rand::Rng; +//use std::cmp::Ordering; +use mio::net::*; +use mio::*; +use log::*; + +mod utils; +mod server; +mod protocol; + +use crate::server::network::NetworkLayer; +use std::time::Duration; + +fn main() { + env_logger::init(); + + info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION); + + let address = "0.0.0.0:46631".parse().unwrap(); + let listener = TcpListener::bind(&address).unwrap(); + + let poll = Poll::new().unwrap(); + let mut hw_network = NetworkLayer::new(listener, 1024, 512); + hw_network.register_server(&poll).unwrap(); + + let mut events = Events::with_capacity(1024); + + loop { + let timeout = if hw_network.has_pending_operations() { + Some(Duration::from_millis(1)) + } else { + None + }; + poll.poll(&mut events, timeout).unwrap(); + + for event in events.iter() { + if event.readiness() & Ready::readable() == Ready::readable() { + match event.token() { + utils::SERVER => hw_network.accept_client(&poll).unwrap(), + Token(tok) => hw_network.client_readable(&poll, tok).unwrap(), + } + } + if event.readiness() & Ready::writable() == Ready::writable() { + match event.token() { + utils::SERVER => unreachable!(), + Token(tok) => hw_network.client_writable(&poll, tok).unwrap(), + } + } +// if event.kind().is_hup() || event.kind().is_error() { +// match event.token() { +// utils::SERVER => unreachable!(), +// Token(tok) => server.client_error(&poll, tok).unwrap(), +// } +// } + } + hw_network.on_idle(&poll).unwrap(); + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/protocol.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/protocol.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,47 @@ +use netbuf; +use std::{ + io::{Read, Result} +}; +use nom::{ + IResult, Err +}; + +pub mod messages; +#[cfg(test)] +pub mod test; +mod parser; + +pub struct ProtocolDecoder { + buf: netbuf::Buf, + consumed: usize, +} + +impl ProtocolDecoder { + pub fn new() -> ProtocolDecoder { + ProtocolDecoder { + buf: netbuf::Buf::new(), + consumed: 0, + } + } + + pub fn read_from(&mut self, stream: &mut R) -> Result { + self.buf.read_from(stream) + } + + pub fn extract_messages(&mut self) -> Vec { + let parse_result = parser::extract_messages(&self.buf[..]); + match parse_result { + Ok((tail, msgs)) => { + self.consumed = self.buf.len() - self.consumed - tail.len(); + msgs + }, + Err(Err::Incomplete(_)) => unreachable!(), + Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(), + } + } + + pub fn sweep(&mut self) { + self.buf.consume(self.consumed); + self.consumed = 0; + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/protocol/messages.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/protocol/messages.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,316 @@ +use crate::server::coretypes::{ + ServerVar, GameCfg, TeamInfo, + HedgehogInfo, VoteType +}; +use std::{ops, convert::From, iter::once}; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum HWProtocolMessage { + // core + Ping, + Pong, + Quit(Option), + //Cmd(String, Vec), + Global(String), + Watch(String), + ToggleServerRegisteredOnly, + SuperPower, + Info(String), + // not entered state + Nick(String), + Proto(u16), + Password(String, String), + Checker(u16, String, String), + // lobby + List, + Chat(String), + CreateRoom(String, Option), + JoinRoom(String, Option), + Follow(String), + Rnd(Vec), + Kick(String), + Ban(String, String, u32), + BanIP(String, String, u32), + BanNick(String, String, u32), + BanList, + Unban(String), + SetServerVar(ServerVar), + GetServerVar, + RestartServer, + Stats, + // in room + Part(Option), + Cfg(GameCfg), + AddTeam(Box), + RemoveTeam(String), + SetHedgehogsNumber(String, u8), + SetTeamColor(String, u8), + ToggleReady, + StartGame, + EngineMessage(String), + RoundFinished, + ToggleRestrictJoin, + ToggleRestrictTeams, + ToggleRegisteredOnly, + RoomName(String), + Delegate(String), + TeamChat(String), + MaxTeams(u8), + Fix, + Unfix, + Greeting(String), + CallVote(Option), + Vote(bool), + ForceVote(bool), + Save(String, String), + Delete(String), + SaveRoom(String), + LoadRoom(String), + Malformed, + Empty, +} + +#[derive(Debug)] +pub enum HWServerMessage { + Ping, + Pong, + Bye(String), + Nick(String), + Proto(u16), + ServerAuth(String), + LobbyLeft(String, String), + LobbyJoined(Vec), + ChatMsg {nick: String, msg: String}, + ClientFlags(String, Vec), + Rooms(Vec), + RoomAdd(Vec), + RoomJoined(Vec), + RoomLeft(String, String), + RoomRemove(String), + RoomUpdated(String, Vec), + TeamAdd(Vec), + TeamRemove(String), + TeamAccepted(String), + TeamColor(String, u8), + HedgehogsNumber(String, u8), + ConfigEntry(String, Vec), + Kicked, + RunGame, + ForwardEngineMessage(Vec), + RoundFinished, + + ServerMessage(String), + Notice(String), + Warning(String), + Error(String), + Connected(u32), + Unreachable, + + //Deprecated messages + LegacyReady(bool, Vec) +} + +pub fn server_chat(msg: String) -> HWServerMessage { + HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg } +} + +impl GameCfg { + pub fn to_protocol(&self) -> (String, Vec) { + use crate::server::coretypes::GameCfg::*; + match self { + FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]), + MapType(t) => ("MAP".to_string(), vec![t.to_string()]), + MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]), + MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]), + Seed(s) => ("SEED".to_string(), vec![s.to_string()]), + Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]), + + Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]), + Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]), + Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]), + Scheme(n, s) => ("SCHEME".to_string(), { + let mut v = vec![n.to_string()]; + v.extend(s.clone().into_iter()); + v + }), + Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]), + Theme(t) => ("THEME".to_string(), vec![t.to_string()]), + DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()]) + } + } + + pub fn to_server_msg(&self) -> HWServerMessage { + use self::HWServerMessage::ConfigEntry; + let (name, args) = self.to_protocol(); + HWServerMessage::ConfigEntry(name, args) + } +} + +macro_rules! const_braces { + ($e: expr) => { "{}\n" } +} + +macro_rules! msg { + [$($part: expr),*] => { + format!(concat!($(const_braces!($part)),*, "\n"), $($part),*); + }; +} + +#[cfg(test)] +macro_rules! several { + [$part: expr] => { once($part) }; + [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) }; +} + +impl HWProtocolMessage { + /** Converts the message to a raw `String`, which can be sent over the network. + * + * This is the inverse of the `message` parser. + */ + #[cfg(test)] + pub(crate) fn to_raw_protocol(&self) -> String { + use self::HWProtocolMessage::*; + match self { + Ping => msg!["PING"], + Pong => msg!["PONG"], + Quit(None) => msg!["QUIT"], + Quit(Some(msg)) => msg!["QUIT", msg], + Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)], + Watch(name) => msg!["CMD", format!("WATCH {}", name)], + ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"], + SuperPower => msg!["CMD", "SUPER_POWER"], + Info(info) => msg!["CMD", format!("INFO {}", info)], + Nick(nick) => msg!("NICK", nick), + Proto(version) => msg!["PROTO", version], + Password(p, s) => msg!["PASSWORD", p, s], + Checker(i, n, p) => msg!["CHECKER", i, n, p], + List => msg!["LIST"], + Chat(msg) => msg!["CHAT", msg], + CreateRoom(name, None) => msg!["CREATE_ROOM", name], + CreateRoom(name, Some(password)) => + msg!["CREATE_ROOM", name, password], + JoinRoom(name, None) => msg!["JOIN_ROOM", name], + JoinRoom(name, Some(password)) => + msg!["JOIN_ROOM", name, password], + Follow(name) => msg!["FOLLOW", name], + Rnd(args) => if args.is_empty() { + msg!["CMD", "RND"] + } else { + msg!["CMD", format!("RND {}", args.join(" "))] + }, + Kick(name) => msg!["KICK", name], + Ban(name, reason, time) => msg!["BAN", name, reason, time], + BanIP(ip, reason, time) => msg!["BAN_IP", ip, reason, time], + BanNick(nick, reason, time) => + msg!("BAN_NICK", nick, reason, time), + BanList => msg!["BANLIST"], + Unban(name) => msg!["UNBAN", name], + //SetServerVar(ServerVar), ??? + GetServerVar => msg!["GET_SERVER_VAR"], + RestartServer => msg!["CMD", "RESTART_SERVER YES"], + Stats => msg!["CMD", "STATS"], + Part(None) => msg!["PART"], + Part(Some(msg)) => msg!["PART", msg], + Cfg(config) => { + let (name, args) = config.to_protocol(); + msg!["CFG", name, args.join("\n")] + }, + AddTeam(info) => + msg!["ADD_TEAM", info.name, info.color, info.grave, info.fort, + info.voice_pack, info.flag, info.difficulty, + info.hedgehogs.iter() + .flat_map(|h| several![&h.name[..], &h.hat[..]]) + .collect::>().join("\n")], + RemoveTeam(name) => msg!["REMOVE_TEAM", name], + SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number], + SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color], + ToggleReady => msg!["TOGGLE_READY"], + StartGame => msg!["START_GAME"], + EngineMessage(msg) => msg!["EM", msg], + RoundFinished => msg!["ROUNDFINISHED"], + ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"], + ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"], + ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"], + RoomName(name) => msg!["ROOM_NAME", name], + Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)], + TeamChat(msg) => msg!["TEAMCHAT", msg], + MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)] , + Fix => msg!["CMD", "FIX"], + Unfix => msg!["CMD", "UNFIX"], + Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)], + //CallVote(Option<(String, Option)>) =>, ?? + Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg {"YES"} else {"NO"})], + ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg {"YES"} else {"NO"})], + Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)], + Delete(name) => msg!["CMD", format!("DELETE {}", name)], + SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)], + LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)], + Malformed => msg!["A", "QUICK", "BROWN", "HOG", "JUMPS", "OVER", "THE", "LAZY", "DOG"], + Empty => msg![""], + _ => panic!("Protocol message not yet implemented") + } + } +} + +fn construct_message(header: &[&str], msg: &[String]) -> String { + let mut v: Vec<_> = header.iter().cloned().collect(); + v.extend(msg.iter().map(|s| &s[..])); + v.push("\n"); + v.join("\n") +} + +impl HWServerMessage { + pub fn to_raw_protocol(&self) -> String { + use self::HWServerMessage::*; + match self { + Ping => msg!["PING"], + Pong => msg!["PONG"], + Connected(protocol_version) => msg![ + "CONNECTED", + "Hedgewars server https://www.hedgewars.org/", + protocol_version], + Bye(msg) => msg!["BYE", msg], + Nick(nick) => msg!["NICK", nick], + Proto(proto) => msg!["PROTO", proto], + ServerAuth(hash) => msg!["SERVER_AUTH", hash], + LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg], + LobbyJoined(nicks) => + construct_message(&["LOBBY:JOINED"], &nicks), + ClientFlags(flags, nicks) => + construct_message(&["CLIENT_FLAGS", flags], &nicks), + Rooms(info) => + construct_message(&["ROOMS"], &info), + RoomAdd(info) => + construct_message(&["ROOM", "ADD"], &info), + RoomJoined(nicks) => + construct_message(&["JOINED"], &nicks), + RoomLeft(nick, msg) => msg!["LEFT", nick, msg], + RoomRemove(name) => msg!["ROOM", "DEL", name], + RoomUpdated(name, info) => + construct_message(&["ROOM", "UPD", name], &info), + TeamAdd(info) => + construct_message(&["ADD_TEAM"], &info), + TeamRemove(name) => msg!["REMOVE_TEAM", name], + TeamAccepted(name) => msg!["TEAM_ACCEPTED", name], + TeamColor(name, color) => msg!["TEAM_COLOR", name, color], + HedgehogsNumber(name, number) => msg!["HH_NUM", name, number], + ConfigEntry(name, values) => + construct_message(&["CFG", name], &values), + Kicked => msg!["KICKED"], + RunGame => msg!["RUN_GAME"], + ForwardEngineMessage(em) => + construct_message(&["EM"], &em), + RoundFinished => msg!["ROUND_FINISHED"], + ChatMsg {nick, msg} => msg!["CHAT", nick, msg], + ServerMessage(msg) => msg!["SERVER_MESSAGE", msg], + Notice(msg) => msg!["NOTICE", msg], + Warning(msg) => msg!["WARNING", msg], + Error(msg) => msg!["ERROR", msg], + + LegacyReady(is_ready, nicks) => + construct_message(&[if *is_ready {"READY"} else {"NOT_READY"}], &nicks), + + _ => msg!["ERROR", "UNIMPLEMENTED"], + } + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/protocol/parser.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/protocol/parser.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,284 @@ +/** The parsers for the chat and multiplayer protocol. The main parser is `message`. + * # Protocol + * All messages consist of `\n`-separated strings. The end of a message is + * indicated by a double newline - `\n\n`. + * + * For example, a nullary command like PING will be actually sent as `PING\n\n`. + * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`. + */ + +use nom::*; + +use std::{ + str, str::FromStr, + ops::Range +}; +use super::{ + messages::{HWProtocolMessage, HWProtocolMessage::*} +}; +#[cfg(test)] +use { + super::test::gen_proto_msg, + proptest::{proptest, proptest_helper} +}; +use crate::server::coretypes::{ + HedgehogInfo, TeamInfo, GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM +}; + +named!(end_of_message, tag!("\n\n")); +named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); +named!( a_line<&[u8], String>, map!(str_line, String::from)); +named!(cmd_arg<&[u8], String>, + map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from)); +named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str)); +named!(u16_line<&[u8], u16>, map_res!(str_line, FromStr::from_str)); +named!(u32_line<&[u8], u32>, map_res!(str_line, FromStr::from_str)); +named!(yes_no_line<&[u8], bool>, alt!( + do_parse!(tag_no_case!("YES") >> (true)) + | do_parse!(tag_no_case!("NO") >> (false)))); +named!(opt_param<&[u8], Option >, alt!( + do_parse!(peek!(tag!("\n\n")) >> (None)) + | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string()))))); +named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" "))); +named!(opt_space_param<&[u8], Option >, alt!( + do_parse!(peek!(tag!("\n\n")) >> (None)) + | do_parse!(spaces >> s: str_line >> (Some(s.to_string()))))); +named!(hog_line<&[u8], HedgehogInfo>, + do_parse!(name: str_line >> eol >> hat: str_line >> + (HedgehogInfo{name: name.to_string(), hat: hat.to_string()}))); +named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>, + do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >> + h3: hog_line >> eol >> h4: hog_line >> eol >> + h5: hog_line >> eol >> h6: hog_line >> eol >> + h7: hog_line >> eol >> h8: hog_line >> + ([h1, h2, h3, h4, h5, h6, h7, h8]))); +named!(voting<&[u8], VoteType>, alt!( + do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >> + (VoteType::Kick(n))) + | do_parse!(tag_no_case!("MAP") >> + n: opt!(preceded!(spaces, a_line)) >> + (VoteType::Map(n))) + | do_parse!(tag_no_case!("PAUSE") >> + (VoteType::Pause)) + | do_parse!(tag_no_case!("NEWSEED") >> + (VoteType::NewSeed)) + | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >> + (VoteType::HedgehogsPerTeam(n))))); + +/** Recognizes messages which do not take any parameters */ +named!(basic_message<&[u8], HWProtocolMessage>, alt!( + do_parse!(tag!("PING") >> (Ping)) + | do_parse!(tag!("PONG") >> (Pong)) + | do_parse!(tag!("LIST") >> (List)) + | do_parse!(tag!("BANLIST") >> (BanList)) + | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar)) + | do_parse!(tag!("TOGGLE_READY") >> (ToggleReady)) + | do_parse!(tag!("START_GAME") >> (StartGame)) + | do_parse!(tag!("ROUNDFINISHED") >> _m: opt_param >> (RoundFinished)) + | do_parse!(tag!("TOGGLE_RESTRICT_JOINS") >> (ToggleRestrictJoin)) + | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS") >> (ToggleRestrictTeams)) + | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly)) +)); + +/** Recognizes messages which take exactly one parameter */ +named!(one_param_message<&[u8], HWProtocolMessage>, alt!( + do_parse!(tag!("NICK") >> eol >> n: a_line >> (Nick(n))) + | do_parse!(tag!("INFO") >> eol >> n: a_line >> (Info(n))) + | do_parse!(tag!("CHAT") >> eol >> m: a_line >> (Chat(m))) + | do_parse!(tag!("PART") >> msg: opt_param >> (Part(msg))) + | do_parse!(tag!("FOLLOW") >> eol >> n: a_line >> (Follow(n))) + | do_parse!(tag!("KICK") >> eol >> n: a_line >> (Kick(n))) + | do_parse!(tag!("UNBAN") >> eol >> n: a_line >> (Unban(n))) + | do_parse!(tag!("EM") >> eol >> m: a_line >> (EngineMessage(m))) + | do_parse!(tag!("TEAMCHAT") >> eol >> m: a_line >> (TeamChat(m))) + | do_parse!(tag!("ROOM_NAME") >> eol >> n: a_line >> (RoomName(n))) + | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n))) + + | do_parse!(tag!("PROTO") >> eol >> d: u16_line >> (Proto(d))) + + | do_parse!(tag!("QUIT") >> msg: opt_param >> (Quit(msg))) +)); + +/** Recognizes messages preceded with CMD */ +named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!( + do_parse!(tag_no_case!("STATS") >> (Stats)) + | do_parse!(tag_no_case!("FIX") >> (Fix)) + | do_parse!(tag_no_case!("UNFIX") >> (Unfix)) + | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer)) + | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly)) + | do_parse!(tag_no_case!("SUPER_POWER") >> (SuperPower)) + | do_parse!(tag_no_case!("PART") >> m: opt_space_param >> (Part(m))) + | do_parse!(tag_no_case!("QUIT") >> m: opt_space_param >> (Quit(m))) + | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line >> (Delegate(n))) + | do_parse!(tag_no_case!("SAVE") >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l))) + | do_parse!(tag_no_case!("DELETE") >> spaces >> n: a_line >> (Delete(n))) + | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line >> (SaveRoom(r))) + | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line >> (LoadRoom(r))) + | do_parse!(tag_no_case!("GLOBAL") >> spaces >> m: a_line >> (Global(m))) + | do_parse!(tag_no_case!("WATCH") >> spaces >> i: a_line >> (Watch(i))) + | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line >> (Greeting(m))) + | do_parse!(tag_no_case!("VOTE") >> spaces >> m: yes_no_line >> (Vote(m))) + | do_parse!(tag_no_case!("FORCE") >> spaces >> m: yes_no_line >> (ForceVote(m))) + | do_parse!(tag_no_case!("INFO") >> spaces >> n: a_line >> (Info(n))) + | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n))) + | do_parse!(tag_no_case!("CALLVOTE") >> + v: opt!(preceded!(spaces, voting)) >> (CallVote(v))) + | do_parse!( + tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >> + v: str_line >> + (Rnd(v.split_whitespace().map(String::from).collect()))) +))); + +named!(complex_message<&[u8], HWProtocolMessage>, alt!( + do_parse!(tag!("PASSWORD") >> eol >> + p: a_line >> eol >> + s: a_line >> + (Password(p, s))) + | do_parse!(tag!("CHECKER") >> eol >> + i: u16_line >> eol >> + n: a_line >> eol >> + p: a_line >> + (Checker(i, n, p))) + | do_parse!(tag!("CREATE_ROOM") >> eol >> + n: a_line >> + p: opt_param >> + (CreateRoom(n, p))) + | do_parse!(tag!("JOIN_ROOM") >> eol >> + n: a_line >> + p: opt_param >> + (JoinRoom(n, p))) + | do_parse!(tag!("ADD_TEAM") >> eol >> + name: a_line >> eol >> + color: u8_line >> eol >> + grave: a_line >> eol >> + fort: a_line >> eol >> + voice_pack: a_line >> eol >> + flag: a_line >> eol >> + difficulty: u8_line >> eol >> + hedgehogs: _8_hogs >> + (AddTeam(Box::new(TeamInfo{ + name, color, grave, fort, + voice_pack, flag, difficulty, + hedgehogs, hedgehogs_number: 0 + })))) + | do_parse!(tag!("HH_NUM") >> eol >> + n: a_line >> eol >> + c: u8_line >> + (SetHedgehogsNumber(n, c))) + | do_parse!(tag!("TEAM_COLOR") >> eol >> + n: a_line >> eol >> + c: u8_line >> + (SetTeamColor(n, c))) + | do_parse!(tag!("BAN") >> eol >> + n: a_line >> eol >> + r: a_line >> eol >> + t: u32_line >> + (Ban(n, r, t))) + | do_parse!(tag!("BAN_IP") >> eol >> + n: a_line >> eol >> + r: a_line >> eol >> + t: u32_line >> + (BanIP(n, r, t))) + | do_parse!(tag!("BAN_NICK") >> eol >> + n: a_line >> eol >> + r: a_line >> eol >> + t: u32_line >> + (BanNick(n, r, t))) +)); + +named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!( + do_parse!(tag!("THEME") >> eol >> + name: a_line >> + (GameCfg::Theme(name))) + | do_parse!(tag!("SCRIPT") >> eol >> + name: a_line >> + (GameCfg::Script(name))) + | do_parse!(tag!("AMMO") >> eol >> + name: a_line >> + value: opt_param >> + (GameCfg::Ammo(name, value))) + | do_parse!(tag!("SCHEME") >> eol >> + name: a_line >> + values: opt!(preceded!(eol, separated_list!(eol, a_line))) >> + (GameCfg::Scheme(name, values.unwrap_or_default()))) + | do_parse!(tag!("FEATURE_SIZE") >> eol >> + value: u32_line >> + (GameCfg::FeatureSize(value))) + | do_parse!(tag!("MAP") >> eol >> + value: a_line >> + (GameCfg::MapType(value))) + | do_parse!(tag!("MAPGEN") >> eol >> + value: u32_line >> + (GameCfg::MapGenerator(value))) + | do_parse!(tag!("MAZE_SIZE") >> eol >> + value: u32_line >> + (GameCfg::MazeSize(value))) + | do_parse!(tag!("SEED") >> eol >> + value: a_line >> + (GameCfg::Seed(value))) + | do_parse!(tag!("TEMPLATE") >> eol >> + value: u32_line >> + (GameCfg::Template(value))) + | do_parse!(tag!("DRAWNMAP") >> eol >> + value: a_line >> + (GameCfg::DrawnMap(value))) +), Cfg))); + +named!(malformed_message<&[u8], HWProtocolMessage>, + do_parse!(separated_list!(eol, a_line) >> (Malformed))); + +named!(empty_message<&[u8], HWProtocolMessage>, + do_parse!(alt!(end_of_message | eol) >> (Empty))); + +named!(message<&[u8], HWProtocolMessage>, alt!(terminated!( + alt!( + basic_message + | one_param_message + | cmd_message + | complex_message + | cfg_message + ), end_of_message + ) + | terminated!(malformed_message, end_of_message) + | empty_message + ) +); + +named!(pub extract_messages<&[u8], Vec >, many0!(complete!(message))); + +#[cfg(test)] +proptest! { + #[test] + fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { + println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); + assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone()))) + } +} + +#[test] +fn parse_test() { + assert_eq!(message(b"PING\n\n"), Ok((&b""[..], Ping))); + assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame))); + assert_eq!(message(b"NICK\nit's me\n\n"), Ok((&b""[..], Nick("it's me".to_string())))); + assert_eq!(message(b"PROTO\n51\n\n"), Ok((&b""[..], Proto(51)))); + assert_eq!(message(b"QUIT\nbye-bye\n\n"), Ok((&b""[..], Quit(Some("bye-bye".to_string()))))); + assert_eq!(message(b"QUIT\n\n"), Ok((&b""[..], Quit(None)))); + assert_eq!(message(b"CMD\nwatch demo\n\n"), Ok((&b""[..], Watch("demo".to_string())))); + assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77)))); + + assert_eq!(message(b"CMD\nPART\n\n"), Ok((&b""[..], Part(None)))); + assert_eq!(message(b"CMD\nPART _msg_\n\n"), Ok((&b""[..], Part(Some("_msg_".to_string()))))); + + assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![])))); + assert_eq!( + message(b"CMD\nRND A B\n\n"), + Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")]))) + ); + + assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), Ok((&b""[..], vec![Malformed]))); + + assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), Ok((&b"P"[..], vec![Ping, Ping]))); + assert_eq!(extract_messages(b"SING\n\nPING\n\n"), Ok((&b""[..], vec![Malformed, Ping]))); + assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), Ok((&b""[..], vec![Empty, Empty, Ping]))); + assert_eq!(extract_messages(b"\n\n\nPING\n\n"), Ok((&b""[..], vec![Empty, Empty, Ping]))); +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/protocol/test.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/protocol/test.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,168 @@ +use proptest::{ + test_runner::{TestRunner, Reason}, + arbitrary::{any, any_with, Arbitrary, StrategyFor}, + strategy::{Strategy, BoxedStrategy, Just, Map} +}; + +use crate::server::coretypes::{GameCfg, TeamInfo, HedgehogInfo}; + +use super::messages::{ + HWProtocolMessage, HWProtocolMessage::* +}; + +// Due to inability to define From between Options +trait Into2: Sized { fn into2(self) -> T; } +impl Into2 for T { fn into2(self) -> T { self } } +impl Into2> for Vec { + fn into2(self) -> Vec { + self.into_iter().map(|x| x.0).collect() + } +} +impl Into2 for Ascii { fn into2(self) -> String { self.0 } } +impl Into2> for Option{ + fn into2(self) -> Option { self.map(|x| {x.0}) } +} + +macro_rules! proto_msg_case { + ($val: ident()) => + (Just($val)); + ($val: ident($arg: ty)) => + (any::<$arg>().prop_map(|v| {$val(v.into2())})); + ($val: ident($arg1: ty, $arg2: ty)) => + (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())})); + ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => + (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())})); +} + +macro_rules! proto_msg_match { + ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => ( + match $var { + $($num => (proto_msg_case!($constr $res)).boxed()),*, + _ => Just($default).boxed() + } + ) +} + +/// Wrapper type for generating non-empty strings +#[derive(Debug)] +struct Ascii(String); + +impl Arbitrary for Ascii { + type Parameters = ::Parameters; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + "[a-zA-Z0-9]+".prop_map(Ascii).boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for GameCfg { + type Parameters = (); + + fn arbitrary_with(_args: ::Parameters) -> ::Strategy { + use crate::server::coretypes::GameCfg::*; + (0..10).no_shrink().prop_flat_map(|i| { + proto_msg_match!(i, def = FeatureSize(0), + 0 => FeatureSize(u32), + 1 => MapType(Ascii), + 2 => MapGenerator(u32), + 3 => MazeSize(u32), + 4 => Seed(Ascii), + 5 => Template(u32), + 6 => Ammo(Ascii, Option), + 7 => Scheme(Ascii, Vec), + 8 => Script(Ascii), + 9 => Theme(Ascii), + 10 => DrawnMap(Ascii)) + }).boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for TeamInfo { + type Parameters = (); + + fn arbitrary_with(_args: ::Parameters) -> ::Strategy { + ("[a-z]+", 0u8..127u8, "[a-z]+", "[a-z]+", "[a-z]+", "[a-z]+", 0u8..127u8) + .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| { + fn hog(n: u8) -> HedgehogInfo { + HedgehogInfo { name: format!("hog{}", n), hat: format!("hat{}", n)} + } + let hedgehogs = [hog(1), hog(2), hog(3), hog(4), hog(5), hog(6), hog(7), hog(8)]; + TeamInfo { + name, color, grave, fort, + voice_pack, flag,difficulty, + hedgehogs, hedgehogs_number: 0 + } + }).boxed() + } + + type Strategy = BoxedStrategy; +} + +pub fn gen_proto_msg() -> BoxedStrategy where { + let res = (0..58).no_shrink().prop_flat_map(|i| { + proto_msg_match!(i, def = Malformed, + 0 => Ping(), + 1 => Pong(), + 2 => Quit(Option), + //3 => Cmd + 4 => Global(Ascii), + 5 => Watch(Ascii), + 6 => ToggleServerRegisteredOnly(), + 7 => SuperPower(), + 8 => Info(Ascii), + 9 => Nick(Ascii), + 10 => Proto(u16), + 11 => Password(Ascii, Ascii), + 12 => Checker(u16, Ascii, Ascii), + 13 => List(), + 14 => Chat(Ascii), + 15 => CreateRoom(Ascii, Option), + 16 => JoinRoom(Ascii, Option), + 17 => Follow(Ascii), + 18 => Rnd(Vec), + 19 => Kick(Ascii), + 20 => Ban(Ascii, Ascii, u32), + 21 => BanIP(Ascii, Ascii, u32), + 22 => BanNick(Ascii, Ascii, u32), + 23 => BanList(), + 24 => Unban(Ascii), + //25 => SetServerVar(ServerVar), + 26 => GetServerVar(), + 27 => RestartServer(), + 28 => Stats(), + 29 => Part(Option), + 30 => Cfg(GameCfg), + 31 => AddTeam(Box), + 32 => RemoveTeam(Ascii), + 33 => SetHedgehogsNumber(Ascii, u8), + 34 => SetTeamColor(Ascii, u8), + 35 => ToggleReady(), + 36 => StartGame(), + 37 => EngineMessage(Ascii), + 38 => RoundFinished(), + 39 => ToggleRestrictJoin(), + 40 => ToggleRestrictTeams(), + 41 => ToggleRegisteredOnly(), + 42 => RoomName(Ascii), + 43 => Delegate(Ascii), + 44 => TeamChat(Ascii), + 45 => MaxTeams(u8), + 46 => Fix(), + 47 => Unfix(), + 48 => Greeting(Ascii), + //49 => CallVote(Option<(String, Option)>), + 50 => Vote(bool), + 51 => ForceVote(bool), + 52 => Save(Ascii, Ascii), + 53 => Delete(Ascii), + 54 => SaveRoom(Ascii), + 55 => LoadRoom(Ascii), + 56 => Malformed(), + 57 => Empty() + )}); + res.boxed() +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,8 @@ +pub mod core; +pub mod client; +pub mod io; +pub mod room; +pub mod network; +pub mod coretypes; +mod actions; +mod handlers; diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/actions.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/actions.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,631 @@ +use std::{ + io, io::Write, + iter::once, + mem::replace +}; +use super::{ + core::HWServer, + room::{GameInfo, RoomFlags}, + client::HWClient, + coretypes::{ClientId, RoomId, GameCfg, VoteType}, + room::HWRoom, + handlers +}; +use crate::{ + protocol::messages::{ + HWProtocolMessage, + HWServerMessage, + HWServerMessage::*, + server_chat + }, + utils::to_engine_msg +}; +use rand::{thread_rng, Rng, distributions::Uniform}; + +pub enum Destination { + ToId(ClientId), + ToSelf, + ToAll { + room_id: Option, + protocol: Option, + skip_self: bool + } +} + +pub struct PendingMessage { + pub destination: Destination, + pub message: HWServerMessage +} + +impl PendingMessage { + pub fn send(message: HWServerMessage, client_id: ClientId) -> PendingMessage { + PendingMessage{ destination: Destination::ToId(client_id), message} + } + + pub fn send_self(message: HWServerMessage) -> PendingMessage { + PendingMessage{ destination: Destination::ToSelf, message } + } + + pub fn send_all(message: HWServerMessage) -> PendingMessage { + let destination = Destination::ToAll { + room_id: None, + protocol: None, + skip_self: false, + }; + PendingMessage{ destination, message } + } + + pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage { + if let Destination::ToAll {ref mut room_id, ..} = self.destination { + *room_id = Some(clients_room_id) + } + self + } + + pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage { + if let Destination::ToAll {ref mut protocol, ..} = self.destination { + *protocol = Some(protocol_number) + } + self + } + + pub fn but_self(mut self) -> PendingMessage { + if let Destination::ToAll {ref mut skip_self, ..} = self.destination { + *skip_self = true + } + self + } + + pub fn action(self) -> Action { Send(self) } +} + +impl Into for PendingMessage { + fn into(self) -> Action { self.action() } +} + +impl HWServerMessage { + pub fn send(self, client_id: ClientId) -> PendingMessage { PendingMessage::send(self, client_id) } + pub fn send_self(self) -> PendingMessage { PendingMessage::send_self(self) } + pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) } +} + +pub enum Action { + Send(PendingMessage), + RemoveClient, + ByeClient(String), + ReactProtocolMessage(HWProtocolMessage), + CheckRegistered, + JoinLobby, + AddRoom(String, Option), + RemoveRoom(RoomId), + MoveToRoom(RoomId), + MoveToLobby(String), + ChangeMaster(RoomId, Option), + RemoveTeam(String), + RemoveClientTeams, + SendRoomUpdate(Option), + StartRoomGame(RoomId), + SendTeamRemovalMessage(String), + FinishRoomGame(RoomId), + SendRoomData{to: ClientId, teams: bool, config: bool, flags: bool}, + AddVote{vote: bool, is_forced: bool}, + ApplyVoting(VoteType, RoomId), + Warn(String), + ProtocolError(String) +} + +use self::Action::*; + +pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) { + match action { + Send(msg) => server.send(client_id, &msg.destination, msg.message), + ByeClient(msg) => { + let c = &server.clients[client_id]; + let nick = c.nick.clone(); + + if let Some(id) = c.room_id{ + if id != server.lobby_id { + server.react(client_id, vec![ + MoveToLobby(format!("quit: {}", msg.clone()))]); + } + } + + server.react(client_id, vec![ + LobbyLeft(nick, msg.clone()).send_all().action(), + Bye(msg).send_self().action(), + RemoveClient]); + }, + RemoveClient => { + server.removed_clients.push(client_id); + if server.clients.contains(client_id) { + server.clients.remove(client_id); + } + }, + ReactProtocolMessage(msg) => + handlers::handle(server, client_id, msg), + CheckRegistered => { + let client = &server.clients[client_id]; + if client.protocol_number > 0 && client.nick != "" { + let has_nick_clash = server.clients.iter().any( + |(id, c)| id != client_id && c.nick == client.nick); + + let actions = if !client.is_checker() && has_nick_clash { + if client.protocol_number < 38 { + vec![ByeClient("Nickname is already in use".to_string())] + } else { + server.clients[client_id].nick.clear(); + vec![Notice("NickAlreadyInUse".to_string()).send_self().action()] + } + } else { + vec![JoinLobby] + }; + server.react(client_id, actions); + } + }, + JoinLobby => { + server.clients[client_id].room_id = Some(server.lobby_id); + + let mut lobby_nicks = Vec::new(); + for (_, c) in server.clients.iter() { + if c.room_id.is_some() { + lobby_nicks.push(c.nick.clone()); + } + } + let joined_msg = LobbyJoined(lobby_nicks); + + let everyone_msg = LobbyJoined(vec![server.clients[client_id].nick.clone()]); + let flags_msg = ClientFlags( + "+i".to_string(), + server.clients.iter() + .filter(|(_, c)| c.room_id.is_some()) + .map(|(_, c)| c.nick.clone()) + .collect()); + let server_msg = ServerMessage("\u{1f994} is watching".to_string()); + let rooms_msg = Rooms(server.rooms.iter() + .filter(|(id, _)| *id != server.lobby_id) + .flat_map(|(_, r)| + r.info(r.master_id.map(|id| &server.clients[id]))) + .collect()); + server.react(client_id, vec![ + everyone_msg.send_all().but_self().action(), + joined_msg.send_self().action(), + flags_msg.send_self().action(), + server_msg.send_self().action(), + rooms_msg.send_self().action(), + ]); + }, + AddRoom(name, password) => { + let room_id = server.add_room();; + + let r = &mut server.rooms[room_id]; + let c = &mut server.clients[client_id]; + r.master_id = Some(c.id); + r.name = name; + r.password = password; + r.protocol_number = c.protocol_number; + + let actions = vec![ + RoomAdd(r.info(Some(&c))).send_all() + .with_protocol(r.protocol_number).action(), + MoveToRoom(room_id)]; + + server.react(client_id, actions); + }, + RemoveRoom(room_id) => { + let r = &mut server.rooms[room_id]; + let actions = vec![RoomRemove(r.name.clone()).send_all() + .with_protocol(r.protocol_number).action()]; + server.rooms.remove(room_id); + server.react(client_id, actions); + } + MoveToRoom(room_id) => { + let r = &mut server.rooms[room_id]; + let c = &mut server.clients[client_id]; + r.players_number += 1; + c.room_id = Some(room_id); + + let is_master = r.master_id == Some(c.id); + c.set_is_master(is_master); + c.set_is_ready(is_master); + c.set_is_joined_mid_game(false); + + if is_master { + r.ready_players_number += 1; + } + + let mut v = vec![ + RoomJoined(vec![c.nick.clone()]).send_all().in_room(room_id).action(), + ClientFlags("+i".to_string(), vec![c.nick.clone()]).send_all().action(), + SendRoomUpdate(None)]; + + if !r.greeting.is_empty() { + v.push(ChatMsg {nick: "[greeting]".to_string(), msg: r.greeting.clone()} + .send_self().action()); + } + + if !c.is_master() { + let team_names: Vec<_>; + if let Some(ref mut info) = r.game_info { + c.set_is_in_game(true); + c.set_is_joined_mid_game(true); + + { + let teams = info.client_teams(c.id); + c.teams_in_game = teams.clone().count() as u8; + c.clan = teams.clone().next().map(|t| t.color); + team_names = teams.map(|t| t.name.clone()).collect(); + } + + if !team_names.is_empty() { + info.left_teams.retain(|name| + !team_names.contains(&name)); + info.teams_in_game += team_names.len() as u8; + r.teams = info.teams_at_start.iter() + .filter(|(_, t)| !team_names.contains(&t.name)) + .cloned().collect(); + } + } else { + team_names = Vec::new(); + } + + v.push(SendRoomData{ to: client_id, teams: true, config: true, flags: true}); + + if let Some(ref info) = r.game_info { + v.push(RunGame.send_self().action()); + v.push(ClientFlags("+g".to_string(), vec![c.nick.clone()]) + .send_all().in_room(r.id).action()); + v.push(ForwardEngineMessage( + vec![to_engine_msg("e$spectate 1".bytes())]) + .send_self().action()); + v.push(ForwardEngineMessage(info.msg_log.clone()) + .send_self().action()); + + for name in &team_names { + v.push(ForwardEngineMessage( + vec![to_engine_msg(once(b'G').chain(name.bytes()))]) + .send_all().in_room(r.id).action()); + } + if info.is_paused { + v.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) + .send_all().in_room(r.id).action()) + } + } + } + server.react(client_id, v); + } + SendRoomData {to, teams, config, flags} => { + let mut actions = Vec::new(); + let room_id = server.clients[client_id].room_id; + if let Some(r) = room_id.and_then(|id| server.rooms.get(id)) { + if config { + actions.push(ConfigEntry("FULLMAPCONFIG".to_string(), r.map_config()) + .send(to).action()); + for cfg in r.game_config() { + actions.push(cfg.to_server_msg().send(to).action()); + } + } + if teams { + let current_teams = match r.game_info { + Some(ref info) => &info.teams_at_start, + None => &r.teams + }; + for (owner_id, team) in current_teams.iter() { + actions.push(TeamAdd(HWRoom::team_info(&server.clients[*owner_id], &team)) + .send(to).action()); + actions.push(TeamColor(team.name.clone(), team.color) + .send(to).action()); + actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number) + .send(to).action()); + } + } + if flags { + if let Some(id) = r.master_id { + actions.push(ClientFlags("+h".to_string(), vec![server.clients[id].nick.clone()]) + .send(to).action()); + } + let nicks: Vec<_> = server.clients.iter() + .filter(|(_, c)| c.room_id == Some(r.id) && c.is_ready()) + .map(|(_, c)| c.nick.clone()).collect(); + if !nicks.is_empty() { + actions.push(ClientFlags("+r".to_string(), nicks) + .send(to).action()); + } + } + } + server.react(client_id, actions); + } + AddVote{vote, is_forced} => { + let mut actions = Vec::new(); + if let Some(r) = server.room(client_id) { + let mut result = None; + if let Some(ref mut voting) = r.voting { + if is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) { + actions.push(server_chat("Your vote has been counted.".to_string()) + .send_self().action()); + voting.votes.push((client_id, vote)); + let i = voting.votes.iter(); + let pro = i.clone().filter(|(_, v)| *v).count(); + let contra = i.filter(|(_, v)| !*v).count(); + let success_quota = voting.voters.len() / 2 + 1; + if is_forced && vote || pro >= success_quota { + result = Some(true); + } else if is_forced && !vote || contra > voting.voters.len() - success_quota { + result = Some(false); + } + } else { + actions.push(server_chat("You already have voted.".to_string()) + .send_self().action()); + } + } else { + actions.push(server_chat("There's no voting going on.".to_string()) + .send_self().action()); + } + + if let Some(res) = result { + actions.push(server_chat("Voting closed.".to_string()) + .send_all().in_room(r.id).action()); + let voting = replace(&mut r.voting, None).unwrap(); + if res { + actions.push(ApplyVoting(voting.kind, r.id)); + } + } + } + + server.react(client_id, actions); + } + ApplyVoting(kind, room_id) => { + let mut actions = Vec::new(); + let mut id = client_id; + match kind { + VoteType::Kick(nick) => { + if let Some(c) = server.find_client(&nick) { + if c.room_id == Some(room_id) { + id = c.id; + actions.push(Kicked.send_self().action()); + actions.push(MoveToLobby("kicked".to_string())); + } + } + }, + VoteType::Map(None) => (), + VoteType::Map(Some(name)) => { + if let Some(location) = server.rooms[room_id].load_config(&name) { + actions.push(server_chat(location.to_string()) + .send_all().in_room(room_id).action()); + actions.push(SendRoomUpdate(None)); + for (_, c) in server.clients.iter() { + if c.room_id == Some(room_id) { + actions.push(SendRoomData{ + to: c.id, teams: false, + config: true, flags: false}) + } + } + } + }, + VoteType::Pause => { + if let Some(ref mut info) = server.rooms[room_id].game_info { + info.is_paused = !info.is_paused; + actions.push(server_chat("Pause toggled.".to_string()) + .send_all().in_room(room_id).action()); + actions.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) + .send_all().in_room(room_id).action()); + } + }, + VoteType::NewSeed => { + let seed = thread_rng().gen_range(0, 1_000_000_000).to_string(); + let cfg = GameCfg::Seed(seed); + actions.push(cfg.to_server_msg().send_all().in_room(room_id).action()); + server.rooms[room_id].set_config(cfg); + }, + VoteType::HedgehogsPerTeam(number) => { + let r = &mut server.rooms[room_id]; + let nicks = r.set_hedgehogs_number(number); + actions.extend(nicks.into_iter().map(|n| + HedgehogsNumber(n, number).send_all().in_room(room_id).action() + )); + }, + } + server.react(id, actions); + } + MoveToLobby(msg) => { + let mut actions = Vec::new(); + let lobby_id = server.lobby_id; + if let (c, Some(r)) = server.client_and_room(client_id) { + r.players_number -= 1; + if c.is_ready() && r.ready_players_number > 0 { + r.ready_players_number -= 1; + } + if c.is_master() && (r.players_number > 0 || r.is_fixed()) { + actions.push(ChangeMaster(r.id, None)); + } + actions.push(ClientFlags("-i".to_string(), vec![c.nick.clone()]) + .send_all().action()); + } + server.react(client_id, actions); + actions = Vec::new(); + + if let (c, Some(r)) = server.client_and_room(client_id) { + c.room_id = Some(lobby_id); + if r.players_number == 0 && !r.is_fixed() { + actions.push(RemoveRoom(r.id)); + } else { + actions.push(RemoveClientTeams); + actions.push(RoomLeft(c.nick.clone(), msg) + .send_all().in_room(r.id).but_self().action()); + actions.push(SendRoomUpdate(Some(r.name.clone()))); + } + } + server.react(client_id, actions) + } + ChangeMaster(room_id, new_id) => { + let mut actions = Vec::new(); + let room_client_ids = server.room_clients(room_id); + let new_id = if server.room(client_id).map(|r| r.is_fixed()).unwrap_or(false) { + new_id + } else { + new_id.or_else(|| + room_client_ids.iter().find(|id| **id != client_id).cloned()) + }; + let new_nick = new_id.map(|id| server.clients[id].nick.clone()); + + if let (c, Some(r)) = server.client_and_room(client_id) { + match r.master_id { + Some(id) if id == c.id => { + c.set_is_master(false); + r.master_id = None; + actions.push(ClientFlags("-h".to_string(), vec![c.nick.clone()]) + .send_all().in_room(r.id).action()); + } + Some(_) => unreachable!(), + None => {} + } + r.master_id = new_id; + if !r.is_fixed() && c.protocol_number < 42 { + r.name.replace_range(.., new_nick.as_ref().map_or("[]", String::as_str)); + } + r.set_join_restriction(false); + r.set_team_add_restriction(false); + let is_fixed = r.is_fixed(); + r.set_unregistered_players_restriction(is_fixed); + if let Some(nick) = new_nick { + actions.push(ClientFlags("+h".to_string(), vec![nick]) + .send_all().in_room(r.id).action()); + } + } + if let Some(id) = new_id { + server.clients[id].set_is_master(true) + } + server.react(client_id, actions); + } + RemoveTeam(name) => { + let mut actions = Vec::new(); + if let (c, Some(r)) = server.client_and_room(client_id) { + r.remove_team(&name); + if let Some(ref mut info) = r.game_info { + info.left_teams.push(name.clone()); + } + actions.push(TeamRemove(name.clone()).send_all().in_room(r.id).action()); + actions.push(SendRoomUpdate(None)); + if r.game_info.is_some() && c.is_in_game() { + actions.push(SendTeamRemovalMessage(name)); + } + } + server.react(client_id, actions); + }, + RemoveClientTeams => { + if let (c, Some(r)) = server.client_and_room(client_id) { + let actions = r.client_teams(c.id).map(|t| RemoveTeam(t.name.clone())).collect(); + server.react(client_id, actions); + } + } + SendRoomUpdate(old_name) => { + if let (c, Some(r)) = server.client_and_room(client_id) { + let name = old_name.unwrap_or_else(|| r.name.clone()); + let actions = vec![RoomUpdated(name, r.info(Some(&c))) + .send_all().with_protocol(r.protocol_number).action()]; + server.react(client_id, actions); + } + }, + StartRoomGame(room_id) => { + let actions = { + let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server.clients.iter() + .map(|(id, c)| (id, c.nick.clone())).unzip(); + let room = &mut server.rooms[room_id]; + + if !room.has_multiple_clans() { + vec![Warn("The game can't be started with less than two clans!".to_string())] + } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { + vec![Warn("Not all players are ready".to_string())] + } else if room.game_info.is_some() { + vec![Warn("The game is already in progress".to_string())] + } else { + room.start_round(); + for id in room_clients { + let c = &mut server.clients[id]; + c.set_is_in_game(false); + c.team_indices = room.client_team_indices(c.id); + } + vec![RunGame.send_all().in_room(room.id).action(), + SendRoomUpdate(None), + ClientFlags("+g".to_string(), room_nicks) + .send_all().in_room(room.id).action()] + } + }; + server.react(client_id, actions); + } + SendTeamRemovalMessage(team_name) => { + let mut actions = Vec::new(); + if let Some(r) = server.room(client_id) { + if let Some(ref mut info) = r.game_info { + let msg = once(b'F').chain(team_name.bytes()); + actions.push(ForwardEngineMessage(vec![to_engine_msg(msg)]). + send_all().in_room(r.id).but_self().action()); + info.teams_in_game -= 1; + if info.teams_in_game == 0 { + actions.push(FinishRoomGame(r.id)); + } + let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes())); + if let Some(m) = &info.sync_msg { + info.msg_log.push(m.clone()); + } + if info.sync_msg.is_some() { + info.sync_msg = None + } + info.msg_log.push(remove_msg.clone()); + actions.push(ForwardEngineMessage(vec![remove_msg]) + .send_all().in_room(r.id).but_self().action()); + } + } + server.react(client_id, actions); + } + FinishRoomGame(room_id) => { + let mut actions = Vec::new(); + + let r = &mut server.rooms[room_id]; + r.ready_players_number = 1; + actions.push(SendRoomUpdate(None)); + actions.push(RoundFinished.send_all().in_room(r.id).action()); + + if let Some(info) = replace(&mut r.game_info, None) { + for (_, c) in server.clients.iter() { + if c.room_id == Some(room_id) && c.is_joined_mid_game() { + actions.push(SendRoomData{ + to: c.id, teams: false, + config: true, flags: false}); + for name in &info.left_teams { + actions.push(TeamRemove(name.clone()) + .send(c.id).action()); + } + } + } + } + + let nicks: Vec<_> = server.clients.iter_mut() + .filter(|(_, c)| c.room_id == Some(room_id)) + .map(|(_, c)| { + c.set_is_ready(c.is_master()); + c.set_is_joined_mid_game(false); + c + }).filter_map(|c| if !c.is_master() { + Some(c.nick.clone()) + } else { + None + }).collect(); + + if !nicks.is_empty() { + let msg = if r.protocol_number < 38 { + LegacyReady(false, nicks) + } else { + ClientFlags("-r".to_string(), nicks) + }; + actions.push(msg.send_all().in_room(room_id).action()); + } + server.react(client_id, actions); + } + Warn(msg) => { + run_action(server, client_id, Warning(msg).send_self().action()); + } + ProtocolError(msg) => { + run_action(server, client_id, Error(msg).send_self().action()) + } + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/client.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/client.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,68 @@ +use super::coretypes::ClientId; +use bitflags::*; + +bitflags!{ + pub struct ClientFlags: u8 { + const IS_ADMIN = 0b0000_0001; + const IS_MASTER = 0b0000_0010; + const IS_READY = 0b0000_0100; + const IS_IN_GAME = 0b0000_1000; + const IS_JOINED_MID_GAME = 0b0001_0000; + const IS_CHECKER = 0b0010_0000; + + const NONE = 0b0000_0000; + const DEFAULT = Self::NONE.bits; + } +} + +pub struct HWClient { + pub id: ClientId, + pub room_id: Option, + pub nick: String, + pub web_password: String, + pub server_salt: String, + pub protocol_number: u16, + pub flags: ClientFlags, + pub teams_in_game: u8, + pub team_indices: Vec, + pub clan: Option +} + +impl HWClient { + pub fn new(id: ClientId, salt: String) -> HWClient { + HWClient { + id, + room_id: None, + nick: String::new(), + web_password: String::new(), + server_salt: salt, + protocol_number: 0, + flags: ClientFlags::DEFAULT, + teams_in_game: 0, + team_indices: Vec::new(), + clan: None, + } + } + + fn contains(& self, mask: ClientFlags) -> bool { + self.flags.contains(mask) + } + + fn set(&mut self, mask: ClientFlags, value: bool) { + self.flags.set(mask, value); + } + + pub fn is_admin(&self)-> bool { self.contains(ClientFlags::IS_ADMIN) } + pub fn is_master(&self)-> bool { self.contains(ClientFlags::IS_MASTER) } + pub fn is_ready(&self)-> bool { self.contains(ClientFlags::IS_READY) } + pub fn is_in_game(&self)-> bool { self.contains(ClientFlags::IS_IN_GAME) } + pub fn is_joined_mid_game(&self)-> bool { self.contains(ClientFlags::IS_JOINED_MID_GAME) } + pub fn is_checker(&self)-> bool { self.contains(ClientFlags::IS_CHECKER) } + + pub fn set_is_admin(&mut self, value: bool) { self.set(ClientFlags::IS_ADMIN, value) } + pub fn set_is_master(&mut self, value: bool) { self.set(ClientFlags::IS_MASTER, value) } + pub fn set_is_ready(&mut self, value: bool) { self.set(ClientFlags::IS_READY, value) } + pub fn set_is_in_game(&mut self, value: bool) { self.set(ClientFlags::IS_IN_GAME, value) } + pub fn set_is_joined_mid_game(&mut self, value: bool) { self.set(ClientFlags::IS_JOINED_MID_GAME, value) } + pub fn set_is_checker(&mut self, value: bool) { self.set(ClientFlags::IS_CHECKER, value) } +} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/core.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/core.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,158 @@ +use slab; +use crate::utils; +use super::{ + io::HWServerIO, + client::HWClient, room::HWRoom, actions, handlers, + coretypes::{ClientId, RoomId}, + actions::{Destination, PendingMessage} +}; +use crate::protocol::messages::*; +use rand::{RngCore, thread_rng}; +use base64::{encode}; +use log::*; + +type Slab = slab::Slab; + +pub struct HWServer { + pub clients: Slab, + pub rooms: Slab, + pub lobby_id: RoomId, + pub output: Vec<(Vec, HWServerMessage)>, + pub removed_clients: Vec, + pub io: Box +} + +impl HWServer { + pub fn new(clients_limit: usize, rooms_limit: usize, io: Box) -> HWServer { + let rooms = Slab::with_capacity(rooms_limit); + let clients = Slab::with_capacity(clients_limit); + let mut server = HWServer { + clients, rooms, + lobby_id: 0, + output: vec![], + removed_clients: vec![], + io + }; + server.lobby_id = server.add_room(); + server + } + + pub fn add_client(&mut self) -> ClientId { + let key: ClientId; + { + let entry = self.clients.vacant_entry(); + key = entry.key(); + let mut salt = [0u8; 18]; + thread_rng().fill_bytes(&mut salt); + + let client = HWClient::new(entry.key(), encode(&salt)); + entry.insert(client); + } + self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION)); + key + } + + pub fn client_lost(&mut self, client_id: ClientId) { + actions::run_action(self, client_id, + actions::Action::ByeClient("Connection reset".to_string())); + } + + pub fn add_room(&mut self) -> RoomId { + let entry = self.rooms.vacant_entry(); + let key = entry.key(); + let room = HWRoom::new(entry.key()); + entry.insert(room); + key + } + + pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) { + debug!("Handling message {:?} for client {}", msg, client_id); + if self.clients.contains(client_id) { + handlers::handle(self, client_id, msg); + } + } + + fn get_recipients(&self, client_id: ClientId, destination: &Destination) -> Vec { + let mut ids = match *destination { + Destination::ToSelf => vec![client_id], + Destination::ToId(id) => vec![id], + Destination::ToAll {room_id: Some(id), ..} => + self.room_clients(id), + Destination::ToAll {protocol: Some(proto), ..} => + self.protocol_clients(proto), + Destination::ToAll {..} => + self.clients.iter().map(|(id, _)| id).collect::>() + }; + if let Destination::ToAll {skip_self: true, ..} = destination { + if let Some(index) = ids.iter().position(|id| *id == client_id) { + ids.remove(index); + } + } + ids + } + + pub fn send(&mut self, client_id: ClientId, destination: &Destination, message: HWServerMessage) { + let ids = self.get_recipients(client_id, &destination); + self.output.push((ids, message)); + } + + pub fn react(&mut self, client_id: ClientId, actions: Vec) { + for action in actions { + actions::run_action(self, client_id, action); + } + } + + pub fn lobby(&self) -> &HWRoom { &self.rooms[self.lobby_id] } + + pub fn has_room(&self, name: &str) -> bool { + self.rooms.iter().any(|(_, r)| r.name == name) + } + + pub fn find_room(&self, name: &str) -> Option<&HWRoom> { + self.rooms.iter().find_map(|(_, r)| Some(r).filter(|r| r.name == name)) + } + + pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HWRoom> { + self.rooms.iter_mut().find_map(|(_, r)| Some(r).filter(|r| r.name == name)) + } + + pub fn find_client(&self, nick: &str) -> Option<&HWClient> { + self.clients.iter().find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) + } + + pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> { + self.clients.iter_mut().find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) + } + + pub fn select_clients(&self, f: F) -> Vec + where F: Fn(&(usize, &HWClient)) -> bool { + self.clients.iter().filter(f) + .map(|(_, c)| c.id).collect() + } + + pub fn room_clients(&self, room_id: RoomId) -> Vec { + self.select_clients(|(_, c)| c.room_id == Some(room_id)) + } + + pub fn protocol_clients(&self, protocol: u16) -> Vec { + self.select_clients(|(_, c)| c.protocol_number == protocol) + } + + pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec { + let room_id = self.clients[self_id].room_id; + self.select_clients(|(id, c)| *id != self_id && c.room_id == room_id ) + } + + pub fn client_and_room(&mut self, client_id: ClientId) -> (&mut HWClient, Option<&mut HWRoom>) { + let c = &mut self.clients[client_id]; + if let Some(room_id) = c.room_id { + (c, Some(&mut self.rooms[room_id])) + } else { + (c, None) + } + } + + pub fn room(&mut self, client_id: ClientId) -> Option<&mut HWRoom> { + self.client_and_room(client_id).1 + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/coretypes.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/coretypes.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,72 @@ +pub type ClientId = usize; +pub type RoomId = usize; + +pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum ServerVar { + MOTDNew(String), + MOTDOld(String), + LatestProto(u32), +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum GameCfg { + FeatureSize(u32), + MapType(String), + MapGenerator(u32), + MazeSize(u32), + Seed(String), + Template(u32), + + Ammo(String, Option), + Scheme(String, Vec), + Script(String), + Theme(String), + DrawnMap(String) +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct TeamInfo { + pub name: String, + pub color: u8, + pub grave: String, + pub fort: String, + pub voice_pack: String, + pub flag: String, + pub difficulty: u8, + pub hedgehogs_number: u8, + pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize], +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct HedgehogInfo { + pub name: String, + pub hat: String, +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum VoteType { + Kick(String), + Map(Option), + Pause, + NewSeed, + HedgehogsPerTeam(u8) +} + +#[derive(Clone, Debug)] +pub struct Voting { + pub ttl: u32, + pub voters: Vec, + pub votes: Vec<(ClientId, bool)>, + pub kind: VoteType +} + +impl Voting { + pub fn new(kind: VoteType, voters: Vec) -> Voting { + Voting { + kind, voters, ttl: 2, + votes: Vec::new() + } + } +} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/handlers.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/handlers.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,44 @@ +use mio; +use std::{io, io::Write}; + +use super::{ + core::HWServer, + actions::{Action, Action::*}, + coretypes::ClientId +}; +use crate::{ + protocol::messages::{ + HWProtocolMessage, + HWServerMessage::* + } +}; +use log::*; + +mod loggingin; +mod lobby; +mod inroom; +mod common; +mod checker; + +pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) { + match message { + HWProtocolMessage::Ping => + server.react(client_id, vec![Pong.send_self().action()]), + HWProtocolMessage::Quit(Some(msg)) => + server.react(client_id, vec![ByeClient("User quit: ".to_string() + &msg)]), + HWProtocolMessage::Quit(None) => + server.react(client_id, vec![ByeClient("User quit".to_string())]), + HWProtocolMessage::Malformed => warn!("Malformed/unknown message"), + HWProtocolMessage::Empty => warn!("Empty message"), + _ => { + match server.clients[client_id].room_id { + None => + loggingin::handle(server, client_id, message), + Some(id) if id == server.lobby_id => + lobby::handle(server, client_id, message), + Some(id) => + inroom::handle(server, client_id, id, message) + } + }, + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/handlers/checker.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/handlers/checker.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,18 @@ +use mio; +use log::*; + +use crate::{ + server::{ + core::HWServer, + coretypes::ClientId, + }, + protocol::messages::{ + HWProtocolMessage + }, +}; + +pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) { + match message { + _ => warn!("Unknown command"), + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/handlers/common.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/handlers/common.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,73 @@ +use crate::{ + server::{actions::Action, core::HWServer}, + protocol::messages::{ + HWProtocolMessage::{self, Rnd}, HWServerMessage::{self, ChatMsg}, + } +}; +use rand::{self, Rng, thread_rng}; + +pub fn rnd_reply(options: &[String]) -> HWServerMessage { + let mut rng = thread_rng(); + let reply = if options.is_empty() { + (*rng.choose(&["heads", "tails"]).unwrap()).to_owned() + } else { + rng.choose(&options).unwrap().clone() + }; + + ChatMsg { + nick: "[random]".to_owned(), + msg: reply.clone(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::messages::HWServerMessage::ChatMsg; + use crate::server::actions::{ + Action::{self, Send}, PendingMessage, + }; + + fn reply2string(r: HWServerMessage) -> String { + match r { + ChatMsg { msg: p, .. } => String::from(p), + _ => panic!("expected a ChatMsg"), + } + } + + fn run_handle_test(opts: Vec) { + let opts2 = opts.clone(); + for opt in opts { + while reply2string(rnd_reply(&opts2)) != opt {} + } + } + + /// This test terminates almost surely. + #[test] + fn test_handle_rnd_empty() { + run_handle_test(vec![]) + } + + /// This test terminates almost surely. + #[test] + fn test_handle_rnd_nonempty() { + run_handle_test(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]) + } + + /// This test terminates almost surely (strong law of large numbers) + #[test] + fn test_distribution() { + let eps = 0.000001; + let lim = 0.5; + let opts = vec![0.to_string(), 1.to_string()]; + let mut ones = 0; + let mut tries = 0; + + while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps { + tries += 1; + if reply2string(rnd_reply(&opts)) == 1.to_string() { + ones += 1; + } + } + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/handlers/inroom.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/handlers/inroom.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,486 @@ +use mio; + +use crate::{ + server::{ + coretypes::{ + ClientId, RoomId, Voting, VoteType, GameCfg, + MAX_HEDGEHOGS_PER_TEAM + }, + core::HWServer, + room::{HWRoom, RoomFlags}, + actions::{Action, Action::*} + }, + protocol::messages::{ + HWProtocolMessage, + HWServerMessage::*, + server_chat + }, + utils::is_name_illegal +}; +use std::{ + mem::swap +}; +use base64::{encode, decode}; +use super::common::rnd_reply; +use log::*; + +#[derive(Clone)] +struct ByMsg<'a> { + messages: &'a[u8] +} + +impl <'a> Iterator for ByMsg<'a> { + type Item = &'a[u8]; + + fn next(&mut self) -> Option<::Item> { + if let Some(size) = self.messages.get(0) { + let (msg, next) = self.messages.split_at(*size as usize + 1); + self.messages = next; + Some(msg) + } else { + None + } + } +} + +fn by_msg(source: &[u8]) -> ByMsg { + ByMsg {messages: source} +} + +const VALID_MESSAGES: &[u8] = + b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A"; +const NON_TIMED_MESSAGES: &[u8] = b"M#hb"; + +#[cfg(canhazslicepatterns)] +fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool { + match msg { + [size, typ, body..] => VALID_MESSAGES.contains(typ) + && match body { + [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => + team_indices.contains(team), + _ => *typ != b'h' + }, + _ => false + } +} + +fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool { + if let Some(typ) = msg.get(1) { + VALID_MESSAGES.contains(typ) + } else { + false + } +} + +fn is_msg_empty(msg: &[u8]) -> bool { + msg.get(1).filter(|t| **t == b'+').is_some() +} + +fn is_msg_timed(msg: &[u8]) -> bool { + msg.get(1).filter(|t| !NON_TIMED_MESSAGES.contains(t)).is_some() +} + +fn voting_description(kind: &VoteType) -> String { + format!("New voting started: {}", match kind { + VoteType::Kick(nick) => format!("kick {}", nick), + VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()), + VoteType::Pause => "pause".to_string(), + VoteType::NewSeed => "new seed".to_string(), + VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number) + }) +} + +fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags { + use crate::protocol::messages::HWProtocolMessage::*; + match msg { + ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN, + ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD, + ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, + _ => RoomFlags::empty() + } +} + +pub fn handle(server: &mut HWServer, client_id: ClientId, room_id: RoomId, message: HWProtocolMessage) { + use crate::protocol::messages::HWProtocolMessage::*; + match message { + Part(None) => server.react(client_id, vec![ + MoveToLobby("part".to_string())]), + Part(Some(msg)) => server.react(client_id, vec![ + MoveToLobby(format!("part: {}", msg))]), + Chat(msg) => { + let actions = { + let c = &mut server.clients[client_id]; + let chat_msg = ChatMsg {nick: c.nick.clone(), msg}; + vec![chat_msg.send_all().in_room(room_id).but_self().action()] + }; + server.react(client_id, actions); + }, + Fix => { + if let (c, Some(r)) = server.client_and_room(client_id) { + if c.is_admin() { r.set_is_fixed(true) } + } + } + Unfix => { + if let (c, Some(r)) = server.client_and_room(client_id) { + if c.is_admin() { r.set_is_fixed(false) } + } + } + Greeting(text) => { + if let (c, Some(r)) = server.client_and_room(client_id) { + if c.is_admin() || c.is_master() && !r.is_fixed() { + r.greeting = text + } + } + } + RoomName(new_name) => { + let actions = + if is_name_illegal(&new_name) { + vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] + } else if server.rooms[room_id].is_fixed() { + vec![Warn("Access denied.".to_string())] + } else if server.has_room(&new_name) { + vec![Warn("A room with the same name already exists.".to_string())] + } else { + let mut old_name = new_name.clone(); + swap(&mut server.rooms[room_id].name, &mut old_name); + vec![SendRoomUpdate(Some(old_name))] + }; + server.react(client_id, actions); + }, + ToggleReady => { + if let (c, Some(r)) = server.client_and_room(client_id) { + let flags = if c.is_ready() { + r.ready_players_number -= 1; + "-r" + } else { + r.ready_players_number += 1; + "+r" + }; + + let msg = if c.protocol_number < 38 { + LegacyReady(c.is_ready(), vec![c.nick.clone()]) + } else { + ClientFlags(flags.to_string(), vec![c.nick.clone()]) + }; + + let mut v = vec![msg.send_all().in_room(r.id).action()]; + + if r.is_fixed() && r.ready_players_number == r.players_number { + v.push(StartRoomGame(r.id)) + } + + c.set_is_ready(!c.is_ready()); + server.react(client_id, v); + } + } + AddTeam(info) => { + let mut actions = Vec::new(); + if let (c, Some(r)) = server.client_and_room(client_id) { + if r.teams.len() >= r.team_limit as usize { + actions.push(Warn("Too many teams!".to_string())) + } else if r.addable_hedgehogs() == 0 { + actions.push(Warn("Too many hedgehogs!".to_string())) + } else if r.find_team(|t| t.name == info.name) != None { + actions.push(Warn("There's already a team with same name in the list.".to_string())) + } else if r.game_info.is_some() { + actions.push(Warn("Joining not possible: Round is in progress.".to_string())) + } else if r.is_team_add_restricted() { + actions.push(Warn("This room currently does not allow adding new teams.".to_string())); + } else { + let team = r.add_team(c.id, *info, c.protocol_number < 42); + c.teams_in_game += 1; + c.clan = Some(team.color); + actions.push(TeamAccepted(team.name.clone()) + .send_self().action()); + actions.push(TeamAdd(HWRoom::team_info(&c, team)) + .send_all().in_room(room_id).but_self().action()); + actions.push(TeamColor(team.name.clone(), team.color) + .send_all().in_room(room_id).action()); + actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number) + .send_all().in_room(room_id).action()); + actions.push(SendRoomUpdate(None)); + } + } + server.react(client_id, actions); + }, + RemoveTeam(name) => { + let mut actions = Vec::new(); + if let (c, Some(r)) = server.client_and_room(client_id) { + match r.find_team_owner(&name) { + None => + actions.push(Warn("Error: The team you tried to remove does not exist.".to_string())), + Some((id, _)) if id != client_id => + actions.push(Warn("You can't remove a team you don't own.".to_string())), + Some((_, name)) => { + c.teams_in_game -= 1; + c.clan = r.find_team_color(c.id); + actions.push(Action::RemoveTeam(name.to_string())); + } + } + }; + server.react(client_id, actions); + }, + SetHedgehogsNumber(team_name, number) => { + if let (c, Some(r)) = server.client_and_room(client_id) { + let addable_hedgehogs = r.addable_hedgehogs(); + let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { + if !c.is_master() { + vec![ProtocolError("You're not the room master!".to_string())] + } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM + || number > addable_hedgehogs + team.hedgehogs_number { + vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number) + .send_self().action()] + } else { + team.hedgehogs_number = number; + vec![HedgehogsNumber(team.name.clone(), number) + .send_all().in_room(room_id).but_self().action()] + } + } else { + vec![(Warn("No such team.".to_string()))] + }; + server.react(client_id, actions); + } + }, + SetTeamColor(team_name, color) => { + if let (c, Some(r)) = server.client_and_room(client_id) { + let mut owner_id = None; + let actions = if let Some((owner, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { + if !c.is_master() { + vec![ProtocolError("You're not the room master!".to_string())] + } else if false { + Vec::new() + } else { + owner_id = Some(owner); + team.color = color; + vec![TeamColor(team.name.clone(), color) + .send_all().in_room(room_id).but_self().action()] + } + } else { + vec![(Warn("No such team.".to_string()))] + }; + + if let Some(id) = owner_id { + server.clients[id].clan = Some(color); + } + + server.react(client_id, actions); + }; + }, + Cfg(cfg) => { + if let (c, Some(r)) = server.client_and_room(client_id) { + let actions = if r.is_fixed() { + vec![Warn("Access denied.".to_string())] + } else if !c.is_master() { + vec![ProtocolError("You're not the room master!".to_string())] + } else { + let cfg = match cfg { + GameCfg::Scheme(name, mut values) => { + if c.protocol_number == 49 && values.len() >= 2 { + let mut s = "X".repeat(50); + s.push_str(&values.pop().unwrap()); + values.push(s); + } + GameCfg::Scheme(name, values) + } + cfg => cfg + }; + + let v = vec![cfg.to_server_msg() + .send_all().in_room(r.id).but_self().action()]; + r.set_config(cfg); + v + }; + server.react(client_id, actions); + } + } + Save(name, location) => { + let actions = vec![server_chat(format!("Room config saved as {}", name)) + .send_all().in_room(room_id).action()]; + server.rooms[room_id].save_config(name, location); + server.react(client_id, actions); + } + SaveRoom(filename) => { + if server.clients[client_id].is_admin() { + let actions = match server.rooms[room_id].get_saves() { + Ok(text) => match server.io.write_file(&filename, &text) { + Ok(_) => vec![server_chat("Room configs saved successfully.".to_string()) + .send_self().action()], + Err(e) => { + warn!("Error while writing the config file \"{}\": {}", filename, e); + vec![Warn("Unable to save the room configs.".to_string())] + } + } + Err(e) => { + warn!("Error while serializing the room configs: {}", e); + vec![Warn("Unable to serialize the room configs.".to_string())] + } + }; + server.react(client_id, actions); + } + } + LoadRoom(filename) => { + if server.clients[client_id].is_admin() { + let actions = match server.io.read_file(&filename) { + Ok(text) => match server.rooms[room_id].set_saves(&text) { + Ok(_) => vec![server_chat("Room configs loaded successfully.".to_string()) + .send_self().action()], + Err(e) => { + warn!("Error while deserializing the room configs: {}", e); + vec![Warn("Unable to deserialize the room configs.".to_string())] + } + } + Err(e) => { + warn!("Error while reading the config file \"{}\": {}", filename, e); + vec![Warn("Unable to load the room configs.".to_string())] + } + }; + server.react(client_id, actions); + } + } + Delete(name) => { + let actions = if !server.rooms[room_id].delete_config(&name) { + vec![Warn(format!("Save doesn't exist: {}", name))] + } else { + vec![server_chat(format!("Room config {} has been deleted", name)) + .send_all().in_room(room_id).action()] + }; + server.react(client_id, actions); + } + CallVote(None) => { + server.react(client_id, vec![ + server_chat("Available callvote commands: kick , map , pause, newseed, hedgehogs ".to_string()) + .send_self().action()]) + } + CallVote(Some(kind)) => { + let is_in_game = server.rooms[room_id].game_info.is_some(); + let error = match &kind { + VoteType::Kick(nick) => { + if server.find_client(&nick).filter(|c| c.room_id == Some(room_id)).is_some() { + None + } else { + Some("/callvote kick: No such user!".to_string()) + } + }, + VoteType::Map(None) => { + let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect(); + if names.is_empty() { + Some("/callvote map: No maps saved in this room!".to_string()) + } else { + Some(format!("Available maps: {}", names.join(", "))) + } + }, + VoteType::Map(Some(name)) => { + if server.rooms[room_id].saves.get(&name[..]).is_some() { + None + } else { + Some("/callvote map: No such map!".to_string()) + } + }, + VoteType::Pause => { + if is_in_game { + None + } else { + Some("/callvote pause: No game in progress!".to_string()) + } + }, + VoteType::NewSeed => { + None + }, + VoteType::HedgehogsPerTeam(number) => { + match number { + 1...MAX_HEDGEHOGS_PER_TEAM => None, + _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()) + } + }, + }; + match error { + None => { + let msg = voting_description(&kind); + let voting = Voting::new(kind, server.room_clients(client_id)); + server.rooms[room_id].voting = Some(voting); + server.react(client_id, vec![ + server_chat(msg).send_all().in_room(room_id).action(), + AddVote{ vote: true, is_forced: false}]); + } + Some(msg) => { + server.react(client_id, vec![ + server_chat(msg).send_self().action()]) + } + } + } + Vote(vote) => { + server.react(client_id, vec![AddVote{ vote, is_forced: false }]); + } + ForceVote(vote) => { + let is_forced = server.clients[client_id].is_admin(); + server.react(client_id, vec![AddVote{ vote, is_forced }]); + } + ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => { + if server.clients[client_id].is_master() { + server.rooms[room_id].flags.toggle(room_message_flag(&message)); + } + server.react(client_id, vec![SendRoomUpdate(None)]); + } + StartGame => { + server.react(client_id, vec![StartRoomGame(room_id)]); + } + EngineMessage(em) => { + let mut actions = Vec::new(); + if let (c, Some(r)) = server.client_and_room(client_id) { + if c.teams_in_game > 0 { + let decoding = decode(&em[..]).unwrap(); + let messages = by_msg(&decoding); + let valid = messages.filter(|m| is_msg_valid(m, &c.team_indices)); + let non_empty = valid.clone().filter(|m| !is_msg_empty(m)); + let sync_msg = valid.clone().filter(|m| is_msg_timed(m)) + .last().map(|m| if is_msg_empty(m) {Some(encode(m))} else {None}); + + let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::>()); + if !em_response.is_empty() { + actions.push(ForwardEngineMessage(vec![em_response]) + .send_all().in_room(r.id).but_self().action()); + } + let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::>()); + if let Some(ref mut info) = r.game_info { + if !em_log.is_empty() { + info.msg_log.push(em_log); + } + if let Some(msg) = sync_msg { + info.sync_msg = msg; + } + } + } + } + server.react(client_id, actions) + } + RoundFinished => { + let mut actions = Vec::new(); + if let (c, Some(r)) = server.client_and_room(client_id) { + if c.is_in_game() { + c.set_is_in_game(false); + actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]). + send_all().in_room(r.id).action()); + if r.game_info.is_some() { + for team in r.client_teams(c.id) { + actions.push(SendTeamRemovalMessage(team.name.clone())); + } + } + } + } + server.react(client_id, actions) + }, + Rnd(v) => { + let result = rnd_reply(&v); + let mut echo = vec!["/rnd".to_string()]; + echo.extend(v.into_iter()); + let chat_msg = ChatMsg { + nick: server.clients[client_id].nick.clone(), + msg: echo.join(" ") + }; + server.react(client_id, vec![ + chat_msg.send_all().in_room(room_id).action(), + result.send_all().in_room(room_id).action()]) + }, + _ => warn!("Unimplemented!") + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/handlers/lobby.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/handlers/lobby.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,72 @@ +use mio; + +use crate::{ + server::{ + core::HWServer, + coretypes::ClientId, + actions::{Action, Action::*} + }, + protocol::messages::{ + HWProtocolMessage, + HWServerMessage::* + }, + utils::is_name_illegal +}; +use super::common::rnd_reply; +use log::*; + +pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) { + use crate::protocol::messages::HWProtocolMessage::*; + match message { + CreateRoom(name, password) => { + let actions = + if is_name_illegal(&name) { + vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] + } else if server.has_room(&name) { + vec![Warn("A room with the same name already exists.".to_string())] + } else { + let flags_msg = ClientFlags( + "+hr".to_string(), + vec![server.clients[client_id].nick.clone()]); + vec![AddRoom(name, password), + flags_msg.send_self().action()] + }; + server.react(client_id, actions); + }, + Chat(msg) => { + let actions = vec![ChatMsg {nick: server.clients[client_id].nick.clone(), msg} + .send_all().in_room(server.lobby_id).but_self().action()]; + server.react(client_id, actions); + }, + JoinRoom(name, _password) => { + let room = server.rooms.iter().find(|(_, r)| r.name == name); + let room_id = room.map(|(_, r)| r.id); + let nicks = server.clients.iter() + .filter(|(_, c)| c.room_id == room_id) + .map(|(_, c)| c.nick.clone()) + .collect(); + let c = &mut server.clients[client_id]; + + let actions = if let Some((_, r)) = room { + if c.protocol_number != r.protocol_number { + vec![Warn("Room version incompatible to your Hedgewars version!".to_string())] + } else if r.is_join_restricted() { + vec![Warn("Access denied. This room currently doesn't allow joining.".to_string())] + } else if r.players_number == u8::max_value() { + vec![Warn("This room is already full".to_string())] + } else { + vec![MoveToRoom(r.id), + RoomJoined(nicks).send_self().action()] + } + } else { + vec![Warn("No such room.".to_string())] + }; + server.react(client_id, actions); + }, + Rnd(v) => { + server.react(client_id, vec![rnd_reply(&v).send_self().action()]); + }, + List => warn!("Deprecated LIST message received"), + _ => warn!("Incorrect command in lobby state"), + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/handlers/loggingin.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/handlers/loggingin.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,99 @@ +use mio; + +use crate::{ + server::{ + client::HWClient, + core::HWServer, + coretypes::ClientId, + actions::{Action, Action::*} + }, + protocol::messages::{ + HWProtocolMessage, HWServerMessage::* + }, + utils::is_name_illegal +}; +#[cfg(feature = "official-server")] +use openssl::sha::sha1; +use std::fmt::{Formatter, LowerHex}; +use log::*; + +#[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(client: &HWClient, salt1: &str, salt2: &str) -> Sha1Digest { + let s = format!("{}{}{}{}{}", salt1, salt2, + client.web_password, client.protocol_number, "!hedgewars"); + Sha1Digest(sha1(s.as_bytes())) +} + +pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) { + match message { + HWProtocolMessage::Nick(nick) => { + let client = &mut server.clients[client_id]; + debug!("{} {}", nick, is_name_illegal(&nick)); + let actions = if client.room_id != None { + unreachable!() + } + else if !client.nick.is_empty() { + vec![ProtocolError("Nickname already provided.".to_string())] + } + else if is_name_illegal(&nick) { + vec![ByeClient("Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] + } + else { + client.nick = nick.clone(); + vec![Nick(nick).send_self().action(), + CheckRegistered] + }; + + server.react(client_id, actions); + } + HWProtocolMessage::Proto(proto) => { + let client = &mut server.clients[client_id]; + let actions = if client.protocol_number != 0 { + vec![ProtocolError("Protocol already known.".to_string())] + } + else if proto == 0 { + vec![ProtocolError("Bad number.".to_string())] + } + else { + client.protocol_number = proto; + vec![Proto(proto).send_self().action(), + CheckRegistered] + }; + server.react(client_id, actions); + } + #[cfg(feature = "official-server")] + HWProtocolMessage::Password(hash, salt) => { + let c = &server.clients[client_id]; + + let client_hash = get_hash(c, &salt, &c.server_salt); + let server_hash = get_hash(c, &c.server_salt, &salt); + let actions = if client_hash == server_hash { + vec![ServerAuth(format!("{:x}", server_hash)).send_self().action(), + JoinLobby] + } else { + vec![ByeClient("Authentication failed".to_string())] + }; + server.react(client_id, actions); + } + #[cfg(feature = "official-server")] + HWProtocolMessage::Checker(protocol, nick, password) => { + let c = &mut server.clients[client_id]; + c.nick = nick; + c.web_password = password; + c.set_is_checker(true); + } + _ => warn!("Incorrect command in logging-in state"), + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/io.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/io.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,49 @@ +use std::{ + fs::{File, OpenOptions}, + io::{Read, Write, Result, Error, ErrorKind} +}; + +pub trait HWServerIO { + fn write_file(&mut self, name: &str, content: &str) -> Result<()>; + fn read_file(&mut self, name: &str) -> Result; +} + +pub struct EmptyServerIO {} + +impl EmptyServerIO { + pub fn new() -> Self { + Self {} + } +} + +impl HWServerIO for EmptyServerIO { + fn write_file(&mut self, _name: &str, _content: &str) -> Result<()> { + Ok(()) + } + + fn read_file(&mut self, _name: &str) -> 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) -> 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) + } +} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/network.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/network.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,444 @@ +extern crate slab; + +use std::{ + io, io::{Error, ErrorKind, Read, Write}, + net::{SocketAddr, IpAddr, Ipv4Addr}, + collections::HashSet, + mem::{swap, replace} +}; + +use mio::{ + net::{TcpStream, TcpListener}, + Poll, PollOpt, Ready, Token +}; +use netbuf; +use slab::Slab; +use log::*; + +use crate::{ + utils, + protocol::{ProtocolDecoder, messages::*} +}; +use super::{ + io::FileServerIO, + core::{HWServer}, + coretypes::ClientId +}; +#[cfg(feature = "tls-connections")] +use openssl::{ + ssl::{ + SslMethod, SslContext, Ssl, SslContextBuilder, + SslVerifyMode, SslFiletype, SslOptions, + SslStreamBuilder, HandshakeError, MidHandshakeSslStream, SslStream + }, + error::ErrorStack +}; + +const MAX_BYTES_PER_READ: usize = 2048; + +#[derive(Hash, Eq, PartialEq, Copy, Clone)] +pub enum NetworkClientState { + Idle, + NeedsWrite, + NeedsRead, + Closed, +} + +type NetworkResult = io::Result<(T, NetworkClientState)>; + +#[cfg(not(feature = "tls-connections"))] +pub enum ClientSocket { + Plain(TcpStream) +} + +#[cfg(feature = "tls-connections")] +pub enum ClientSocket { + SslHandshake(Option>), + SslStream(SslStream) +} + +impl ClientSocket { + fn inner(&self) -> &TcpStream { + #[cfg(not(feature = "tls-connections"))] + match self { + ClientSocket::Plain(stream) => stream, + } + + #[cfg(feature = "tls-connections")] + match self { + ClientSocket::SslHandshake(Some(builder)) => builder.get_ref(), + ClientSocket::SslHandshake(None) => unreachable!(), + ClientSocket::SslStream(ssl_stream) => ssl_stream.get_ref() + } + } +} + +pub struct NetworkClient { + id: ClientId, + socket: ClientSocket, + peer_addr: SocketAddr, + decoder: ProtocolDecoder, + buf_out: netbuf::Buf +} + +impl NetworkClient { + pub fn new(id: ClientId, socket: ClientSocket, peer_addr: SocketAddr) -> NetworkClient { + NetworkClient { + id, socket, peer_addr, + decoder: ProtocolDecoder::new(), + buf_out: netbuf::Buf::new() + } + } + + #[cfg(feature = "tls-connections")] + fn handshake_impl(&mut self, handshake: MidHandshakeSslStream) -> io::Result { + match handshake.handshake() { + Ok(stream) => { + self.socket = ClientSocket::SslStream(stream); + debug!("TLS handshake with {} ({}) completed", self.id, self.peer_addr); + Ok(NetworkClientState::Idle) + } + Err(HandshakeError::WouldBlock(new_handshake)) => { + self.socket = ClientSocket::SslHandshake(Some(new_handshake)); + Ok(NetworkClientState::Idle) + } + Err(HandshakeError::Failure(new_handshake)) => { + self.socket = ClientSocket::SslHandshake(Some(new_handshake)); + debug!("TLS handshake with {} ({}) failed", self.id, self.peer_addr); + Err(Error::new(ErrorKind::Other, "Connection failure")) + } + Err(HandshakeError::SetupFailure(_)) => unreachable!() + } + } + + fn read_impl(decoder: &mut ProtocolDecoder, source: &mut R, + id: ClientId, addr: &SocketAddr) -> NetworkResult> { + let mut bytes_read = 0; + let result = loop { + match decoder.read_from(source) { + Ok(bytes) => { + debug!("Client {}: read {} bytes", id, bytes); + bytes_read += bytes; + if bytes == 0 { + let result = if bytes_read == 0 { + info!("EOF for client {} ({})", id, addr); + (Vec::new(), NetworkClientState::Closed) + } else { + (decoder.extract_messages(), NetworkClientState::NeedsRead) + }; + break Ok(result); + } + else if bytes_read >= MAX_BYTES_PER_READ { + break Ok((decoder.extract_messages(), NetworkClientState::NeedsRead)) + } + } + Err(ref error) if error.kind() == ErrorKind::WouldBlock => { + let messages = if bytes_read == 0 { + Vec::new() + } else { + decoder.extract_messages() + }; + break Ok((messages, NetworkClientState::Idle)); + } + Err(error) => + break Err(error) + } + }; + decoder.sweep(); + result + } + + pub fn read(&mut self) -> NetworkResult> { + #[cfg(not(feature = "tls-connections"))] + match self.socket { + ClientSocket::Plain(ref mut stream) => + NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr), + } + + #[cfg(feature = "tls-connections")] + match self.socket { + ClientSocket::SslHandshake(ref mut handshake_opt) => { + let handshake = std::mem::replace(handshake_opt, None).unwrap(); + Ok((Vec::new(), self.handshake_impl(handshake)?)) + }, + ClientSocket::SslStream(ref mut stream) => + NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr) + } + } + + fn write_impl(buf_out: &mut netbuf::Buf, destination: &mut W) -> NetworkResult<()> { + let result = loop { + match buf_out.write_to(destination) { + Ok(bytes) if buf_out.is_empty() || bytes == 0 => + break Ok(((), NetworkClientState::Idle)), + Ok(_) => (), + Err(ref error) if error.kind() == ErrorKind::Interrupted + || error.kind() == ErrorKind::WouldBlock => { + break Ok(((), NetworkClientState::NeedsWrite)); + }, + Err(error) => + break Err(error) + } + }; + result + } + + pub fn write(&mut self) -> NetworkResult<()> { + let result = { + #[cfg(not(feature = "tls-connections"))] + match self.socket { + ClientSocket::Plain(ref mut stream) => + NetworkClient::write_impl(&mut self.buf_out, stream) + } + + #[cfg(feature = "tls-connections")] { + match self.socket { + ClientSocket::SslHandshake(ref mut handshake_opt) => { + let handshake = std::mem::replace(handshake_opt, None).unwrap(); + Ok(((), self.handshake_impl(handshake)?)) + } + ClientSocket::SslStream(ref mut stream) => + NetworkClient::write_impl(&mut self.buf_out, stream) + } + } + }; + + self.socket.inner().flush()?; + result + } + + pub fn send_raw_msg(&mut self, msg: &[u8]) { + self.buf_out.write_all(msg).unwrap(); + } + + pub fn send_string(&mut self, msg: &str) { + self.send_raw_msg(&msg.as_bytes()); + } + + pub fn send_msg(&mut self, msg: &HWServerMessage) { + self.send_string(&msg.to_raw_protocol()); + } +} + +#[cfg(feature = "tls-connections")] +struct ServerSsl { + context: SslContext +} + +pub struct NetworkLayer { + listener: TcpListener, + server: HWServer, + clients: Slab, + pending: HashSet<(ClientId, NetworkClientState)>, + pending_cache: Vec<(ClientId, NetworkClientState)>, + #[cfg(feature = "tls-connections")] + ssl: ServerSsl +} + +impl NetworkLayer { + pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer { + let server = HWServer::new(clients_limit, rooms_limit, Box::new(FileServerIO::new())); + let clients = Slab::with_capacity(clients_limit); + let pending = HashSet::with_capacity(2 * clients_limit); + let pending_cache = Vec::with_capacity(2 * clients_limit); + + NetworkLayer { + listener, server, clients, pending, pending_cache, + #[cfg(feature = "tls-connections")] + ssl: NetworkLayer::create_ssl_context() + } + } + + #[cfg(feature = "tls-connections")] + fn create_ssl_context() -> ServerSsl { + let mut builder = SslContextBuilder::new(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + builder.set_read_ahead(true); + builder.set_certificate_file("ssl/cert.pem", SslFiletype::PEM).unwrap(); + builder.set_private_key_file("ssl/key.pem", SslFiletype::PEM).unwrap(); + builder.set_options(SslOptions::NO_COMPRESSION); + builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap(); + ServerSsl { context: builder.build() } + } + + pub fn register_server(&self, poll: &Poll) -> io::Result<()> { + poll.register(&self.listener, utils::SERVER, Ready::readable(), + PollOpt::edge()) + } + + fn deregister_client(&mut self, poll: &Poll, id: ClientId) { + let mut client_exists = false; + if let Some(ref client) = self.clients.get(id) { + poll.deregister(client.socket.inner()) + .expect("could not deregister socket"); + info!("client {} ({}) removed", client.id, client.peer_addr); + client_exists = true; + } + if client_exists { + self.clients.remove(id); + } + } + + fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: ClientSocket, addr: SocketAddr) { + poll.register(client_socket.inner(), Token(id), + Ready::readable() | Ready::writable(), + PollOpt::edge()) + .expect("could not register socket with event loop"); + + let entry = self.clients.vacant_entry(); + let client = NetworkClient::new(id, client_socket, addr); + info!("client {} ({}) added", client.id, client.peer_addr); + entry.insert(client); + } + + fn flush_server_messages(&mut self) { + debug!("{} pending server messages", self.server.output.len()); + for (clients, message) in self.server.output.drain(..) { + debug!("Message {:?} to {:?}", message, clients); + let msg_string = message.to_raw_protocol(); + for client_id in clients { + if let Some(client) = self.clients.get_mut(client_id) { + client.send_string(&msg_string); + self.pending.insert((client_id, NetworkClientState::NeedsWrite)); + } + } + } + } + + fn create_client_socket(&self, socket: TcpStream) -> io::Result { + #[cfg(not(feature = "tls-connections"))] { + Ok(ClientSocket::Plain(socket)) + } + + #[cfg(feature = "tls-connections")] { + let ssl = Ssl::new(&self.ssl.context).unwrap(); + let mut builder = SslStreamBuilder::new(ssl, socket); + builder.set_accept_state(); + match builder.handshake() { + Ok(stream) => + Ok(ClientSocket::SslStream(stream)), + Err(HandshakeError::WouldBlock(stream)) => + Ok(ClientSocket::SslHandshake(Some(stream))), + Err(e) => { + debug!("OpenSSL handshake failed: {}", e); + Err(Error::new(ErrorKind::Other, "Connection failure")) + } + } + } + } + + pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> { + let (client_socket, addr) = self.listener.accept()?; + info!("Connected: {}", addr); + + let client_id = self.server.add_client(); + self.register_client(poll, client_id, self.create_client_socket(client_socket)?, addr); + self.flush_server_messages(); + + Ok(()) + } + + fn operation_failed(&mut self, poll: &Poll, client_id: ClientId, error: &Error, msg: &str) -> io::Result<()> { + let addr = if let Some(ref mut client) = self.clients.get_mut(client_id) { + client.peer_addr + } else { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0) + }; + debug!("{}({}): {}", msg, addr, error); + self.client_error(poll, client_id) + } + + pub fn client_readable(&mut self, poll: &Poll, + client_id: ClientId) -> io::Result<()> { + let messages = + if let Some(ref mut client) = self.clients.get_mut(client_id) { + client.read() + } else { + warn!("invalid readable client: {}", client_id); + Ok((Vec::new(), NetworkClientState::Idle)) + }; + + match messages { + Ok((messages, state)) => { + for message in messages { + self.server.handle_msg(client_id, message); + } + match state { + NetworkClientState::NeedsRead => { + self.pending.insert((client_id, state)); + }, + NetworkClientState::Closed => + self.client_error(&poll, client_id)?, + _ => {} + }; + } + Err(e) => self.operation_failed( + poll, client_id, &e, + "Error while reading from client socket")? + } + + self.flush_server_messages(); + + if !self.server.removed_clients.is_empty() { + let ids: Vec<_> = self.server.removed_clients.drain(..).collect(); + for client_id in ids { + self.deregister_client(poll, client_id); + } + } + + Ok(()) + } + + pub fn client_writable(&mut self, poll: &Poll, + client_id: ClientId) -> io::Result<()> { + let result = + if let Some(ref mut client) = self.clients.get_mut(client_id) { + client.write() + } else { + warn!("invalid writable client: {}", client_id); + Ok(((), NetworkClientState::Idle)) + }; + + match result { + Ok(((), state)) if state == NetworkClientState::NeedsWrite => { + self.pending.insert((client_id, state)); + }, + Ok(_) => {} + Err(e) => self.operation_failed( + poll, client_id, &e, + "Error while writing to client socket")? + } + + Ok(()) + } + + pub fn client_error(&mut self, poll: &Poll, + client_id: ClientId) -> io::Result<()> { + self.deregister_client(poll, client_id); + self.server.client_lost(client_id); + + Ok(()) + } + + pub fn has_pending_operations(&self) -> bool { + !self.pending.is_empty() + } + + pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> { + if self.has_pending_operations() { + let mut cache = replace(&mut self.pending_cache, Vec::new()); + cache.extend(self.pending.drain()); + for (id, state) in cache.drain(..) { + match state { + NetworkClientState::NeedsRead => + self.client_readable(poll, id)?, + NetworkClientState::NeedsWrite => + self.client_writable(poll, id)?, + _ => {} + } + } + swap(&mut cache, &mut self.pending_cache); + } + Ok(()) + } +} diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/server/room.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/room.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,391 @@ +use std::{ + iter, collections::HashMap +}; +use crate::server::{ + coretypes::{ + ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting, + MAX_HEDGEHOGS_PER_TEAM + }, + client::{HWClient} +}; +use bitflags::*; +use serde::{Serialize, Deserialize}; +use serde_derive::{Serialize, Deserialize}; +use serde_yaml; + +const MAX_TEAMS_IN_ROOM: u8 = 8; +const MAX_HEDGEHOGS_IN_ROOM: u8 = + MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM; + +#[derive(Clone, Serialize, Deserialize)] +struct Ammo { + name: String, + settings: Option +} + +#[derive(Clone, Serialize, Deserialize)] +struct Scheme { + name: String, + settings: Vec +} + +#[derive(Clone, Serialize, Deserialize)] +struct RoomConfig { + feature_size: u32, + map_type: String, + map_generator: u32, + maze_size: u32, + seed: String, + template: u32, + + ammo: Ammo, + scheme: Scheme, + script: String, + theme: String, + drawn_map: Option +} + +impl RoomConfig { + fn new() -> RoomConfig { + RoomConfig { + feature_size: 12, + map_type: "+rnd+".to_string(), + map_generator: 0, + maze_size: 0, + seed: "seed".to_string(), + template: 0, + + ammo: Ammo {name: "Default".to_string(), settings: None }, + scheme: Scheme {name: "Default".to_string(), settings: Vec::new() }, + script: "Normal".to_string(), + theme: "\u{1f994}".to_string(), + drawn_map: None + } + } +} + +fn client_teams_impl(teams: &[(ClientId, TeamInfo)], client_id: ClientId) + -> impl Iterator + Clone +{ + teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t) +} + +fn map_config_from(c: &RoomConfig) -> Vec { + vec![c.feature_size.to_string(), c.map_type.to_string(), + c.map_generator.to_string(), c.maze_size.to_string(), + c.seed.to_string(), c.template.to_string()] +} + +fn game_config_from(c: &RoomConfig) -> Vec { + use crate::server::coretypes::GameCfg::*; + let mut v = vec![ + Ammo(c.ammo.name.to_string(), c.ammo.settings.clone()), + Scheme(c.scheme.name.to_string(), c.scheme.settings.clone()), + Script(c.script.to_string()), + Theme(c.theme.to_string())]; + if let Some(ref m) = c.drawn_map { + v.push(DrawnMap(m.to_string())) + } + v +} + +pub struct GameInfo { + pub teams_in_game: u8, + pub teams_at_start: Vec<(ClientId, TeamInfo)>, + pub left_teams: Vec, + pub msg_log: Vec, + pub sync_msg: Option, + pub is_paused: bool, + config: RoomConfig +} + +impl GameInfo { + fn new(teams: Vec<(ClientId, TeamInfo)>, config: RoomConfig) -> GameInfo { + GameInfo { + left_teams: Vec::new(), + msg_log: Vec::new(), + sync_msg: None, + is_paused: false, + teams_in_game: teams.len() as u8, + teams_at_start: teams, + config + } + } + + pub fn client_teams(&self, client_id: ClientId) -> impl Iterator + Clone { + client_teams_impl(&self.teams_at_start, client_id) + } +} + +#[derive(Serialize, Deserialize)] +pub struct RoomSave { + pub location: String, + config: RoomConfig +} + +bitflags!{ + pub struct RoomFlags: u8 { + const FIXED = 0b0000_0001; + const RESTRICTED_JOIN = 0b0000_0010; + const RESTRICTED_TEAM_ADD = 0b0000_0100; + const RESTRICTED_UNREGISTERED_PLAYERS = 0b0000_1000; + } +} + +pub struct HWRoom { + pub id: RoomId, + pub master_id: Option, + pub name: String, + pub password: Option, + pub greeting: String, + pub protocol_number: u16, + pub flags: RoomFlags, + + pub players_number: u8, + pub default_hedgehog_number: u8, + pub team_limit: u8, + pub ready_players_number: u8, + pub teams: Vec<(ClientId, TeamInfo)>, + config: RoomConfig, + pub voting: Option, + pub saves: HashMap, + pub game_info: Option +} + +impl HWRoom { + pub fn new(id: RoomId) -> HWRoom { + HWRoom { + id, + master_id: None, + name: String::new(), + password: None, + greeting: "".to_string(), + flags: RoomFlags::empty(), + protocol_number: 0, + players_number: 0, + default_hedgehog_number: 4, + team_limit: MAX_TEAMS_IN_ROOM, + ready_players_number: 0, + teams: Vec::new(), + config: RoomConfig::new(), + voting: None, + saves: HashMap::new(), + game_info: None + } + } + + pub fn hedgehogs_number(&self) -> u8 { + self.teams.iter().map(|(_, t)| t.hedgehogs_number).sum() + } + + pub fn addable_hedgehogs(&self) -> u8 { + MAX_HEDGEHOGS_IN_ROOM - self.hedgehogs_number() + } + + pub fn add_team(&mut self, owner_id: ClientId, mut team: TeamInfo, preserve_color: bool) -> &TeamInfo { + if !preserve_color { + team.color = iter::repeat(()).enumerate() + .map(|(i, _)| i as u8).take(u8::max_value() as usize + 1) + .find(|i| self.teams.iter().all(|(_, t)| t.color != *i)) + .unwrap_or(0u8) + }; + team.hedgehogs_number = if self.teams.is_empty() { + self.default_hedgehog_number + } else { + self.teams[0].1.hedgehogs_number.min(self.addable_hedgehogs()) + }; + self.teams.push((owner_id, team)); + &self.teams.last().unwrap().1 + } + + pub fn remove_team(&mut self, name: &str) { + if let Some(index) = self.teams.iter().position(|(_, t)| t.name == name) { + self.teams.remove(index); + } + } + + pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec { + let mut names = Vec::new(); + let teams = match self.game_info { + Some(ref mut info) => &mut info.teams_at_start, + None => &mut self.teams + }; + + if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM { + for (_, team) in teams.iter_mut() { + team.hedgehogs_number = n; + names.push(team.name.clone()) + }; + self.default_hedgehog_number = n; + } + names + } + + pub fn find_team_and_owner_mut(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)> + where F: Fn(&TeamInfo) -> bool { + self.teams.iter_mut().find(|(_, t)| f(t)).map(|(id, t)| (*id, t)) + } + + pub fn find_team(&self, f: F) -> Option<&TeamInfo> + where F: Fn(&TeamInfo) -> bool { + self.teams.iter().find_map(|(_, t)| Some(t).filter(|t| f(&t))) + } + + pub fn client_teams(&self, client_id: ClientId) -> impl Iterator { + client_teams_impl(&self.teams, client_id) + } + + pub fn client_team_indices(&self, client_id: ClientId) -> Vec { + self.teams.iter().enumerate() + .filter(move |(_, (id, _))| *id == client_id) + .map(|(i, _)| i as u8).collect() + } + + pub fn find_team_owner(&self, team_name: &str) -> Option<(ClientId, &str)> { + self.teams.iter().find(|(_, t)| t.name == team_name) + .map(|(id, t)| (*id, &t.name[..])) + } + + pub fn find_team_color(&self, owner_id: ClientId) -> Option { + self.client_teams(owner_id).nth(0).map(|t| t.color) + } + + pub fn has_multiple_clans(&self) -> bool { + self.teams.iter().min_by_key(|(_, t)| t.color) != + self.teams.iter().max_by_key(|(_, t)| t.color) + } + + pub fn set_config(&mut self, cfg: GameCfg) { + let c = &mut self.config; + match cfg { + FeatureSize(s) => c.feature_size = s, + MapType(t) => c.map_type = t, + MapGenerator(g) => c.map_generator = g, + MazeSize(s) => c.maze_size = s, + Seed(s) => c.seed = s, + Template(t) => c.template = t, + + Ammo(n, s) => c.ammo = Ammo {name: n, settings: s}, + Scheme(n, s) => c.scheme = Scheme {name: n, settings: s}, + Script(s) => c.script = s, + Theme(t) => c.theme = t, + DrawnMap(m) => c.drawn_map = Some(m) + }; + } + + pub fn start_round(&mut self) { + if self.game_info.is_none() { + self.game_info = Some(GameInfo::new( + self.teams.clone(), self.config.clone())); + } + } + + pub fn is_fixed(&self) -> bool { + self.flags.contains(RoomFlags::FIXED) + } + pub fn is_join_restricted(&self) -> bool { + self.flags.contains(RoomFlags::RESTRICTED_JOIN) + } + pub fn is_team_add_restricted(&self) -> bool { + self.flags.contains(RoomFlags::RESTRICTED_TEAM_ADD) + } + pub fn are_unregistered_players_restricted(&self) -> bool { + self.flags.contains(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS) + } + + pub fn set_is_fixed(&mut self, value: bool) { + self.flags.set(RoomFlags::FIXED, value) + } + pub fn set_join_restriction(&mut self, value: bool) { + self.flags.set(RoomFlags::RESTRICTED_JOIN, value) + } + pub fn set_team_add_restriction(&mut self, value: bool) { + self.flags.set(RoomFlags::RESTRICTED_TEAM_ADD, value) + } + pub fn set_unregistered_players_restriction(&mut self, value: bool) { + self.flags.set(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, value) + } + + fn flags_string(&self) -> String { + let mut result = "-".to_string(); + if self.game_info.is_some() { result += "g" } + if self.password.is_some() { result += "p" } + if self.is_join_restricted() { result += "j" } + if self.are_unregistered_players_restricted() { + result += "r" + } + result + } + + pub fn info(&self, master: Option<&HWClient>) -> Vec { + let c = &self.config; + vec![ + self.flags_string(), + self.name.clone(), + self.players_number.to_string(), + self.teams.len().to_string(), + master.map_or("[]", |c| &c.nick).to_string(), + c.map_type.to_string(), + c.script.to_string(), + c.scheme.name.to_string(), + c.ammo.name.to_string() + ] + } + + pub fn map_config(&self) -> Vec { + match self.game_info { + Some(ref info) => map_config_from(&info.config), + None => map_config_from(&self.config) + } + } + + pub fn game_config(&self) -> Vec { + match self.game_info { + Some(ref info) => game_config_from(&info.config), + None => game_config_from(&self.config) + } + } + + pub fn save_config(&mut self, name: String, location: String) { + self.saves.insert(name, RoomSave { location, config: self.config.clone() }); + } + + pub fn load_config(&mut self, name: &str) -> Option<&str> { + if let Some(save) = self.saves.get(name) { + self.config = save.config.clone(); + Some(&save.location[..]) + } else { + None + } + } + + pub fn delete_config(&mut self, name: &str) -> bool { + self.saves.remove(name).is_some() + } + + pub fn get_saves(&self) -> Result { + serde_yaml::to_string(&(&self.greeting, &self.saves)) + } + + pub fn set_saves(&mut self, text: &str) -> Result<(), serde_yaml::Error> { + serde_yaml::from_str::<(String, HashMap)>(text).map(|(greeting, saves)| { + self.greeting = greeting; + self.saves = saves; + }) + } + + pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec { + let mut info = vec![ + team.name.clone(), + team.grave.clone(), + team.fort.clone(), + team.voice_pack.clone(), + team.flag.clone(), + owner.nick.clone(), + team.difficulty.to_string()]; + let hogs = team.hedgehogs.iter().flat_map(|h| + iter::once(h.name.clone()).chain(iter::once(h.hat.clone()))); + info.extend(hogs); + info + } +} \ No newline at end of file diff -r 6843c4551cde -r 06672690d71b rust/hedgewars-server/src/utils.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/utils.rs Mon Dec 10 22:44:46 2018 +0100 @@ -0,0 +1,67 @@ +use std::iter::Iterator; +use mio; +use base64::{encode}; + +pub const PROTOCOL_VERSION : u32 = 3; +pub const SERVER: mio::Token = mio::Token(1_000_000_000); + +pub fn is_name_illegal(name: &str ) -> bool{ + name.len() > 40 || + name.trim().is_empty() || + name.chars().any(|c| + "$()*+?[]^{|}\x7F".contains(c) || + '\x00' <= c && c <= '\x1F') +} + +pub fn to_engine_msg(msg: T) -> String + where T: Iterator + Clone +{ + let mut tmp = Vec::new(); + tmp.push(msg.clone().count() as u8); + tmp.extend(msg); + encode(&tmp) +} + +pub fn protocol_version_string(protocol_number: u16) -> &'static str { + match protocol_number { + 17 => "0.9.7-dev", + 19 => "0.9.7", + 20 => "0.9.8-dev", + 21 => "0.9.8", + 22 => "0.9.9-dev", + 23 => "0.9.9", + 24 => "0.9.10-dev", + 25 => "0.9.10", + 26 => "0.9.11-dev", + 27 => "0.9.11", + 28 => "0.9.12-dev", + 29 => "0.9.12", + 30 => "0.9.13-dev", + 31 => "0.9.13", + 32 => "0.9.14-dev", + 33 => "0.9.14", + 34 => "0.9.15-dev", + 35 => "0.9.14.1", + 37 => "0.9.15", + 38 => "0.9.16-dev", + 39 => "0.9.16", + 40 => "0.9.17-dev", + 41 => "0.9.17", + 42 => "0.9.18-dev", + 43 => "0.9.18", + 44 => "0.9.19-dev", + 45 => "0.9.19", + 46 => "0.9.20-dev", + 47 => "0.9.20", + 48 => "0.9.21-dev", + 49 => "0.9.21", + 50 => "0.9.22-dev", + 51 => "0.9.22", + 52 => "0.9.23-dev", + 53 => "0.9.23", + 54 => "0.9.24-dev", + 55 => "0.9.24", + 56 => "0.9.25-dev", + _ => "Unknown" + } +} \ No newline at end of file