--- 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 <a.korotaev@hedgewars.org>" ]
-
-[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
--- 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();
- }
-}
--- 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<R: Read>(&mut self, stream: &mut R) -> Result<usize> {
- self.buf.read_from(stream)
- }
-
- pub fn extract_messages(&mut self) -> Vec<messages::HWProtocolMessage> {
- 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;
- }
-}
--- 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<String>),
- //Cmd(String, Vec<String>),
- 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<String>),
- JoinRoom(String, Option<String>),
- Follow(String),
- Rnd(Vec<String>),
- 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<String>),
- Cfg(GameCfg),
- AddTeam(Box<TeamInfo>),
- 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<VoteType>),
- 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<String>),
- ChatMsg {nick: String, msg: String},
- ClientFlags(String, Vec<String>),
- Rooms(Vec<String>),
- RoomAdd(Vec<String>),
- RoomJoined(Vec<String>),
- RoomLeft(String, String),
- RoomRemove(String),
- RoomUpdated(String, Vec<String>),
- TeamAdd(Vec<String>),
- TeamRemove(String),
- TeamAccepted(String),
- TeamColor(String, u8),
- HedgehogsNumber(String, u8),
- ConfigEntry(String, Vec<String>),
- Kicked,
- RunGame,
- ForwardEngineMessage(Vec<String>),
- RoundFinished,
-
- ServerMessage(String),
- Notice(String),
- Warning(String),
- Error(String),
- Connected(u32),
- Unreachable,
-
- //Deprecated messages
- LegacyReady(bool, Vec<String>)
-}
-
-pub fn server_chat(msg: String) -> HWServerMessage {
- HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg }
-}
-
-impl GameCfg {
- pub fn to_protocol(&self) -> (String, Vec<String>) {
- 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::<Vec<_>>().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<String>)>) =>, ??
- 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"],
- }
- }
-}
--- 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<String> >, 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<String> >, 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<HWProtocolMessage> >, 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])));
-}
--- 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<T>: Sized { fn into2(self) -> T; }
-impl <T> Into2<T> for T { fn into2(self) -> T { self } }
-impl Into2<Vec<String>> for Vec<Ascii> {
- fn into2(self) -> Vec<String> {
- self.into_iter().map(|x| x.0).collect()
- }
-}
-impl Into2<String> for Ascii { fn into2(self) -> String { self.0 } }
-impl Into2<Option<String>> for Option<Ascii>{
- fn into2(self) -> Option<String> { 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 = <String as Arbitrary>::Parameters;
-
- fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
- "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
- }
-
- type Strategy = BoxedStrategy<Ascii>;
-}
-
-impl Arbitrary for GameCfg {
- type Parameters = ();
-
- fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::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<Ascii>),
- 7 => Scheme(Ascii, Vec<Ascii>),
- 8 => Script(Ascii),
- 9 => Theme(Ascii),
- 10 => DrawnMap(Ascii))
- }).boxed()
- }
-
- type Strategy = BoxedStrategy<GameCfg>;
-}
-
-impl Arbitrary for TeamInfo {
- type Parameters = ();
-
- fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::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<TeamInfo>;
-}
-
-pub fn gen_proto_msg() -> BoxedStrategy<HWProtocolMessage> where {
- let res = (0..58).no_shrink().prop_flat_map(|i| {
- proto_msg_match!(i, def = Malformed,
- 0 => Ping(),
- 1 => Pong(),
- 2 => Quit(Option<Ascii>),
- //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<Ascii>),
- 16 => JoinRoom(Ascii, Option<Ascii>),
- 17 => Follow(Ascii),
- 18 => Rnd(Vec<Ascii>),
- 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<Ascii>),
- 30 => Cfg(GameCfg),
- 31 => AddTeam(Box<TeamInfo>),
- 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<String>)>),
- 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()
-}
--- 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;
--- 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<RoomId>,
- protocol: Option<u16>,
- 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<Action> 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<String>),
- RemoveRoom(RoomId),
- MoveToRoom(RoomId),
- MoveToLobby(String),
- ChangeMaster(RoomId, Option<ClientId>),
- RemoveTeam(String),
- RemoveClientTeams,
- SendRoomUpdate(Option<String>),
- 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())
- }
- }
-}
--- 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<usize>,
- 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<u8>,
- pub clan: Option<u8>
-}
-
-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
--- 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<T> = slab::Slab<T>;
-
-pub struct HWServer {
- pub clients: Slab<HWClient>,
- pub rooms: Slab<HWRoom>,
- pub lobby_id: RoomId,
- pub output: Vec<(Vec<ClientId>, HWServerMessage)>,
- pub removed_clients: Vec<ClientId>,
- pub io: Box<dyn HWServerIO>
-}
-
-impl HWServer {
- pub fn new(clients_limit: usize, rooms_limit: usize, io: Box<dyn HWServerIO>) -> 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<ClientId> {
- 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::<Vec<_>>()
- };
- 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<actions::Action>) {
- 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<F>(&self, f: F) -> Vec<ClientId>
- 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<ClientId> {
- self.select_clients(|(_, c)| c.room_id == Some(room_id))
- }
-
- pub fn protocol_clients(&self, protocol: u16) -> Vec<ClientId> {
- self.select_clients(|(_, c)| c.protocol_number == protocol)
- }
-
- pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec<ClientId> {
- 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
- }
-}
--- 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<String>),
- Scheme(String, Vec<String>),
- 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<String>),
- Pause,
- NewSeed,
- HedgehogsPerTeam(u8)
-}
-
-#[derive(Clone, Debug)]
-pub struct Voting {
- pub ttl: u32,
- pub voters: Vec<ClientId>,
- pub votes: Vec<(ClientId, bool)>,
- pub kind: VoteType
-}
-
-impl Voting {
- pub fn new(kind: VoteType, voters: Vec<ClientId>) -> Voting {
- Voting {
- kind, voters, ttl: 2,
- votes: Vec::new()
- }
- }
-}
\ No newline at end of file
--- 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)
- }
- },
- }
-}
--- 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"),
- }
-}
--- 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<String>) {
- 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;
- }
- }
- }
-}
--- 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<<Self as Iterator>::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 <nickname>, map <name>, pause, newseed, hedgehogs <number>".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::<Vec<_>>());
- 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::<Vec<_>>());
- 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!")
- }
-}
--- 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"),
- }
-}
--- 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"),
- }
-}
--- 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<String>;
-}
-
-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<String> {
- 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<String> {
- 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
--- 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<T> = io::Result<(T, NetworkClientState)>;
-
-#[cfg(not(feature = "tls-connections"))]
-pub enum ClientSocket {
- Plain(TcpStream)
-}
-
-#[cfg(feature = "tls-connections")]
-pub enum ClientSocket {
- SslHandshake(Option<MidHandshakeSslStream<TcpStream>>),
- SslStream(SslStream<TcpStream>)
-}
-
-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<TcpStream>) -> io::Result<NetworkClientState> {
- 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<R: Read>(decoder: &mut ProtocolDecoder, source: &mut R,
- id: ClientId, addr: &SocketAddr) -> NetworkResult<Vec<HWProtocolMessage>> {
- 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<Vec<HWProtocolMessage>> {
- #[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<W: Write>(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<NetworkClient>,
- 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<ClientSocket> {
- #[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(())
- }
-}
--- 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<String>
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Scheme {
- name: String,
- settings: Vec<String>
-}
-
-#[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<String>
-}
-
-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<Item = &TeamInfo> + Clone
-{
- teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
-}
-
-fn map_config_from(c: &RoomConfig) -> Vec<String> {
- 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<GameCfg> {
- 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<String>,
- pub msg_log: Vec<String>,
- pub sync_msg: Option<String>,
- 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<Item = &TeamInfo> + 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<ClientId>,
- pub name: String,
- pub password: Option<String>,
- 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<Voting>,
- pub saves: HashMap<String, RoomSave>,
- pub game_info: Option<GameInfo>
-}
-
-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<String> {
- 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<F>(&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<F>(&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<Item = &TeamInfo> {
- client_teams_impl(&self.teams, client_id)
- }
-
- pub fn client_team_indices(&self, client_id: ClientId) -> Vec<u8> {
- 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<u8> {
- 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<String> {
- 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<String> {
- match self.game_info {
- Some(ref info) => map_config_from(&info.config),
- None => map_config_from(&self.config)
- }
- }
-
- pub fn game_config(&self) -> Vec<GameCfg> {
- 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<String, serde_yaml::Error> {
- 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<String, RoomSave>)>(text).map(|(greeting, saves)| {
- self.greeting = greeting;
- self.saves = saves;
- })
- }
-
- pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec<String> {
- 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
--- 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<T>(msg: T) -> String
- where T: Iterator<Item = u8> + 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
--- /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 <a.korotaev@hedgewars.org>" ]
+
+[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
--- /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();
+ }
+}
--- /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<R: Read>(&mut self, stream: &mut R) -> Result<usize> {
+ self.buf.read_from(stream)
+ }
+
+ pub fn extract_messages(&mut self) -> Vec<messages::HWProtocolMessage> {
+ 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;
+ }
+}
--- /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<String>),
+ //Cmd(String, Vec<String>),
+ 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<String>),
+ JoinRoom(String, Option<String>),
+ Follow(String),
+ Rnd(Vec<String>),
+ 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<String>),
+ Cfg(GameCfg),
+ AddTeam(Box<TeamInfo>),
+ 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<VoteType>),
+ 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<String>),
+ ChatMsg {nick: String, msg: String},
+ ClientFlags(String, Vec<String>),
+ Rooms(Vec<String>),
+ RoomAdd(Vec<String>),
+ RoomJoined(Vec<String>),
+ RoomLeft(String, String),
+ RoomRemove(String),
+ RoomUpdated(String, Vec<String>),
+ TeamAdd(Vec<String>),
+ TeamRemove(String),
+ TeamAccepted(String),
+ TeamColor(String, u8),
+ HedgehogsNumber(String, u8),
+ ConfigEntry(String, Vec<String>),
+ Kicked,
+ RunGame,
+ ForwardEngineMessage(Vec<String>),
+ RoundFinished,
+
+ ServerMessage(String),
+ Notice(String),
+ Warning(String),
+ Error(String),
+ Connected(u32),
+ Unreachable,
+
+ //Deprecated messages
+ LegacyReady(bool, Vec<String>)
+}
+
+pub fn server_chat(msg: String) -> HWServerMessage {
+ HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg }
+}
+
+impl GameCfg {
+ pub fn to_protocol(&self) -> (String, Vec<String>) {
+ 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::<Vec<_>>().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<String>)>) =>, ??
+ 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"],
+ }
+ }
+}
--- /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<String> >, 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<String> >, 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<HWProtocolMessage> >, 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])));
+}
--- /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<T>: Sized { fn into2(self) -> T; }
+impl <T> Into2<T> for T { fn into2(self) -> T { self } }
+impl Into2<Vec<String>> for Vec<Ascii> {
+ fn into2(self) -> Vec<String> {
+ self.into_iter().map(|x| x.0).collect()
+ }
+}
+impl Into2<String> for Ascii { fn into2(self) -> String { self.0 } }
+impl Into2<Option<String>> for Option<Ascii>{
+ fn into2(self) -> Option<String> { 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 = <String as Arbitrary>::Parameters;
+
+ fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+ "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
+ }
+
+ type Strategy = BoxedStrategy<Ascii>;
+}
+
+impl Arbitrary for GameCfg {
+ type Parameters = ();
+
+ fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::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<Ascii>),
+ 7 => Scheme(Ascii, Vec<Ascii>),
+ 8 => Script(Ascii),
+ 9 => Theme(Ascii),
+ 10 => DrawnMap(Ascii))
+ }).boxed()
+ }
+
+ type Strategy = BoxedStrategy<GameCfg>;
+}
+
+impl Arbitrary for TeamInfo {
+ type Parameters = ();
+
+ fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::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<TeamInfo>;
+}
+
+pub fn gen_proto_msg() -> BoxedStrategy<HWProtocolMessage> where {
+ let res = (0..58).no_shrink().prop_flat_map(|i| {
+ proto_msg_match!(i, def = Malformed,
+ 0 => Ping(),
+ 1 => Pong(),
+ 2 => Quit(Option<Ascii>),
+ //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<Ascii>),
+ 16 => JoinRoom(Ascii, Option<Ascii>),
+ 17 => Follow(Ascii),
+ 18 => Rnd(Vec<Ascii>),
+ 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<Ascii>),
+ 30 => Cfg(GameCfg),
+ 31 => AddTeam(Box<TeamInfo>),
+ 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<String>)>),
+ 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()
+}
--- /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;
--- /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<RoomId>,
+ protocol: Option<u16>,
+ 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<Action> 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<String>),
+ RemoveRoom(RoomId),
+ MoveToRoom(RoomId),
+ MoveToLobby(String),
+ ChangeMaster(RoomId, Option<ClientId>),
+ RemoveTeam(String),
+ RemoveClientTeams,
+ SendRoomUpdate(Option<String>),
+ 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())
+ }
+ }
+}
--- /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<usize>,
+ 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<u8>,
+ pub clan: Option<u8>
+}
+
+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
--- /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<T> = slab::Slab<T>;
+
+pub struct HWServer {
+ pub clients: Slab<HWClient>,
+ pub rooms: Slab<HWRoom>,
+ pub lobby_id: RoomId,
+ pub output: Vec<(Vec<ClientId>, HWServerMessage)>,
+ pub removed_clients: Vec<ClientId>,
+ pub io: Box<dyn HWServerIO>
+}
+
+impl HWServer {
+ pub fn new(clients_limit: usize, rooms_limit: usize, io: Box<dyn HWServerIO>) -> 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<ClientId> {
+ 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::<Vec<_>>()
+ };
+ 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<actions::Action>) {
+ 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<F>(&self, f: F) -> Vec<ClientId>
+ 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<ClientId> {
+ self.select_clients(|(_, c)| c.room_id == Some(room_id))
+ }
+
+ pub fn protocol_clients(&self, protocol: u16) -> Vec<ClientId> {
+ self.select_clients(|(_, c)| c.protocol_number == protocol)
+ }
+
+ pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec<ClientId> {
+ 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
+ }
+}
--- /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<String>),
+ Scheme(String, Vec<String>),
+ 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<String>),
+ Pause,
+ NewSeed,
+ HedgehogsPerTeam(u8)
+}
+
+#[derive(Clone, Debug)]
+pub struct Voting {
+ pub ttl: u32,
+ pub voters: Vec<ClientId>,
+ pub votes: Vec<(ClientId, bool)>,
+ pub kind: VoteType
+}
+
+impl Voting {
+ pub fn new(kind: VoteType, voters: Vec<ClientId>) -> Voting {
+ Voting {
+ kind, voters, ttl: 2,
+ votes: Vec::new()
+ }
+ }
+}
\ No newline at end of file
--- /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)
+ }
+ },
+ }
+}
--- /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"),
+ }
+}
--- /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<String>) {
+ 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;
+ }
+ }
+ }
+}
--- /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<<Self as Iterator>::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 <nickname>, map <name>, pause, newseed, hedgehogs <number>".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::<Vec<_>>());
+ 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::<Vec<_>>());
+ 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!")
+ }
+}
--- /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"),
+ }
+}
--- /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"),
+ }
+}
--- /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<String>;
+}
+
+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<String> {
+ 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<String> {
+ 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
--- /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<T> = io::Result<(T, NetworkClientState)>;
+
+#[cfg(not(feature = "tls-connections"))]
+pub enum ClientSocket {
+ Plain(TcpStream)
+}
+
+#[cfg(feature = "tls-connections")]
+pub enum ClientSocket {
+ SslHandshake(Option<MidHandshakeSslStream<TcpStream>>),
+ SslStream(SslStream<TcpStream>)
+}
+
+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<TcpStream>) -> io::Result<NetworkClientState> {
+ 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<R: Read>(decoder: &mut ProtocolDecoder, source: &mut R,
+ id: ClientId, addr: &SocketAddr) -> NetworkResult<Vec<HWProtocolMessage>> {
+ 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<Vec<HWProtocolMessage>> {
+ #[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<W: Write>(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<NetworkClient>,
+ 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<ClientSocket> {
+ #[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(())
+ }
+}
--- /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<String>
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+struct Scheme {
+ name: String,
+ settings: Vec<String>
+}
+
+#[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<String>
+}
+
+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<Item = &TeamInfo> + Clone
+{
+ teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
+}
+
+fn map_config_from(c: &RoomConfig) -> Vec<String> {
+ 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<GameCfg> {
+ 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<String>,
+ pub msg_log: Vec<String>,
+ pub sync_msg: Option<String>,
+ 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<Item = &TeamInfo> + 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<ClientId>,
+ pub name: String,
+ pub password: Option<String>,
+ 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<Voting>,
+ pub saves: HashMap<String, RoomSave>,
+ pub game_info: Option<GameInfo>
+}
+
+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<String> {
+ 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<F>(&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<F>(&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<Item = &TeamInfo> {
+ client_teams_impl(&self.teams, client_id)
+ }
+
+ pub fn client_team_indices(&self, client_id: ClientId) -> Vec<u8> {
+ 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<u8> {
+ 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<String> {
+ 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<String> {
+ match self.game_info {
+ Some(ref info) => map_config_from(&info.config),
+ None => map_config_from(&self.config)
+ }
+ }
+
+ pub fn game_config(&self) -> Vec<GameCfg> {
+ 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<String, serde_yaml::Error> {
+ 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<String, RoomSave>)>(text).map(|(greeting, saves)| {
+ self.greeting = greeting;
+ self.saves = saves;
+ })
+ }
+
+ pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec<String> {
+ 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
--- /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<T>(msg: T) -> String
+ where T: Iterator<Item = u8> + 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