# HG changeset patch # User alfadur # Date 1520539278 18000 # Node ID 1e39b874907259dfdbb79ad32bea670756512367 # Parent 1ddb8aac5e305c2d845a53103f385d28874fe1fa separated the server logic from all the async io mess. diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/Cargo.toml --- a/gameServer2/Cargo.toml Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/Cargo.toml Thu Mar 08 15:01:18 2018 -0500 @@ -11,3 +11,4 @@ nom = "3.2" env_logger = "0.4" log = "0.3.8" +proptest = "0.5.1" diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/main.rs --- a/gameServer2/src/main.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/main.rs Thu Mar 08 15:01:18 2018 -0500 @@ -1,3 +1,5 @@ +#![allow(unused_imports)] + extern crate rand; extern crate mio; extern crate slab; @@ -7,6 +9,7 @@ #[macro_use] extern crate log; extern crate env_logger; +#[macro_use] extern crate proptest; //use std::io::*; //use rand::Rng; @@ -18,6 +21,8 @@ mod server; mod protocol; +use server::network::NetworkLayer; + fn main() { env_logger::init().unwrap(); @@ -25,10 +30,10 @@ let address = "0.0.0.0:46631".parse().unwrap(); let listener = TcpListener::bind(&address).unwrap(); - let mut server = server::server::HWServer::new(listener, 1024, 512); let poll = Poll::new().unwrap(); - server.register(&poll).unwrap(); + let mut hw_network = NetworkLayer::new(listener, 1024, 512); + hw_network.register_server(&poll).unwrap(); let mut events = Events::with_capacity(1024); @@ -38,14 +43,14 @@ for event in events.iter() { if event.readiness() & Ready::readable() == Ready::readable() { match event.token() { - utils::SERVER => server.accept(&poll).unwrap(), - Token(tok) => server.client_readable(&poll, tok).unwrap(), + 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) => server.client_writable(&poll, tok).unwrap(), + Token(tok) => hw_network.client_writable(&poll, tok).unwrap(), } } // if event.kind().is_hup() || event.kind().is_error() { diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/protocol/messages.rs --- a/gameServer2/src/protocol/messages.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/protocol/messages.rs Thu Mar 08 15:01:18 2018 -0500 @@ -3,7 +3,7 @@ use std::ops; use std::convert::From; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub enum HWProtocolMessage { // core Ping, @@ -69,17 +69,17 @@ Empty, } -pub enum HWServerMessage<'a> { +pub enum HWServerMessage { Ping, Pong, - Bye(&'a str), - Nick(&'a str), - LobbyLeft(&'a str), - LobbyJoined(&'a [&'a str]), - ChatMsg(&'a str, &'a str), - ClientFlags(&'a str, &'a [&'a str]), + Bye(String), + Nick(String), + LobbyLeft(String), + LobbyJoined(Vec), + ChatMsg(String, String), + ClientFlags(String, Vec), - Warning(&'a str), + Warning(String), Connected(u32), Unreachable, } @@ -96,41 +96,117 @@ m } -impl<'a> HWServerMessage<'a> { +impl<'a> HWProtocolMessage { pub fn to_raw_protocol(&self) -> String { + use self::HWProtocolMessage::*; + 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), + 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), + 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) => + 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) => + format!("CREATE_ROOM\n{}\n\n", name), + CreateRoom(ref name, Some(ref password)) => + format!("CREATE_ROOM\n{}\n{}\n\n", name, password), + Join(ref name, None) => + format!("JOIN\n{}\n\n", name), + Join(ref name, Some(ref arg)) => + format!("JOIN\n{}\n{}\n\n", name, arg), + Follow(ref name) => + format!("FOLLOW\n{}\n\n", name), + //Rnd(Vec), ??? + Kick(ref name) => format!("KICK\n{}\n\n", name), + Ban(ref name, ref reason, time) => + format!("BAN\n{}\n{}\n{}\n\n", name, reason, time), + BanIP(ref ip, ref reason, time) => + format!("BAN_IP\n{}\n{}\n{}\n\n", ip, reason, time), + BanNick(ref nick, ref 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), + //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), + //Cfg(GameCfg) ?? + //AddTeam(TeamInfo) ??, + RemoveTeam(ref 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), + 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), + 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), + //CallVote(Option<(String, Option)>) =>, ?? + Vote(ref msg) => format!("CMD\nVOTE\n{}\n\n", msg), + ForceVote(ref 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), + 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") + } + } +} + +impl HWServerMessage { + pub fn to_raw_protocol(&self) -> String { + use self::HWServerMessage::*; match self { - &HWServerMessage::Ping - => "PING\n\n".to_string(), - &HWServerMessage::Pong - => "PONG\n\n".to_string(), - &HWServerMessage::Connected(protocol_version) + &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() ]), - &HWServerMessage::Bye(msg) - => construct_message(&["BYE", &msg]), - &HWServerMessage::Nick(nick) - => construct_message(&["NICK", &nick]), - &HWServerMessage::LobbyLeft(nick) + &Bye(ref msg) => construct_message(&["BYE", &msg]), + &Nick(ref nick) => construct_message(&["NICK", &nick]), + &LobbyLeft(ref nick) => construct_message(&["LOBBY_LEFT", &nick]), - &HWServerMessage::LobbyJoined(nicks) + &LobbyJoined(ref nicks) => { let mut v = vec!["LOBBY:JOINED"]; - v.extend_from_slice(nicks); + v.extend(nicks.iter().map(|n| { &n[..] })); construct_message(&v) }, - &HWServerMessage::ClientFlags(flags, nicks) - => { + &ClientFlags(ref flags, ref nicks) + => { let mut v = vec!["CLIENT_FLAGS"]; - v.push(flags); - v.extend_from_slice(nicks); + v.push(&flags[..]); + v.extend(nicks.iter().map(|n| { &n[..] })); construct_message(&v) }, - &HWServerMessage::ChatMsg(nick, msg) + &ChatMsg(ref nick, ref msg) => construct_message(&["CHAT", &nick, &msg]), - &HWServerMessage::Warning(msg) + &Warning(ref msg) => construct_message(&["WARNING", &msg]), _ => construct_message(&["ERROR", "UNIMPLEMENTED"]), } diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/protocol/parser.rs --- a/gameServer2/src/protocol/parser.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/protocol/parser.rs Thu Mar 08 15:01:18 2018 -0500 @@ -5,6 +5,12 @@ 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; + 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)); @@ -50,8 +56,8 @@ | do_parse!(tag_no_case!("RESTART_SERVER") >> eol >> 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") >> eol >> m: opt_param >> (Quit(m))) - | do_parse!(tag_no_case!("QUIT") >> eol >> m: opt_param >> (Part(m))) + | do_parse!(tag_no_case!("PART") >> m: opt_param >> (Part(m))) + | do_parse!(tag_no_case!("QUIT") >> m: opt_param >> (Quit(m))) | do_parse!(tag_no_case!("DELEGATE") >> eol >> n: a_line >> (Delegate(n))) | do_parse!(tag_no_case!("SAVEROOM") >> eol >> r: a_line >> (SaveRoom(r))) | do_parse!(tag_no_case!("LOADROOM") >> eol >> r: a_line >> (LoadRoom(r))) @@ -121,6 +127,138 @@ 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()) { + println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); + assert_eq!(message(msg.to_raw_protocol().as_bytes()), IResult::Done(&b""[..], msg.clone())) + } +} + #[test] fn parse_test() { assert_eq!(message(b"PING\n\n"), IResult::Done(&b""[..], Ping)); @@ -132,10 +270,13 @@ assert_eq!(message(b"CMD\nwatch\ndemo\n\n"), IResult::Done(&b""[..], Watch("demo".to_string()))); assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), IResult::Done(&b""[..], Ban("me".to_string(), "bad".to_string(), 77))); + assert_eq!(message(b"CMD\nPART\n\n"), IResult::Done(&b""[..], Part(None))); + assert_eq!(message(b"CMD\nPART\n_msg_\n\n"), IResult::Done(&b""[..], Part(Some("_msg_".to_string())))); + assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), IResult::Done(&b""[..], vec![Malformed])); assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), IResult::Done(&b"P"[..], vec![Ping, Ping])); assert_eq!(extract_messages(b"SING\n\nPING\n\n"), IResult::Done(&b""[..], vec![Malformed, Ping])); assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), IResult::Done(&b""[..], vec![Empty, Empty, Ping])); assert_eq!(extract_messages(b"\n\n\nPING\n\n"), IResult::Done(&b""[..], vec![Empty, Empty, Ping])); -} +} \ No newline at end of file diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/actions.rs --- a/gameServer2/src/server/actions.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/actions.rs Thu Mar 08 15:01:18 2018 -0500 @@ -3,14 +3,15 @@ use std::io; use super::server::HWServer; -use super::server::HWRoom; +use super::room::HWRoom; use protocol::messages::HWProtocolMessage; +use protocol::messages::HWServerMessage; use protocol::messages::HWServerMessage::*; use super::handlers; pub enum Action { - SendMe(String), - SendAllButMe(String), + SendMe(HWServerMessage), + SendAllButMe(HWServerMessage), RemoveClient, ByeClient(String), ReactProtocolMessage(HWProtocolMessage), @@ -22,32 +23,30 @@ use self::Action::*; -pub fn run_action(server: &mut HWServer, token: usize, poll: &mio::Poll, action: Action) { +pub fn run_action(server: &mut HWServer, token: usize, action: Action) { match action { SendMe(msg) => - server.send(token, &msg), + server.send_self(token, msg), SendAllButMe(msg) => { - for (_i, c) in server.clients.iter_mut() { - if c.id != token { - c.send_string(&msg) - } - } + server.send_others(token, msg) }, ByeClient(msg) => { - server.react(token, poll, vec![ - SendMe(Bye(&msg).to_raw_protocol()), + server.react(token, vec![ + SendMe(Bye(msg)), RemoveClient, ]); }, RemoveClient => { - server.clients[token].deregister(poll); - server.clients.remove(token); + server.removed_clients.push(token); + if server.clients.contains(token) { + server.clients.remove(token); + } }, ReactProtocolMessage(msg) => - handlers::handle(server, token, poll, msg), + handlers::handle(server, token, msg), CheckRegistered => if server.clients[token].protocol_number > 0 && server.clients[token].nick != "" { - server.react(token, poll, vec![ + server.react(token, vec![ JoinLobby, ]); }, @@ -56,36 +55,34 @@ let joined_msg; { - let mut lobby_nicks: Vec<&str> = Vec::new(); + let mut lobby_nicks = Vec::new(); for (_, c) in server.clients.iter() { if c.room_id.is_some() { - lobby_nicks.push(&c.nick); + lobby_nicks.push(c.nick.clone()); } } - joined_msg = LobbyJoined(&lobby_nicks).to_raw_protocol(); + joined_msg = LobbyJoined(lobby_nicks); } - let everyone_msg = LobbyJoined(&[&server.clients[token].nick]).to_raw_protocol(); - server.react(token, poll, vec![ + let everyone_msg = LobbyJoined(vec![server.clients[token].nick.clone()]); + server.react(token, vec![ SendAllButMe(everyone_msg), SendMe(joined_msg), ]); }, AddRoom(name, password) => { - let room_id = server.rooms.insert(HWRoom::new()); + let room_id = server.add_room();; { let r = &mut server.rooms[room_id]; let c = &mut server.clients[token]; r.name = name; r.password = password; - r.id = room_id.clone(); r.ready_players_number = 1; r.protocol_number = c.protocol_number; c.room_id = Some(room_id); } - }, Warn(msg) => { - run_action(server, token, poll, SendMe(Warning(&msg).to_raw_protocol())); + run_action(server, token,SendMe(Warning(msg))); } //_ => unimplemented!(), } diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/client.rs --- a/gameServer2/src/server/client.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/client.rs Thu Mar 08 15:01:18 2018 -0500 @@ -1,21 +1,7 @@ -use mio::net::TcpStream; -use mio::*; -use std::io::Write; -use std::io; -use netbuf; - -use utils; -use protocol::ProtocolDecoder; -use protocol::messages::*; -use super::actions::Action::*; -use super::actions::Action; +pub type ClientId = usize; pub struct HWClient { - sock: TcpStream, - decoder: ProtocolDecoder, - buf_out: netbuf::Buf, - - pub id: usize, + pub id: ClientId, pub room_id: Option, pub nick: String, pub protocol_number: u32, @@ -25,14 +11,10 @@ } impl HWClient { - pub fn new(sock: TcpStream) -> HWClient { + pub fn new(id: ClientId) -> HWClient { HWClient { - sock: sock, - decoder: ProtocolDecoder::new(), - buf_out: netbuf::Buf::new(), + id, room_id: None, - id: 0, - nick: String::new(), protocol_number: 0, is_master: false, @@ -40,58 +22,4 @@ is_joined_mid_game: false, } } - - pub fn register(&mut self, poll: &Poll, token: Token) { - poll.register(&self.sock, token, Ready::readable() | Ready::writable(), - PollOpt::edge()) - .ok().expect("could not register socket with event loop"); - - self.send_msg(HWServerMessage::Connected(utils::PROTOCOL_VERSION)); - } - - pub fn deregister(&mut self, poll: &Poll) { - poll.deregister(&self.sock) - .ok().expect("could not deregister socket"); - } - - pub fn send_raw_msg(&mut self, msg: &[u8]) { - self.buf_out.write(msg).unwrap(); - self.flush(); - } - - pub fn send_string(&mut self, msg: &String) { - self.send_raw_msg(&msg.as_bytes()); - } - - pub fn send_msg(&mut self, msg: HWServerMessage) { - self.send_string(&msg.to_raw_protocol()); - } - - fn flush(&mut self) { - self.buf_out.write_to(&mut self.sock).unwrap(); - self.sock.flush(); - } - - pub fn readable(&mut self, _poll: &Poll) -> Vec { - let v = self.decoder.read_from(&mut self.sock).unwrap(); - debug!("Read {} bytes", v); - let mut response = Vec::new(); - { - for msg in self.decoder.extract_messages() { - response.push(ReactProtocolMessage(msg)); - } - } - self.decoder.sweep(); - response - } - - pub fn writable(&mut self, _poll: &Poll) -> io::Result<()> { - self.buf_out.write_to(&mut self.sock)?; - - Ok(()) - } - - pub fn error(&mut self, _poll: &Poll) -> Vec { - return vec![ByeClient("Connection reset".to_string())] - } -} +} \ No newline at end of file diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/coretypes.rs --- a/gameServer2/src/server/coretypes.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/coretypes.rs Thu Mar 08 15:01:18 2018 -0500 @@ -1,16 +1,16 @@ -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub enum ServerVar { MOTDNew(String), MOTDOld(String), LatestProto(u32), } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub enum GameCfg { } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub struct TeamInfo { name: String, color: u8, @@ -23,7 +23,7 @@ hedgehogs: [HedgehogInfo; 8], } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub struct HedgehogInfo { name: String, hat: String, diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/handlers/inroom.rs --- a/gameServer2/src/server/handlers/inroom.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/handlers/inroom.rs Thu Mar 08 15:01:18 2018 -0500 @@ -6,7 +6,7 @@ use protocol::messages::HWProtocolMessage; use protocol::messages::HWServerMessage::*; -pub fn handle(server: &mut HWServer, token: usize, _poll: &mio::Poll, message: HWProtocolMessage) { +pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) { match message { _ => warn!("Unimplemented!"), } diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/handlers/lobby.rs --- a/gameServer2/src/server/handlers/lobby.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/handlers/lobby.rs Thu Mar 08 15:01:18 2018 -0500 @@ -6,34 +6,35 @@ use protocol::messages::HWProtocolMessage; use protocol::messages::HWServerMessage::*; -pub fn handle(server: &mut HWServer, token: usize, poll: &mio::Poll, message: HWProtocolMessage) { +pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) { + use protocol::messages::HWProtocolMessage::*; match message { - HWProtocolMessage::Chat(msg) => { - let chat_msg = ChatMsg(&server.clients[token].nick, &msg).to_raw_protocol(); - server.react(token, poll, vec![SendAllButMe(chat_msg)]); + Chat(msg) => { + let chat_msg = ChatMsg(server.clients[token].nick.clone(), msg); + server.react(token, vec![SendAllButMe(chat_msg)]); }, - HWProtocolMessage::CreateRoom(name, password) => { + CreateRoom(name, password) => { let room_exists = server.rooms.iter().find(|&(_, r)| r.name == name).is_some(); if room_exists { - server.react(token, poll, vec![Warn("Room exists".to_string())]); + server.react(token, vec![Warn("Room exists".to_string())]); } else { - let flags_msg = ClientFlags("+hr", &[&server.clients[token].nick]).to_raw_protocol(); + 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, poll, vec![ + server.react(token, vec![ AddRoom(name, password) , SendMe(flags_msg) ]); } }, - HWProtocolMessage::Join(name, password) => { + Join(name, password) => { }, - HWProtocolMessage::List => warn!("Deprecated LIST message received"), + List => warn!("Deprecated LIST message received"), _ => warn!("Incorrect command in lobby state"), } } diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/handlers/loggingin.rs --- a/gameServer2/src/server/handlers/loggingin.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/handlers/loggingin.rs Thu Mar 08 15:01:18 2018 -0500 @@ -6,17 +6,17 @@ use protocol::messages::HWProtocolMessage; use protocol::messages::HWServerMessage::*; -pub fn handle(server: &mut HWServer, token: usize, poll: &mio::Poll, message: HWProtocolMessage) { +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, poll, vec![SendMe(Nick(&nick).to_raw_protocol())]); + server.react(token, vec![SendMe(Nick(nick.clone()))]); server.clients[token].nick = nick; - server.react(token, poll, vec![CheckRegistered]); + server.react(token, vec![CheckRegistered]); }, HWProtocolMessage::Proto(proto) => { server.clients[token].protocol_number = proto; - server.react(token, poll, vec![CheckRegistered]); + server.react(token, vec![CheckRegistered]); }, _ => warn!("Incorrect command in logging-in state"), } diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/handlers/mod.rs --- a/gameServer2/src/server/handlers/mod.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/handlers/mod.rs Thu Mar 08 15:01:18 2018 -0500 @@ -12,23 +12,24 @@ mod lobby; mod inroom; -pub fn handle(server: &mut HWServer, token: usize, poll: &mio::Poll, message: HWProtocolMessage) { +pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) { match message { HWProtocolMessage::Ping => - server.react(token, poll, vec![SendMe(Pong.to_raw_protocol())]), + server.react(token, vec![SendMe(Pong)]), HWProtocolMessage::Quit(Some(msg)) => - server.react(token, poll, vec![ByeClient("User quit: ".to_string() + &msg)]), + server.react(token, vec![ByeClient("User quit: ".to_string() + &msg)]), HWProtocolMessage::Quit(None) => - server.react(token, poll, vec![ByeClient("User quit".to_string())]), + server.react(token, vec![ByeClient("User quit".to_string())]), HWProtocolMessage::Malformed => warn!("Malformed/unknown message"), HWProtocolMessage::Empty => warn!("Empty message"), _ => { - if !server.clients[token].room_id.is_some() { - loggingin::handle(server, token, poll, message); - } else if server.clients[token].room_id == Some(server.lobby_id) { - lobby::handle(server, token, poll, message); - } else { - inroom::handle(server, token, poll, message); + match server.clients[token].room_id { + None => + loggingin::handle(server, token, message), + Some(id) if id == server.lobby_id => + lobby::handle(server, token, message), + _ => + inroom::handle(server, token, message) } }, } diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/mod.rs --- a/gameServer2/src/server/mod.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/mod.rs Thu Mar 08 15:01:18 2018 -0500 @@ -1,5 +1,7 @@ pub mod server; pub mod client; +pub mod room; +pub mod network; pub mod coretypes; mod actions; mod handlers; diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/network.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gameServer2/src/server/network.rs Thu Mar 08 15:01:18 2018 -0500 @@ -0,0 +1,211 @@ +extern crate slab; + +use std::io::ErrorKind; +use mio::net::*; +use super::server::{HWServer, PendingMessage, Destination}; +use super::client::ClientId; +use slab::Slab; + +use mio::net::TcpStream; +use mio::*; +use std::io::Write; +use std::io; +use netbuf; + +use utils; +use protocol::ProtocolDecoder; +use protocol::messages::*; +use std::net::SocketAddr; + +pub struct NetworkClient { + id: ClientId, + socket: TcpStream, + peer_addr: SocketAddr, + decoder: ProtocolDecoder, + buf_out: netbuf::Buf, + closed: bool +} + +impl NetworkClient { + pub fn new(id: ClientId, socket: TcpStream, peer_addr: SocketAddr) -> NetworkClient { + NetworkClient { + id, socket, peer_addr, + decoder: ProtocolDecoder::new(), + buf_out: netbuf::Buf::new(), + closed: false + } + } + + pub fn send_raw_msg(&mut self, msg: &[u8]) { + self.buf_out.write(msg).unwrap(); + self.flush(); + } + + pub fn send_string(&mut self, msg: &String) { + self.send_raw_msg(&msg.as_bytes()); + } + + pub fn send_msg(&mut self, msg: HWServerMessage) { + self.send_string(&msg.to_raw_protocol()); + } + + fn flush(&mut self) { + self.buf_out.write_to(&mut self.socket).unwrap(); + self.socket.flush().unwrap(); + } + + pub fn read_messages(&mut self) -> io::Result> { + let bytes_read = self.decoder.read_from(&mut self.socket)?; + debug!("Read {} bytes", bytes_read); + + if bytes_read == 0 { + self.closed = true; + info!("EOF for client {} ({})", self.id, self.peer_addr); + } + + Ok(self.decoder.extract_messages()) + } + + pub fn write_messages(&mut self) -> io::Result<()> { + self.buf_out.write_to(&mut self.socket)?; + Ok(()) + } +} + +pub struct NetworkLayer { + listener: TcpListener, + server: HWServer, + + clients: Slab +} + +impl NetworkLayer { + pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer { + let server = HWServer::new(clients_limit, rooms_limit); + let clients = Slab::with_capacity(clients_limit); + NetworkLayer {listener, server, clients} + } + + 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_mut(id) { + poll.deregister(&client.socket) + .ok().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: TcpStream, addr: SocketAddr) { + poll.register(&client_socket, Token(id), + Ready::readable() | Ready::writable(), + PollOpt::edge()) + .ok().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); + } + + 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, client_socket, addr); + self.flush_server_messages(); + + Ok(()) + } + + fn flush_server_messages(&mut self) { + for PendingMessage(destination, msg) in self.server.output.drain(..) { + match destination { + Destination::ToSelf(id) => { + if let Some(ref mut client) = self.clients.get_mut(id) { + client.send_msg(msg) + } + } + Destination::ToOthers(id) => { + let msg_string = msg.to_raw_protocol(); + for item in self.clients.iter_mut() { + if item.0 != id { + item.1.send_string(&msg_string) + } + } + } + } + } + } + + pub fn client_readable(&mut self, poll: &Poll, + client_id: ClientId) -> io::Result<()> { + let mut client_lost = false; + let messages; + if let Some(ref mut client) = self.clients.get_mut(client_id) { + messages = match client.read_messages() { + Ok(messages) => Some(messages), + Err(ref error) if error.kind() == ErrorKind::WouldBlock => None, + Err(error) => return Err(error) + }; + if client.closed { + client_lost = true; + } + } else { + warn!("invalid readable client: {}", client_id); + messages = None; + }; + + if client_lost { + self.client_error(&poll, client_id)?; + } else if let Some(msg) = messages { + for message in msg { + self.server.handle_msg(client_id, message); + } + self.flush_server_messages(); + } + + if !self.server.removed_clients.is_empty() { + let ids = self.server.removed_clients.to_vec(); + self.server.removed_clients.clear(); + 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<()> { + if let Some(ref mut client) = self.clients.get_mut(client_id) { + match client.write_messages() { + Ok(_) => (), + Err(ref error) if error.kind() == ErrorKind::WouldBlock => (), + Err(error) => return Err(error) + } + } else { + warn!("invalid writable client: {}", client_id); + } + + 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(()) + } +} + diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/room.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gameServer2/src/server/room.rs Thu Mar 08 15:01:18 2018 -0500 @@ -0,0 +1,21 @@ +pub type RoomId = usize; + +pub struct HWRoom { + pub id: RoomId, + pub name: String, + pub password: Option, + pub protocol_number: u32, + pub ready_players_number: u8, +} + +impl HWRoom { + pub fn new(id: RoomId) -> HWRoom { + HWRoom { + id, + name: String::new(), + password: None, + protocol_number: 0, + ready_players_number: 0, + } + } +} \ No newline at end of file diff -r 1ddb8aac5e30 -r 1e39b8749072 gameServer2/src/server/server.rs --- a/gameServer2/src/server/server.rs Thu Mar 08 16:49:49 2018 +0100 +++ b/gameServer2/src/server/server.rs Thu Mar 08 15:01:18 2018 -0500 @@ -4,107 +4,85 @@ use std::io; use utils; -use super::client::HWClient; +use super::client::*; +use super::room::*; use super::actions; +use protocol::messages::*; +use super::handlers; type Slab = slab::Slab; +pub enum Destination { + ToSelf(ClientId), + ToOthers(ClientId) +} + +pub struct PendingMessage(pub Destination, pub HWServerMessage); + pub struct HWServer { - listener: TcpListener, pub clients: Slab, pub rooms: Slab, - pub lobby_id: usize, + pub lobby_id: RoomId, + pub output: Vec, + pub removed_clients: Vec, } impl HWServer { - pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> HWServer { - let mut rooms = Slab::with_capacity(rooms_limit); - let token = rooms.insert(HWRoom::new()); - HWServer { - listener: listener, - clients: Slab::with_capacity(clients_limit), - rooms: rooms, - lobby_id: token, - } + pub fn new(clients_limit: usize, rooms_limit: usize) -> 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![] + }; + server.lobby_id = server.add_room(); + server } - pub fn register(&self, poll: &Poll) -> io::Result<()> { - poll.register(&self.listener, utils::SERVER, Ready::readable(), - PollOpt::edge()) - } - - pub fn accept(&mut self, poll: &Poll) -> io::Result<()> { - let (sock, addr) = self.listener.accept()?; - info!("Connected: {}", addr); - - let client = HWClient::new(sock); - let token = self.clients.insert(client); - - self.clients[token].id = token; - self.clients[token].register(poll, Token(token)); - - Ok(()) + pub fn add_client(&mut self) -> ClientId { + let key: ClientId; + { + let entry = self.clients.vacant_entry(); + key = entry.key(); + let client = HWClient::new(entry.key()); + entry.insert(client); + } + self.send_self(key, HWServerMessage::Connected(utils::PROTOCOL_VERSION)); + key } - pub fn client_readable(&mut self, poll: &Poll, - token: usize) -> io::Result<()> { - let actions; - { - actions = self.clients[token].readable(poll); - } - - self.react(token, poll, actions); - - Ok(()) + pub fn client_lost(&mut self, client_id: ClientId) { + actions::run_action(self, client_id, + actions::Action::ByeClient("Connection reset".to_string())); } - pub fn client_writable(&mut self, poll: &Poll, - token: usize) -> io::Result<()> { - self.clients[token].writable(poll)?; - - Ok(()) + 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 client_error(&mut self, poll: &Poll, - token: usize) -> io::Result<()> { - let actions; - { - actions = self.clients[token].error(poll); - } + pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) { + handlers::handle(self, client_id, msg); + } - self.react(token, poll, actions); - - Ok(()) + pub fn send_self(&mut self, client_id: ClientId, msg: HWServerMessage) { + self.output.push(PendingMessage( + Destination::ToSelf(client_id), msg)); } - pub fn send(&mut self, token: usize, msg: &String) { - self.clients[token].send_string(msg); + pub fn send_others(&mut self, client_id: ClientId, msg: HWServerMessage) { + self.output.push(PendingMessage( + Destination::ToOthers(client_id), msg)); } - pub fn react(&mut self, token: usize, poll: &Poll, actions: Vec) { + pub fn react(&mut self, client_id: ClientId, actions: Vec) { for action in actions { - actions::run_action(self, token, poll, action); + actions::run_action(self, client_id, action); } } } - - -pub struct HWRoom { - pub id: usize, - pub name: String, - pub password: Option, - pub protocol_number: u32, - pub ready_players_number: u8, -} - -impl HWRoom { - pub fn new() -> HWRoom { - HWRoom { - id: 0, - name: String::new(), - password: None, - protocol_number: 0, - ready_players_number: 0, - } - } -}