# HG changeset patch # User alfadur # Date 1529328173 14400 # Node ID cdf69667593b844457cc4512439b7f6b2f710911 # Parent 0eedc17055a0d615fb040bbaedfcd8349c5db7af partial room implementation diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/protocol/messages.rs --- a/gameServer2/src/protocol/messages.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/protocol/messages.rs Mon Jun 18 09:22:53 2018 -0400 @@ -24,7 +24,7 @@ List, Chat(String), CreateRoom(String, Option), - Join(String, Option), + JoinRoom(String, Option), Follow(String), Rnd(Vec), Kick(String), @@ -69,105 +69,103 @@ Empty, } +#[derive(Debug)] pub enum HWServerMessage { Ping, Pong, Bye(String), Nick(String), - LobbyLeft(String), + Proto(u32), + LobbyLeft(String, String), LobbyJoined(Vec), ChatMsg(String, String), ClientFlags(String, Vec), + Rooms(Vec), + RoomAdd(Vec), + RoomJoined(Vec), + RoomLeft(String, String), + RoomRemove(String), + RoomUpdated(String, Vec), + ServerMessage(String), Warning(String), + Error(String), Connected(u32), Unreachable, } -fn construct_message(msg: & [&str]) -> String { - let mut m = String::with_capacity(64); - - for s in msg { - m.push_str(s); - m.push('\n'); - } - m.push('\n'); - - m -} - impl<'a> HWProtocolMessage { pub fn to_raw_protocol(&self) -> String { use self::HWProtocolMessage::*; - match *self { + match self { Ping => "PING\n\n".to_string(), Pong => "PONG\n\n".to_string(), Quit(None) => format!("QUIT\n\n"), - Quit(Some(ref msg)) => format!("QUIT\n{}\n\n", msg), - Global(ref msg) => format!("CMD\nGLOBAL\n{}\n\n", msg), - Watch(ref name) => format!("CMD\nWATCH\n{}\n\n", name), + Quit(Some(msg)) => format!("QUIT\n{}\n\n", msg), + Global(msg) => format!("CMD\nGLOBAL\n{}\n\n", msg), + Watch(name) => format!("CMD\nWATCH\n{}\n\n", name), ToggleServerRegisteredOnly => "CMD\nREGISTERED_ONLY\n\n".to_string(), SuperPower => "CMD\nSUPER_POWER\n\n".to_string(), - Info(ref info) => format!("CMD\nINFO\n{}\n\n", info), - Nick(ref nick) => format!("NICK\n{}\n\n", nick), + Info(info) => format!("CMD\nINFO\n{}\n\n", info), + Nick(nick) => format!("NICK\n{}\n\n", nick), Proto(version) => format!("PROTO\n{}\n\n", version), - Password(ref p, ref s) => format!("PASSWORD\n{}\n{}\n\n", p, s), //? - Checker(i, ref n, ref p) => + Password(p, s) => format!("PASSWORD\n{}\n{}\n\n", p, s), //? + Checker(i, n, p) => format!("CHECKER\n{}\n{}\n{}\n\n", i, n, p), //?, List => "LIST\n\n".to_string(), - Chat(ref msg) => format!("CHAT\n{}\n\n", msg), - CreateRoom(ref name, None) => + Chat(msg) => format!("CHAT\n{}\n\n", msg), + CreateRoom(name, None) => format!("CREATE_ROOM\n{}\n\n", name), - CreateRoom(ref name, Some(ref password)) => + CreateRoom(name, Some(password)) => format!("CREATE_ROOM\n{}\n{}\n\n", name, password), - Join(ref name, None) => + JoinRoom(name, None) => format!("JOIN\n{}\n\n", name), - Join(ref name, Some(ref arg)) => + JoinRoom(name, Some(arg)) => format!("JOIN\n{}\n{}\n\n", name, arg), - Follow(ref name) => + Follow(name) => format!("FOLLOW\n{}\n\n", name), //Rnd(Vec), ??? - Kick(ref name) => format!("KICK\n{}\n\n", name), - Ban(ref name, ref reason, time) => + Kick(name) => format!("KICK\n{}\n\n", name), + Ban(name, reason, time) => format!("BAN\n{}\n{}\n{}\n\n", name, reason, time), - BanIP(ref ip, ref reason, time) => + BanIP(ip, reason, time) => format!("BAN_IP\n{}\n{}\n{}\n\n", ip, reason, time), - BanNick(ref nick, ref reason, time) => + BanNick(nick, reason, time) => format!("BAN_NICK\n{}\n{}\n{}\n\n", nick, reason, time), BanList => "BANLIST\n\n".to_string(), - Unban(ref name) => format!("UNBAN\n{}\n\n", name), + Unban(name) => format!("UNBAN\n{}\n\n", name), //SetServerVar(ServerVar), ??? GetServerVar => "GET_SERVER_VAR\n\n".to_string(), RestartServer => "CMD\nRESTART_SERVER\nYES\n\n".to_string(), Stats => "CMD\nSTATS\n\n".to_string(), Part(None) => "CMD\nPART\n\n".to_string(), - Part(Some(ref msg)) => format!("CMD\nPART\n{}\n\n", msg), + Part(Some(msg)) => format!("CMD\nPART\n{}\n\n", msg), //Cfg(GameCfg) ?? //AddTeam(TeamInfo) ??, - RemoveTeam(ref name) => format!("REMOVE_TEAM\n{}\n\n", name), + RemoveTeam(name) => format!("REMOVE_TEAM\n{}\n\n", name), //SetHedgehogsNumber(String, u8), ?? //SetTeamColor(String, u8), ?? ToggleReady => "TOGGLE_READY\n\n".to_string(), StartGame => "START_GAME\n\n".to_string(), - EngineMessage(ref msg) => format!("EM\n{}\n\n", msg), + EngineMessage(msg) => format!("EM\n{}\n\n", msg), RoundFinished => "ROUNDFINISHED\n\n".to_string(), ToggleRestrictJoin => "TOGGLE_RESTRICT_JOINS\n\n".to_string(), ToggleRestrictTeams => "TOGGLE_RESTRICT_TEAMS\n\n".to_string(), ToggleRegisteredOnly => "TOGGLE_REGISTERED_ONLY\n\n".to_string(), - RoomName(ref name) => format!("ROOM_NAME\n{}\n\n", name), - Delegate(ref name) => format!("CMD\nDELEGATE\n{}\n\n", name), - TeamChat(ref msg) => format!("TEAMCHAT\n{}\n\n", msg), + RoomName(name) => format!("ROOM_NAME\n{}\n\n", name), + Delegate(name) => format!("CMD\nDELEGATE\n{}\n\n", name), + TeamChat(msg) => format!("TEAMCHAT\n{}\n\n", msg), MaxTeams(count) => format!("CMD\nMAXTEAMS\n{}\n\n", count) , Fix => "CMD\nFIX\n\n".to_string(), Unfix => "CMD\nUNFIX\n\n".to_string(), - Greeting(ref msg) => format!("CMD\nGREETING\n{}\n\n", msg), + Greeting(msg) => format!("CMD\nGREETING\n{}\n\n", msg), //CallVote(Option<(String, Option)>) =>, ?? - Vote(ref msg) => format!("CMD\nVOTE\n{}\n\n", msg), - ForceVote(ref msg) => format!("CMD\nFORCE\n{}\n\n", msg), + Vote(msg) => format!("CMD\nVOTE\n{}\n\n", msg), + ForceVote(msg) => format!("CMD\nFORCE\n{}\n\n", msg), //Save(String, String), ?? - Delete(ref room) => format!("CMD\nDELETE\n{}\n\n", room), - SaveRoom(ref room) => format!("CMD\nSAVEROOM\n{}\n\n", room), - LoadRoom(ref room) => format!("CMD\nLOADROOM\n{}\n\n", room), + Delete(room) => format!("CMD\nDELETE\n{}\n\n", room), + SaveRoom(room) => format!("CMD\nSAVEROOM\n{}\n\n", room), + LoadRoom(room) => format!("CMD\nLOADROOM\n{}\n\n", room), Malformed => "A\nQUICK\nBROWN\nHOG\nJUMPS\nOVER\nTHE\nLAZY\nDOG\n\n".to_string(), Empty => "\n\n".to_string(), _ => panic!("Protocol message not yet implemented") @@ -175,40 +173,74 @@ } } +macro_rules! const_braces { + ($e: expr) => { "{}\n" } +} + +macro_rules! msg { + [$($part: expr),*] => { + format!(concat!($(const_braces!($part)),*, "\n"), $($part),*); + }; +} + +fn construct_message(mut msg: Vec<&str>) -> String { + msg.push("\n"); + msg.join("\n") +} + impl HWServerMessage { pub fn to_raw_protocol(&self) -> String { use self::HWServerMessage::*; match self { - &Ping => "PING\n\n".to_string(), - &Pong => "PONG\n\n".to_string(), - &Connected(protocol_version) - => construct_message(&[ - "CONNECTED", - "Hedgewars server http://www.hedgewars.org/", - &protocol_version.to_string() - ]), - &Bye(ref msg) => construct_message(&["BYE", &msg]), - &Nick(ref nick) => construct_message(&["NICK", &nick]), - &LobbyLeft(ref nick) - => construct_message(&["LOBBY_LEFT", &nick]), - &LobbyJoined(ref nicks) - => { + Ping => msg!["PING"], + Pong => msg!["PONG"], + Connected(protocol_version) => msg![ + "CONNECTED", + "Hedgewars server http://www.hedgewars.org/", + protocol_version], + Bye(msg) => msg!["BYE", msg], + Nick(nick) => msg!["NICK", nick], + Proto(proto) => msg!["PROTO", proto], + LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg], + LobbyJoined(nicks) => { let mut v = vec!["LOBBY:JOINED"]; v.extend(nicks.iter().map(|n| { &n[..] })); - construct_message(&v) + construct_message(v) }, - &ClientFlags(ref flags, ref nicks) + ClientFlags(flags, nicks) => { let mut v = vec!["CLIENT_FLAGS"]; v.push(&flags[..]); v.extend(nicks.iter().map(|n| { &n[..] })); - construct_message(&v) + construct_message(v) + }, + Rooms(info) => { + let mut v = vec!["ROOMS"]; + v.extend(info.iter().map(|n| { &n[..] })); + construct_message(v) + }, + RoomAdd(info) => { + let mut v = vec!["ROOM", "ADD"]; + v.extend(info.iter().map(|n| { &n[..] })); + construct_message(v) }, - &ChatMsg(ref nick, ref msg) - => construct_message(&["CHAT", &nick, &msg]), - &Warning(ref msg) - => construct_message(&["WARNING", &msg]), - _ => construct_message(&["ERROR", "UNIMPLEMENTED"]), + RoomJoined(nicks) => { + let mut v = vec!["JOINED"]; + v.extend(nicks.iter().map(|n| { &n[..] })); + construct_message(v) + }, + RoomLeft(nick, msg) => msg!["LEFT", nick, msg], + RoomRemove(name) => msg!["ROOM", "DEL", name], + RoomUpdated(name, info) => { + let mut v = vec!["ROOM", "UPD", name]; + v.extend(info.iter().map(|n| { &n[..] })); + construct_message(v) + } + ChatMsg(nick, msg) => msg!["CHAT", nick, msg], + ServerMessage(msg) => msg!["SERVER_MESSAGE", msg], + Warning(msg) => msg!["WARNING", msg], + Error(msg) => msg!["ERROR", msg], + _ => msg!["ERROR", "UNIMPLEMENTED"], } } } diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/protocol/mod.rs --- a/gameServer2/src/protocol/mod.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/protocol/mod.rs Mon Jun 18 09:22:53 2018 -0400 @@ -4,6 +4,7 @@ use nom::IResult; pub mod messages; +pub mod test; mod parser; pub struct ProtocolDecoder { diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/protocol/parser.rs --- a/gameServer2/src/protocol/parser.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/protocol/parser.rs Mon Jun 18 09:22:53 2018 -0400 @@ -1,15 +1,13 @@ use nom::*; -use std::str; -use std::str::FromStr; -use super::messages::HWProtocolMessage; -use super::messages::HWProtocolMessage::*; - -use proptest::test_runner::{TestRunner, Reason}; -use proptest::arbitrary::{any, any_with, Arbitrary, StrategyFor}; -use proptest::strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree}; -use proptest::string::RegexGeneratorValueTree; -use std::ops::Range; +use std::{ + str, str::FromStr, + ops::Range +}; +use super::{ + messages::{HWProtocolMessage, HWProtocolMessage::*}, + test::gen_proto_msg +}; named!(end_of_message, tag!("\n\n")); named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); @@ -36,6 +34,7 @@ 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))) @@ -85,10 +84,10 @@ n: a_line >> p: opt_param >> (CreateRoom(n, p))) - | do_parse!(tag!("JOIN") >> eol >> + | do_parse!(tag!("JOIN_ROOM") >> eol >> n: a_line >> p: opt_param >> - (Join(n, p))) + (JoinRoom(n, p))) | do_parse!(tag!("BAN") >> eol >> n: a_line >> eol >> r: a_line >> eol >> @@ -127,130 +126,6 @@ named!(pub extract_messages<&[u8], Vec >, many0!(complete!(message))); -// 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 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: ident, $($num: expr => $constr: ident $res: tt),*) => ( - match $var { - $($num => (proto_msg_case!($constr $res)).boxed()),*, - _ => Just($default).boxed() - } - ) -} - -#[derive(Debug)] -struct Ascii(String); - -struct AsciiValueTree(RegexGeneratorValueTree); - -impl ValueTree for AsciiValueTree { - type Value = Ascii; - - fn current(&self) -> Self::Value { Ascii(self.0.current()) } - fn simplify(&mut self) -> bool { self.0.simplify() } - fn complicate(&mut self) -> bool { self.0.complicate() } -} - -impl Arbitrary for Ascii { - type Parameters = ::Parameters; - - fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { - any_with::(args) - .prop_filter("not ascii", |s| { - s.len() > 0 && s.is_ascii() && - s.find(|c| { - ['\0', '\n', '\x20'].contains(&c) - }).is_none()}) - .prop_map(Ascii) - .boxed() - } - - type Strategy = BoxedStrategy; - type ValueTree = Box>; -} - -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(u32), - 11 => Password(Ascii, Ascii), - 12 => Checker(u32, Ascii, Ascii), - 13 => List(), - 14 => Chat(Ascii), - 15 => CreateRoom(Ascii, Option), - 16 => Join(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(TeamInfo), - 32 => RemoveTeam(Ascii), - //33 => SetHedgehogsNumber(String, u8), - //34 => SetTeamColor(String, 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(String), - 51 => ForceVote(Ascii), - //52 => Save(String, String), - 53 => Delete(Ascii), - 54 => SaveRoom(Ascii), - 55 => LoadRoom(Ascii), - 56 => Malformed(), - 57 => Empty() - )}); - res.boxed() -} - proptest! { #[test] fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/actions.rs --- a/gameServer2/src/server/actions.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/actions.rs Mon Jun 18 09:22:53 2018 -0400 @@ -1,40 +1,75 @@ -use mio; -use std::io::Write; -use std::io; - -use super::server::HWServer; -use super::room::HWRoom; -use protocol::messages::HWProtocolMessage; -use protocol::messages::HWServerMessage; -use protocol::messages::HWServerMessage::*; -use super::handlers; +use std::{ + io, io::Write +}; +use super::{ + server::HWServer, + client::ClientId, + room::HWRoom, + handlers +}; +use protocol::messages::{ + HWProtocolMessage, + HWServerMessage, + HWServerMessage::* +}; pub enum Action { + SendAll(HWServerMessage), SendMe(HWServerMessage), SendAllButMe(HWServerMessage), + SendToSelected(Vec, HWServerMessage), RemoveClient, ByeClient(String), ReactProtocolMessage(HWProtocolMessage), CheckRegistered, JoinLobby, AddRoom(String, Option), + RemoveRoom(RoomId), + MoveToRoom(RoomId), + MoveToLobby(String), + ChangeMaster(RoomId, Option), + SendRoomUpdate(Option), Warn(String), + ProtocolError(String) } use self::Action::*; +use server::room::RoomId; pub fn run_action(server: &mut HWServer, token: usize, action: Action) { match action { + SendAll(msg) => + server.send_all(msg), SendMe(msg) => server.send_self(token, msg), - SendAllButMe(msg) => { - server.send_others(token, msg) - }, + SendAllButMe(msg) => + server.send_others(token, msg), + SendToSelected(client_ids, msg) => + server.send_to_selected(client_ids, msg), ByeClient(msg) => { + let room_id; + let nick; + { + let c = &server.clients[token]; + room_id = c.room_id; + nick = c.nick.clone(); + } + + let action = room_id.map (|id| { + if id == server.lobby_id { + SendAll(LobbyLeft(nick, msg.clone())) + } else { + MoveToLobby(format!("quit: {}", msg.clone())) + } + }); + + if let Some(action) = action { + server.react(token, vec![action]); + } + server.react(token, vec![ SendMe(Bye(msg)), - RemoveClient, - ]); + RemoveClient]); }, RemoveClient => { server.removed_clients.push(token); @@ -64,26 +99,154 @@ joined_msg = LobbyJoined(lobby_nicks); } let everyone_msg = LobbyJoined(vec![server.clients[token].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(token, vec![ SendAllButMe(everyone_msg), SendMe(joined_msg), + SendMe(flags_msg), + SendMe(server_msg), + SendMe(rooms_msg), ]); }, AddRoom(name, password) => { + let room_protocol; + let room_info; let room_id = server.add_room();; { let r = &mut server.rooms[room_id]; let c = &mut server.clients[token]; + r.master_id = Some(c.id); r.name = name; r.password = password; - r.ready_players_number = 1; r.protocol_number = c.protocol_number; + + room_protocol = r.protocol_number; + room_info = r.info(Some(&c)); + } + let protocol_client_ids = server.protocol_clients(room_protocol); + server.react(token, vec![ + SendToSelected(protocol_client_ids, RoomAdd(room_info)), + MoveToRoom(room_id)]); + }, + RemoveRoom(room_id) => { + let room_protocol; + let room_name; + { + let r = &mut server.rooms[room_id]; + room_protocol = r.protocol_number; + room_name = r.name.clone(); + } + server.rooms.remove(room_id); + let protocol_client_ids = server.protocol_clients(room_protocol); + server.react(token, vec![ + SendToSelected(protocol_client_ids, RoomRemove(room_name))]); + } + MoveToRoom(room_id) => { + let flags_msg; + let nick; + { + let r = &mut server.rooms[room_id]; + let c = &mut server.clients[token]; + r.players_number += 1; c.room_id = Some(room_id); + c.is_joined_mid_game = false; + if r.master_id == Some(c.id) { + r.ready_players_number += 1; + c.is_master = true; + c.is_ready = true; + } else { + c.is_ready = false; + c.is_master = false; + } + flags_msg = ClientFlags("+i".to_string(), vec![c.nick.clone()]); + nick = c.nick.clone(); } + let rooms_client_ids = server.room_clients(room_id); + server.react(token, vec![ + SendToSelected(rooms_client_ids, RoomJoined(vec![nick])), + SendAll(flags_msg), + SendRoomUpdate(None)]); }, + MoveToLobby(msg) => { + let mut actions = Vec::new(); + let other_client_ids = server.other_clients_in_room(token); + let lobby_id = server.lobby_id; + if let (c, Some(r)) = server.client_and_room(token) { + r.players_number -= 1; + if c.is_ready { + r.ready_players_number -= 1; + } + if r.players_number > 0 && c.is_master { + actions.push(ChangeMaster(r.id, None)); + } + actions.push(SendToSelected(other_client_ids, RoomLeft(c.nick.clone(), msg))); + actions.push(SendAll(ClientFlags("-i".to_string(), vec![c.nick.clone()]))); + actions.push(SendRoomUpdate(Some(r.name.clone()))); + } + server.react(token, actions); + actions = Vec::new(); + + if let (c, Some(r)) = server.client_and_room(token) { + c.room_id = Some(lobby_id); + if r.players_number == 0 { + actions.push(RemoveRoom(r.id)); + } + } + server.react(token, actions) + } + ChangeMaster(room_id, new_id) => { + let mut actions = Vec::new(); + let room_client_ids = server.room_clients(room_id); + let new_id = new_id.or_else(|| + room_client_ids.iter().find(|id| **id != token).map(|id| *id)); + let new_nick = new_id.map(|id| server.clients[id].nick.clone()); + + if let (c, Some(r)) = server.client_and_room(token) { + if let Some(id) = r.master_id { + c.is_master = false; + r.master_id = None; + actions.push(SendToSelected(room_client_ids.clone(), ClientFlags("-h".to_string(), vec![c.nick.clone()]))); + } + r.master_id = new_id; + if let Some(nick) = new_nick { + actions.push(SendToSelected(room_client_ids, ClientFlags("+h".to_string(), vec![nick]))); + } + } + new_id.map(|id| server.clients[id].is_master = true); + server.react(token, actions); + } + SendRoomUpdate(old_name) => { + let room_data = + if let (c, Some(r)) = server.client_and_room(token) { + let name = old_name.unwrap_or_else(|| r.name.clone()); + Some((name, r.protocol_number, r.info(Some(&c)))) + } else { + None + }; + + if let Some((room_name, protocol, room_info)) = room_data { + let protocol_clients = server.protocol_clients(protocol); + server.react(token, + vec![SendToSelected(protocol_clients, RoomUpdated(room_name, room_info))]); + } + } + Warn(msg) => { run_action(server, token,SendMe(Warning(msg))); } - //_ => unimplemented!(), + ProtocolError(msg) => { + run_action(server, token, SendMe(Error(msg))) + } } } diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/handlers/inroom.rs --- a/gameServer2/src/server/handlers/inroom.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/handlers/inroom.rs Mon Jun 18 09:22:53 2018 -0400 @@ -1,13 +1,52 @@ use mio; -use server::server::HWServer; -use server::actions::Action; -use server::actions::Action::*; -use protocol::messages::HWProtocolMessage; -use protocol::messages::HWServerMessage::*; +use server::{ + server::HWServer, + actions::{Action, Action::*} +}; +use protocol::messages::{ + HWProtocolMessage, + HWServerMessage::* +}; +use utils::is_name_illegal; +use std::mem::swap; pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) { + use protocol::messages::HWProtocolMessage::*; match message { + Part(None) => server.react(token, vec![ + MoveToLobby("part".to_string())]), + Part(Some(msg)) => server.react(token, vec![ + MoveToLobby(format!("part: {}", msg))]), + Chat(msg) => { + let chat_msg; + let room_id; + { + let c = &mut server.clients[token]; + chat_msg = ChatMsg(c.nick.clone(), msg); + room_id = c.room_id; + } + let client_ids = server.other_clients_in_room(token); + server.react(token, vec![ + SendToSelected(client_ids, chat_msg)]); + }, + 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.has_room(&new_name) { + vec![Warn("A room with the same name already exists.".to_string())] + } else { + let mut old_name = new_name.clone(); + if let (c, Some(r)) = server.client_and_room(token) { + swap(&mut r.name, &mut old_name); + vec![SendRoomUpdate(Some(old_name))] + } else { + Vec::new() + } + }; + server.react(token, actions); + } _ => warn!("Unimplemented!"), } } diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/handlers/lobby.rs --- a/gameServer2/src/server/handlers/lobby.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/handlers/lobby.rs Mon Jun 18 09:22:53 2018 -0400 @@ -1,38 +1,60 @@ use mio; -use server::server::HWServer; -use server::actions::Action; -use server::actions::Action::*; -use protocol::messages::HWProtocolMessage; -use protocol::messages::HWServerMessage::*; +use server::{ + server::HWServer, + actions::{Action, Action::*} +}; +use protocol::messages::{ + HWProtocolMessage, + HWServerMessage::* +}; +use utils::is_name_illegal; pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) { use 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[token].nick.clone()]); + vec![AddRoom(name, password), + SendMe(flags_msg)] + }; + server.react(token, actions); + }, Chat(msg) => { let chat_msg = ChatMsg(server.clients[token].nick.clone(), msg); server.react(token, vec![SendAllButMe(chat_msg)]); }, - CreateRoom(name, password) => { - let room_exists = server.rooms.iter().find(|&(_, r)| r.name == name).is_some(); - if room_exists { - server.react(token, vec![Warn("Room exists".to_string())]); - } else { - let flags_msg = ClientFlags("+hr".to_string(), vec![server.clients[token].nick.clone()]); - { - let c = &mut server.clients[token]; - c.is_master = true; - c.is_ready = true; - c.is_joined_mid_game = false; - } - server.react(token, vec![ - AddRoom(name, password) - , SendMe(flags_msg) - ]); + JoinRoom(name, password) => { + let actions; + { + 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[token]; + actions = match room { + None => vec![Warn("No such room.".to_string())], + Some((_, r)) => { + if c.protocol_number != r.protocol_number { + vec![Warn("Room version incompatible to your Hedgewars version!".to_string())] + } else { + vec![MoveToRoom(r.id), + SendMe(RoomJoined(nicks))] + } + } + }; } - }, - Join(name, password) => { - + server.react(token, actions); }, List => warn!("Deprecated LIST message received"), _ => warn!("Incorrect command in lobby state"), diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/handlers/loggingin.rs --- a/gameServer2/src/server/handlers/loggingin.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/handlers/loggingin.rs Mon Jun 18 09:22:53 2018 -0400 @@ -5,18 +5,47 @@ use server::actions::Action::*; use protocol::messages::HWProtocolMessage; use protocol::messages::HWServerMessage::*; +use utils::is_name_illegal; pub fn handle(server: & mut HWServer, token: usize, message: HWProtocolMessage) { match message { - HWProtocolMessage::Nick(nick) => - if server.clients[token].room_id == None { - server.react(token, vec![SendMe(Nick(nick.clone()))]); - server.clients[token].nick = nick; - server.react(token, vec![CheckRegistered]); - }, + HWProtocolMessage::Nick(nick) => { + let actions; + { + let client = &mut server.clients[token]; + debug!("{} {}", nick, is_name_illegal(&nick)); + 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![SendMe(Nick(nick)), CheckRegistered] + }; + } + server.react(token, actions); + }, HWProtocolMessage::Proto(proto) => { - server.clients[token].protocol_number = proto; - server.react(token, vec![CheckRegistered]); + let actions; + { + let client = &mut server.clients[token]; + 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![SendMe(Proto(proto)), CheckRegistered] + }; + } + server.react(token, actions); }, _ => warn!("Incorrect command in logging-in state"), } diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/network.rs --- a/gameServer2/src/server/network.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/network.rs Mon Jun 18 09:22:53 2018 -0400 @@ -55,7 +55,7 @@ let result = loop { match self.decoder.read_from(&mut self.socket) { Ok(bytes) => { - debug!("Read {} bytes", bytes); + debug!("Client {}: read {} bytes", self.id, bytes); bytes_read += bytes; if bytes == 0 { let result = if bytes_read == 0 { @@ -167,9 +167,17 @@ fn flush_server_messages(&mut self) { debug!("{} pending server messages", self.server.output.len()); for PendingMessage(destination, msg) in self.server.output.drain(..) { + debug!("Message {:?} to {:?}", msg, destination); match destination { + Destination::ToAll => { + let msg_string = msg.to_raw_protocol(); + for (client_id, client) in self.clients.iter_mut() { + client.send_string(&msg_string); + self.pending.insert((client_id, NetworkClientState::NeedsWrite)); + } + }, Destination::ToSelf(id) => { - if let Some(ref mut client) = self.clients.get_mut(id) { + if let Some(client) = self.clients.get_mut(id) { client.send_msg(msg); self.pending.insert((id, NetworkClientState::NeedsWrite)); } @@ -182,6 +190,15 @@ self.pending.insert((client_id, NetworkClientState::NeedsWrite)); } } + }, + Destination::ToSelected(client_ids) => { + let msg_string = msg.to_raw_protocol(); + for id in client_ids { + if let Some(client) = self.clients.get_mut(id) { + client.send_string(&msg_string); + self.pending.insert((id, NetworkClientState::NeedsWrite)); + } + } } } } diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/room.rs --- a/gameServer2/src/server/room.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/room.rs Mon Jun 18 09:22:53 2018 -0400 @@ -1,21 +1,48 @@ +use server::{ + coretypes::TeamInfo, + client::{ClientId, HWClient} +}; + pub type RoomId = usize; pub struct HWRoom { pub id: RoomId, + pub master_id: Option, pub name: String, pub password: Option, pub protocol_number: u32, + + pub players_number: u32, pub ready_players_number: u8, + pub teams: Vec, } impl HWRoom { pub fn new(id: RoomId) -> HWRoom { HWRoom { id, + master_id: None, name: String::new(), password: None, protocol_number: 0, + players_number: 0, ready_players_number: 0, + teams: Vec::new() } } + + pub fn info(&self, master: Option<&HWClient>) -> Vec { + let flags = "-".to_string(); + vec![ + flags, + self.name.clone(), + self.players_number.to_string(), + self.teams.len().to_string(), + master.map_or("?", |c| &c.nick).to_string(), + "Default".to_string(), + "Default".to_string(), + "Default".to_string(), + "Default".to_string(), + ] + } } \ No newline at end of file diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/server/server.rs --- a/gameServer2/src/server/server.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/server/server.rs Mon Jun 18 09:22:53 2018 -0400 @@ -1,20 +1,18 @@ use slab; -use mio::net::*; -use mio::*; -use std::io; - use utils; -use super::client::*; -use super::room::*; -use super::actions; +use super::{ + client::*, room::*, actions, handlers +}; use protocol::messages::*; -use super::handlers; type Slab = slab::Slab; +#[derive(Debug)] pub enum Destination { + ToAll, ToSelf(ClientId), - ToOthers(ClientId) + ToOthers(ClientId), + ToSelected(Vec) } pub struct PendingMessage(pub Destination, pub HWServerMessage); @@ -67,9 +65,15 @@ } pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) { + debug!("Handling message {:?} for client {}", msg, client_id); handlers::handle(self, client_id, msg); } + pub fn send_all(&mut self, msg: HWServerMessage) { + self.output.push(PendingMessage( + Destination::ToAll, msg)); + } + pub fn send_self(&mut self, client_id: ClientId, msg: HWServerMessage) { self.output.push(PendingMessage( Destination::ToSelf(client_id), msg)); @@ -80,9 +84,54 @@ Destination::ToOthers(client_id), msg)); } + pub fn send_to_selected(&mut self, client_ids: Vec, msg: HWServerMessage) { + self.output.push(PendingMessage( + Destination::ToSelected(client_ids), msg)); + } + pub fn react(&mut self, client_id: ClientId, actions: Vec) { for action in actions { actions::run_action(self, client_id, action); } } -} + + 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(|(_, r)| r.name == name).map(|(_, r)| r) + } + + pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HWRoom> { + self.rooms.iter_mut().find(|(_, r)| r.name == name).map(|(_, r)| r) + } + + 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: u32) -> 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) + } + } +} \ No newline at end of file diff -r 0eedc17055a0 -r cdf69667593b gameServer2/src/utils.rs --- a/gameServer2/src/utils.rs Thu Jun 14 16:44:27 2018 -0400 +++ b/gameServer2/src/utils.rs Mon Jun 18 09:22:53 2018 -0400 @@ -2,3 +2,11 @@ pub const PROTOCOL_VERSION : u32 = 3; pub const SERVER: mio::Token = mio::Token(1000000000 + 0); + +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') +} \ No newline at end of file