Extract network protocol into a separate crate
authorunc0rr
Wed, 23 Jun 2021 23:41:51 +0200
changeset 15826 747278149393
parent 15825 b06b33cf0a89
child 15827 61da40b657fa
Extract network protocol into a separate crate
rust/hedgewars-network-protocol/Cargo.toml
rust/hedgewars-network-protocol/src/lib.rs
rust/hedgewars-network-protocol/src/messages.rs
rust/hedgewars-network-protocol/src/parser.rs
rust/hedgewars-network-protocol/src/types.rs
rust/hedgewars-network-protocol/tests/parser.rs
rust/hedgewars-network-protocol/tests/test.rs
rust/hedgewars-server/Cargo.toml
rust/hedgewars-server/src/core/room.rs
rust/hedgewars-server/src/core/server.rs
rust/hedgewars-server/src/core/types.rs
rust/hedgewars-server/src/handlers.rs
rust/hedgewars-server/src/handlers/actions.rs
rust/hedgewars-server/src/handlers/checker.rs
rust/hedgewars-server/src/handlers/common.rs
rust/hedgewars-server/src/handlers/inanteroom.rs
rust/hedgewars-server/src/handlers/inlobby.rs
rust/hedgewars-server/src/handlers/inroom.rs
rust/hedgewars-server/src/protocol.rs
rust/hedgewars-server/src/protocol/messages.rs
rust/hedgewars-server/src/protocol/parser.rs
rust/hedgewars-server/src/protocol/test.rs
rust/hedgewars-server/src/server/demo.rs
rust/hedgewars-server/src/server/network.rs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/Cargo.toml	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,12 @@
+[package]
+name = "hedgewars-network-protocol"
+version = "0.1.0"
+authors = ["Andrey Korotaev <a.korotaev@hedgewars.org>"]
+edition = "2018"
+
+[dependencies]
+nom = "6"
+serde_derive = "1.0"
+serde = "1.0"
+
+proptest = "1.0"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/lib.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,3 @@
+pub mod messages;
+pub mod parser;
+pub mod types;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/messages.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,421 @@
+use crate::types::{GameCfg, ServerVar, TeamInfo, VoteType};
+use std::iter::once;
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum HwProtocolMessage {
+    // common messages
+    Ping,
+    Pong,
+    Quit(Option<String>),
+    Global(String),
+    Watch(u32),
+    ToggleServerRegisteredOnly,
+    SuperPower,
+    Info(String),
+    // anteroom messages
+    Nick(String),
+    Proto(u16),
+    Password(String, String),
+    Checker(u16, String, String),
+    // lobby messages
+    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,
+    // room messages
+    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(Option<String>),
+    CallVote(Option<VoteType>),
+    Vote(bool),
+    ForceVote(bool),
+    Save(String, String),
+    Delete(String),
+    SaveRoom(String),
+    LoadRoom(String),
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum ProtocolFlags {
+    InRoom,
+    RoomMaster,
+    Ready,
+    InGame,
+    Registered,
+    Admin,
+    Contributor,
+}
+
+impl ProtocolFlags {
+    #[inline]
+    fn flag_char(&self) -> char {
+        match self {
+            ProtocolFlags::InRoom => 'i',
+            ProtocolFlags::RoomMaster => 'h',
+            ProtocolFlags::Ready => 'r',
+            ProtocolFlags::InGame => 'g',
+            ProtocolFlags::Registered => 'u',
+            ProtocolFlags::Admin => 'a',
+            ProtocolFlags::Contributor => 'c',
+        }
+    }
+
+    #[inline]
+    fn format(prefix: char, flags: &[ProtocolFlags]) -> String {
+        once(prefix)
+            .chain(flags.iter().map(|f| f.flag_char()))
+            .collect()
+    }
+}
+
+#[inline]
+pub fn add_flags(flags: &[ProtocolFlags]) -> String {
+    ProtocolFlags::format('+', flags)
+}
+
+#[inline]
+pub fn remove_flags(flags: &[ProtocolFlags]) -> String {
+    ProtocolFlags::format('-', flags)
+}
+
+#[derive(Debug)]
+pub enum HwServerMessage {
+    Connected(u32),
+    Redirect(u16),
+
+    Ping,
+    Pong,
+    Bye(String),
+
+    Nick(String),
+    Proto(u16),
+    AskPassword(String),
+    ServerAuth(String),
+    LogonPassed,
+
+    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>),
+    Joining(String),
+    TeamAdd(Vec<String>),
+    TeamRemove(String),
+    TeamAccepted(String),
+    TeamColor(String, u8),
+    HedgehogsNumber(String, u8),
+    ConfigEntry(String, Vec<String>),
+    Kicked,
+    RunGame,
+    ForwardEngineMessage(Vec<String>),
+    RoundFinished,
+    ReplayStart,
+
+    Info(Vec<String>),
+    ServerMessage(String),
+    ServerVars(Vec<String>),
+    Notice(String),
+    Warning(String),
+    Error(String),
+    Unreachable,
+
+    //Deprecated messages
+    LegacyReady(bool, Vec<String>),
+}
+
+fn special_chat(nick: &str, msg: String) -> HwServerMessage {
+    HwServerMessage::ChatMsg {
+        nick: nick.to_string(),
+        msg,
+    }
+}
+
+pub fn server_chat(msg: String) -> HwServerMessage {
+    special_chat("[server]", msg)
+}
+
+pub fn global_chat(msg: String) -> HwServerMessage {
+    special_chat("(global notice)", msg)
+}
+
+impl ServerVar {
+    pub fn to_protocol(&self) -> Vec<String> {
+        use ServerVar::*;
+        match self {
+            MOTDNew(s) => vec!["MOTD_NEW".to_string(), s.clone()],
+            MOTDOld(s) => vec!["MOTD_OLD".to_string(), s.clone()],
+            LatestProto(n) => vec!["LATEST_PROTO".to_string(), n.to_string()],
+        }
+    }
+}
+
+impl VoteType {
+    pub fn to_protocol(&self) -> Vec<String> {
+        use VoteType::*;
+        match self {
+            Kick(nick) => vec!["KICK".to_string(), nick.clone()],
+            Map(None) => vec!["MAP".to_string()],
+            Map(Some(name)) => vec!["MAP".to_string(), name.clone()],
+            Pause => vec!["PAUSE".to_string()],
+            NewSeed => vec!["NEWSEED".to_string()],
+            HedgehogsPerTeam(count) => vec!["HEDGEHOGS".to_string(), count.to_string()],
+        }
+    }
+}
+
+impl GameCfg {
+    pub fn to_protocol(&self) -> (String, Vec<String>) {
+        use 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());
+                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 {
+        let (name, args) = self.to_protocol();
+        HwServerMessage::ConfigEntry(name, args)
+    }
+}
+
+impl TeamInfo {
+    pub fn to_protocol(&self) -> Vec<String> {
+        let mut info = vec![
+            self.name.clone(),
+            self.grave.clone(),
+            self.fort.clone(),
+            self.voice_pack.clone(),
+            self.flag.clone(),
+            self.owner.clone(),
+            self.difficulty.to_string(),
+        ];
+        let hogs = self
+            .hedgehogs
+            .iter()
+            .flat_map(|h| once(h.name.clone()).chain(once(h.hat.clone())));
+        info.extend(hogs);
+        info
+    }
+}
+
+macro_rules! const_braces {
+    ($e: expr) => {
+        "{}\n"
+    };
+}
+
+macro_rules! msg {
+    [$($part: expr),*] => {
+        format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
+    };
+}
+
+impl HwProtocolMessage {
+    /** Converts the message to a raw `String`, which can be sent over the network.
+     *
+     * This is the inverse of the `message` parser.
+     */
+    pub 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(var) => construct_message(&["SET_SERVER_VAR"], &var.to_protocol()),
+            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| [&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(None) => msg!["CMD", "GREETING"],
+            Greeting(Some(msg)) => msg!["CMD", format!("GREETING {}", msg)],
+            CallVote(None) => msg!["CMD", "CALLVOTE"],
+            CallVote(Some(vote)) => {
+                msg!["CMD", format!("CALLVOTE {}", &vote.to_protocol().join(" "))]
+            }
+            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)],
+        }
+    }
+}
+
+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
+            ],
+            Redirect(port) => msg!["REDIRECT", port],
+            Bye(msg) => msg!["BYE", msg],
+            Nick(nick) => msg!["NICK", nick],
+            Proto(proto) => msg!["PROTO", proto],
+            AskPassword(salt) => msg!["ASKPASSWORD", salt],
+            ServerAuth(hash) => msg!["SERVER_AUTH", hash],
+            LogonPassed => msg!["LOGONPASSED"],
+            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),
+            Joining(name) => msg!["JOINING", name],
+            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],
+            Info(info) => construct_message(&["INFO"], &info),
+            ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
+            ServerVars(vars) => construct_message(&["SERVER_VARS"], &vars),
+            Notice(msg) => msg!["NOTICE", msg],
+            Warning(msg) => msg!["WARNING", msg],
+            Error(msg) => msg!["ERROR", msg],
+            ReplayStart => msg!["REPLAY_START"],
+
+            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-network-protocol/src/parser.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,505 @@
+/** 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::{
+    branch::alt,
+    bytes::complete::{tag, tag_no_case, take_until, take_while},
+    character::complete::{newline, not_line_ending},
+    combinator::{map, peek},
+    error::{ErrorKind, ParseError},
+    multi::separated_list0,
+    sequence::{delimited, pair, preceded, terminated, tuple},
+    Err, IResult,
+};
+
+use std::{
+    num::ParseIntError,
+    str,
+    str::{FromStr, Utf8Error},
+};
+
+use crate::messages::{HwProtocolMessage, HwProtocolMessage::*};
+use crate::types::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType};
+
+#[derive(Debug, PartialEq)]
+pub struct HwProtocolError {}
+
+impl HwProtocolError {
+    pub fn new() -> Self {
+        HwProtocolError {}
+    }
+}
+
+impl<I> ParseError<I> for HwProtocolError {
+    fn from_error_kind(_input: I, _kind: ErrorKind) -> Self {
+        HwProtocolError::new()
+    }
+
+    fn append(_input: I, _kind: ErrorKind, _other: Self) -> Self {
+        HwProtocolError::new()
+    }
+}
+
+impl From<Utf8Error> for HwProtocolError {
+    fn from(_: Utf8Error) -> Self {
+        HwProtocolError::new()
+    }
+}
+
+impl From<ParseIntError> for HwProtocolError {
+    fn from(_: ParseIntError) -> Self {
+        HwProtocolError::new()
+    }
+}
+
+pub type HwResult<'a, O> = IResult<&'a [u8], O, HwProtocolError>;
+
+fn end_of_message(input: &[u8]) -> HwResult<&[u8]> {
+    tag("\n\n")(input)
+}
+
+fn convert_utf8(input: &[u8]) -> HwResult<&str> {
+    match str::from_utf8(input) {
+        Ok(str) => Ok((b"", str)),
+        Err(utf_err) => Result::Err(Err::Failure(utf_err.into())),
+    }
+}
+
+fn convert_from_str<T>(str: &str) -> HwResult<T>
+where
+    T: FromStr<Err = ParseIntError>,
+{
+    match T::from_str(str) {
+        Ok(x) => Ok((b"", x)),
+        Err(format_err) => Result::Err(Err::Failure(format_err.into())),
+    }
+}
+
+fn str_line(input: &[u8]) -> HwResult<&str> {
+    let (i, text) = not_line_ending(<&[u8]>::clone(&input))?;
+    if i != input {
+        Ok((i, convert_utf8(text)?.1))
+    } else {
+        Err(Err::Error(HwProtocolError::new()))
+    }
+}
+
+fn a_line(input: &[u8]) -> HwResult<String> {
+    map(str_line, String::from)(input)
+}
+
+fn cmd_arg(input: &[u8]) -> HwResult<String> {
+    let delimiters = b" \n";
+    let (i, str) = take_while(move |c| !delimiters.contains(&c))(<&[u8]>::clone(&input))?;
+    if i != input {
+        Ok((i, convert_utf8(str)?.1.to_string()))
+    } else {
+        Err(Err::Error(HwProtocolError::new()))
+    }
+}
+
+fn u8_line(input: &[u8]) -> HwResult<u8> {
+    let (i, str) = str_line(input)?;
+    Ok((i, convert_from_str(str)?.1))
+}
+
+fn u16_line(input: &[u8]) -> HwResult<u16> {
+    let (i, str) = str_line(input)?;
+    Ok((i, convert_from_str(str)?.1))
+}
+
+fn u32_line(input: &[u8]) -> HwResult<u32> {
+    let (i, str) = str_line(input)?;
+    Ok((i, convert_from_str(str)?.1))
+}
+
+fn yes_no_line(input: &[u8]) -> HwResult<bool> {
+    alt((
+        map(tag_no_case(b"YES"), |_| true),
+        map(tag_no_case(b"NO"), |_| false),
+    ))(input)
+}
+
+fn opt_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
+    alt((
+        map(peek(end_of_message), |_| None),
+        map(preceded(tag("\n"), a_line), Some),
+    ))(input)
+}
+
+fn spaces(input: &[u8]) -> HwResult<&[u8]> {
+    preceded(tag(" "), take_while(|c| c == b' '))(input)
+}
+
+fn opt_space_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
+    alt((
+        map(peek(end_of_message), |_| None),
+        map(preceded(spaces, a_line), Some),
+    ))(input)
+}
+
+fn hedgehog_array(input: &[u8]) -> HwResult<[HedgehogInfo; 8]> {
+    fn hedgehog_line(input: &[u8]) -> HwResult<HedgehogInfo> {
+        map(
+            tuple((terminated(a_line, newline), a_line)),
+            |(name, hat)| HedgehogInfo { name, hat },
+        )(input)
+    }
+
+    let (i, (h1, h2, h3, h4, h5, h6, h7, h8)) = tuple((
+        terminated(hedgehog_line, newline),
+        terminated(hedgehog_line, newline),
+        terminated(hedgehog_line, newline),
+        terminated(hedgehog_line, newline),
+        terminated(hedgehog_line, newline),
+        terminated(hedgehog_line, newline),
+        terminated(hedgehog_line, newline),
+        hedgehog_line,
+    ))(input)?;
+
+    Ok((i, [h1, h2, h3, h4, h5, h6, h7, h8]))
+}
+
+fn voting(input: &[u8]) -> HwResult<VoteType> {
+    alt((
+        map(tag_no_case("PAUSE"), |_| VoteType::Pause),
+        map(tag_no_case("NEWSEED"), |_| VoteType::NewSeed),
+        map(
+            preceded(pair(tag_no_case("KICK"), spaces), a_line),
+            VoteType::Kick,
+        ),
+        map(
+            preceded(pair(tag_no_case("HEDGEHOGS"), spaces), u8_line),
+            VoteType::HedgehogsPerTeam,
+        ),
+        map(preceded(tag_no_case("MAP"), opt_space_arg), VoteType::Map),
+    ))(input)
+}
+
+fn no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+    fn message<'a>(
+        name: &'a str,
+        msg: HwProtocolMessage,
+    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+        move |i| map(tag(name), |_| msg.clone())(i)
+    }
+
+    alt((
+        message("PING", Ping),
+        message("PONG", Pong),
+        message("LIST", List),
+        message("BANLIST", BanList),
+        message("GET_SERVER_VAR", GetServerVar),
+        message("TOGGLE_READY", ToggleReady),
+        message("START_GAME", StartGame),
+        message("TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
+        message("TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
+        message("TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
+    ))(input)
+}
+
+fn single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+    fn message<'a, T, F, G>(
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
+    where
+        F: Fn(&[u8]) -> HwResult<T>,
+        G: Fn(T) -> HwProtocolMessage,
+    {
+        map(preceded(tag(name), parser), constructor)
+    }
+
+    alt((
+        message("NICK\n", a_line, Nick),
+        message("INFO\n", a_line, Info),
+        message("CHAT\n", a_line, Chat),
+        message("PART", opt_arg, Part),
+        message("FOLLOW\n", a_line, Follow),
+        message("KICK\n", a_line, Kick),
+        message("UNBAN\n", a_line, Unban),
+        message("EM\n", a_line, EngineMessage),
+        message("TEAMCHAT\n", a_line, TeamChat),
+        message("ROOM_NAME\n", a_line, RoomName),
+        message("REMOVE_TEAM\n", a_line, RemoveTeam),
+        message("ROUNDFINISHED", opt_arg, |_| RoundFinished),
+        message("PROTO\n", u16_line, Proto),
+        message("QUIT", opt_arg, Quit),
+    ))(input)
+}
+
+fn cmd_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+    fn cmd_no_arg<'a>(
+        name: &'a str,
+        msg: HwProtocolMessage,
+    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+        move |i| map(tag_no_case(name), |_| msg.clone())(i)
+    }
+
+    fn cmd_single_arg<'a, T, F, G>(
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
+    where
+        F: Fn(&'a [u8]) -> HwResult<'a, T>,
+        G: Fn(T) -> HwProtocolMessage,
+    {
+        map(
+            preceded(pair(tag_no_case(name), spaces), parser),
+            constructor,
+        )
+    }
+
+    fn cmd_no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+        alt((
+            cmd_no_arg("STATS", Stats),
+            cmd_no_arg("FIX", Fix),
+            cmd_no_arg("UNFIX", Unfix),
+            cmd_no_arg("REGISTERED_ONLY", ToggleServerRegisteredOnly),
+            cmd_no_arg("SUPER_POWER", SuperPower),
+        ))(input)
+    }
+
+    fn cmd_single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+        alt((
+            cmd_single_arg("RESTART_SERVER", |i| tag("YES")(i), |_| RestartServer),
+            cmd_single_arg("DELEGATE", a_line, Delegate),
+            cmd_single_arg("DELETE", a_line, Delete),
+            cmd_single_arg("SAVEROOM", a_line, SaveRoom),
+            cmd_single_arg("LOADROOM", a_line, LoadRoom),
+            cmd_single_arg("GLOBAL", a_line, Global),
+            cmd_single_arg("WATCH", u32_line, Watch),
+            cmd_single_arg("VOTE", yes_no_line, Vote),
+            cmd_single_arg("FORCE", yes_no_line, ForceVote),
+            cmd_single_arg("INFO", a_line, Info),
+            cmd_single_arg("MAXTEAMS", u8_line, MaxTeams),
+            cmd_single_arg("CALLVOTE", voting, |v| CallVote(Some(v))),
+        ))(input)
+    }
+
+    preceded(
+        tag("CMD\n"),
+        alt((
+            cmd_no_arg_message,
+            cmd_single_arg_message,
+            map(tag_no_case("CALLVOTE"), |_| CallVote(None)),
+            map(preceded(tag_no_case("GREETING"), opt_space_arg), Greeting),
+            map(preceded(tag_no_case("PART"), opt_space_arg), Part),
+            map(preceded(tag_no_case("QUIT"), opt_space_arg), Quit),
+            map(
+                preceded(
+                    tag_no_case("SAVE"),
+                    pair(preceded(spaces, cmd_arg), preceded(spaces, cmd_arg)),
+                ),
+                |(n, l)| Save(n, l),
+            ),
+            map(
+                preceded(
+                    tag_no_case("RND"),
+                    alt((
+                        map(peek(end_of_message), |_| vec![]),
+                        preceded(spaces, separated_list0(spaces, cmd_arg)),
+                    )),
+                ),
+                Rnd,
+            ),
+        )),
+    )(input)
+}
+
+fn config_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+    fn cfg_single_arg<'a, T, F, G>(
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, GameCfg>
+    where
+        F: Fn(&[u8]) -> HwResult<T>,
+        G: Fn(T) -> GameCfg,
+    {
+        map(preceded(pair(tag(name), newline), parser), constructor)
+    }
+
+    let (i, cfg) = preceded(
+        tag("CFG\n"),
+        alt((
+            cfg_single_arg("THEME", a_line, GameCfg::Theme),
+            cfg_single_arg("SCRIPT", a_line, GameCfg::Script),
+            cfg_single_arg("MAP", a_line, GameCfg::MapType),
+            cfg_single_arg("MAPGEN", u32_line, GameCfg::MapGenerator),
+            cfg_single_arg("MAZE_SIZE", u32_line, GameCfg::MazeSize),
+            cfg_single_arg("TEMPLATE", u32_line, GameCfg::Template),
+            cfg_single_arg("FEATURE_SIZE", u32_line, GameCfg::FeatureSize),
+            cfg_single_arg("SEED", a_line, GameCfg::Seed),
+            cfg_single_arg("DRAWNMAP", a_line, GameCfg::DrawnMap),
+            preceded(pair(tag("AMMO"), newline), |i| {
+                let (i, name) = a_line(i)?;
+                let (i, value) = opt_arg(i)?;
+                Ok((i, GameCfg::Ammo(name, value)))
+            }),
+            preceded(
+                pair(tag("SCHEME"), newline),
+                map(
+                    pair(
+                        a_line,
+                        alt((
+                            map(peek(end_of_message), |_| None),
+                            map(preceded(newline, separated_list0(newline, a_line)), Some),
+                        )),
+                    ),
+                    |(name, values)| GameCfg::Scheme(name, values.unwrap_or_default()),
+                ),
+            ),
+        )),
+    )(input)?;
+    Ok((i, Cfg(cfg)))
+}
+
+fn server_var_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+    map(
+        preceded(
+            tag("SET_SERVER_VAR\n"),
+            alt((
+                map(preceded(tag("MOTD_NEW\n"), a_line), ServerVar::MOTDNew),
+                map(preceded(tag("MOTD_OLD\n"), a_line), ServerVar::MOTDOld),
+                map(
+                    preceded(tag("LATEST_PROTO\n"), u16_line),
+                    ServerVar::LatestProto,
+                ),
+            )),
+        ),
+        SetServerVar,
+    )(input)
+}
+
+fn complex_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+    alt((
+        preceded(
+            pair(tag("PASSWORD"), newline),
+            map(pair(terminated(a_line, newline), a_line), |(pass, salt)| {
+                Password(pass, salt)
+            }),
+        ),
+        preceded(
+            pair(tag("CHECKER"), newline),
+            map(
+                tuple((
+                    terminated(u16_line, newline),
+                    terminated(a_line, newline),
+                    a_line,
+                )),
+                |(protocol, name, pass)| Checker(protocol, name, pass),
+            ),
+        ),
+        preceded(
+            pair(tag("CREATE_ROOM"), newline),
+            map(pair(a_line, opt_arg), |(name, pass)| CreateRoom(name, pass)),
+        ),
+        preceded(
+            pair(tag("JOIN_ROOM"), newline),
+            map(pair(a_line, opt_arg), |(name, pass)| JoinRoom(name, pass)),
+        ),
+        preceded(
+            pair(tag("ADD_TEAM"), newline),
+            map(
+                tuple((
+                    terminated(a_line, newline),
+                    terminated(u8_line, newline),
+                    terminated(a_line, newline),
+                    terminated(a_line, newline),
+                    terminated(a_line, newline),
+                    terminated(a_line, newline),
+                    terminated(u8_line, newline),
+                    hedgehog_array,
+                )),
+                |(name, color, grave, fort, voice_pack, flag, difficulty, hedgehogs)| {
+                    AddTeam(Box::new(TeamInfo {
+                        owner: String::new(),
+                        name,
+                        color,
+                        grave,
+                        fort,
+                        voice_pack,
+                        flag,
+                        difficulty,
+                        hedgehogs,
+                        hedgehogs_number: 0,
+                    }))
+                },
+            ),
+        ),
+        preceded(
+            pair(tag("HH_NUM"), newline),
+            map(
+                pair(terminated(a_line, newline), u8_line),
+                |(name, count)| SetHedgehogsNumber(name, count),
+            ),
+        ),
+        preceded(
+            pair(tag("TEAM_COLOR"), newline),
+            map(
+                pair(terminated(a_line, newline), u8_line),
+                |(name, color)| SetTeamColor(name, color),
+            ),
+        ),
+        preceded(
+            pair(tag("BAN"), newline),
+            map(
+                tuple((
+                    terminated(a_line, newline),
+                    terminated(a_line, newline),
+                    u32_line,
+                )),
+                |(name, reason, time)| Ban(name, reason, time),
+            ),
+        ),
+        preceded(
+            pair(tag("BAN_IP"), newline),
+            map(
+                tuple((
+                    terminated(a_line, newline),
+                    terminated(a_line, newline),
+                    u32_line,
+                )),
+                |(ip, reason, time)| BanIp(ip, reason, time),
+            ),
+        ),
+        preceded(
+            pair(tag("BAN_NICK"), newline),
+            map(
+                tuple((
+                    terminated(a_line, newline),
+                    terminated(a_line, newline),
+                    u32_line,
+                )),
+                |(nick, reason, time)| BanNick(nick, reason, time),
+            ),
+        ),
+    ))(input)
+}
+
+pub fn malformed_message(input: &[u8]) -> HwResult<()> {
+    map(terminated(take_until(&b"\n\n"[..]), end_of_message), |_| ())(input)
+}
+
+pub fn message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+    delimited(
+        take_while(|c| c == b'\n'),
+        alt((
+            no_arg_message,
+            single_arg_message,
+            cmd_message,
+            config_message,
+            server_var_message,
+            complex_message,
+        )),
+        end_of_message,
+    )(input)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/types.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,358 @@
+use serde_derive::{Deserialize, Serialize};
+
+pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8;
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum ServerVar {
+    MOTDNew(String),
+    MOTDOld(String),
+    LatestProto(u16),
+}
+
+#[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, Default)]
+pub struct TeamInfo {
+    pub owner: String,
+    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, Default)]
+pub struct HedgehogInfo {
+    pub name: String,
+    pub hat: String,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct Ammo {
+    pub name: String,
+    pub settings: Option<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct Scheme {
+    pub name: String,
+    pub settings: Vec<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct RoomConfig {
+    pub feature_size: u32,
+    pub map_type: String,
+    pub map_generator: u32,
+    pub maze_size: u32,
+    pub seed: String,
+    pub template: u32,
+
+    pub ammo: Ammo,
+    pub scheme: Scheme,
+    pub script: String,
+    pub theme: String,
+    pub drawn_map: Option<String>,
+}
+
+impl RoomConfig {
+    pub 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,
+        }
+    }
+
+    pub fn set_config(&mut self, cfg: GameCfg) {
+        match cfg {
+            GameCfg::FeatureSize(s) => self.feature_size = s,
+            GameCfg::MapType(t) => self.map_type = t,
+            GameCfg::MapGenerator(g) => self.map_generator = g,
+            GameCfg::MazeSize(s) => self.maze_size = s,
+            GameCfg::Seed(s) => self.seed = s,
+            GameCfg::Template(t) => self.template = t,
+
+            GameCfg::Ammo(n, s) => {
+                self.ammo = Ammo {
+                    name: n,
+                    settings: s,
+                }
+            }
+            GameCfg::Scheme(n, s) => {
+                self.scheme = Scheme {
+                    name: n,
+                    settings: s,
+                }
+            }
+            GameCfg::Script(s) => self.script = s,
+            GameCfg::Theme(t) => self.theme = t,
+            GameCfg::DrawnMap(m) => self.drawn_map = Some(m),
+        };
+    }
+
+    pub fn to_map_config(&self) -> Vec<String> {
+        vec![
+            self.feature_size.to_string(),
+            self.map_type.to_string(),
+            self.map_generator.to_string(),
+            self.maze_size.to_string(),
+            self.seed.to_string(),
+            self.template.to_string(),
+        ]
+    }
+
+    pub fn to_game_config(&self) -> Vec<GameCfg> {
+        use GameCfg::*;
+        let mut v = vec![
+            Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()),
+            Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()),
+            Script(self.script.to_string()),
+            Theme(self.theme.to_string()),
+        ];
+        if let Some(ref m) = self.drawn_map {
+            v.push(DrawnMap(m.to_string()))
+        }
+        v
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum VoteType {
+    Kick(String),
+    Map(Option<String>),
+    Pause,
+    NewSeed,
+    HedgehogsPerTeam(u8),
+}
+
+pub struct Vote {
+    pub is_pro: bool,
+    pub is_forced: bool,
+}
+
+//#[cfg(test)]
+#[macro_use]
+pub mod testing {
+    use crate::types::ServerVar::*;
+    use crate::types::*;
+    use proptest::{
+        arbitrary::{any, Arbitrary},
+        strategy::{BoxedStrategy, Just, Strategy},
+    };
+
+    // Due to inability to define From between Options
+    pub 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_export]
+    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_export]
+    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)]
+    pub 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::types::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 {
+                        owner: String::new(),
+                        name,
+                        color,
+                        grave,
+                        fort,
+                        voice_pack,
+                        flag,
+                        difficulty,
+                        hedgehogs,
+                        hedgehogs_number: 0,
+                    }
+                })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<TeamInfo>;
+    }
+
+    impl Arbitrary for ServerVar {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (0..=2)
+                .no_shrink()
+                .prop_flat_map(|i| {
+                    proto_msg_match!(i, def = ServerVar::LatestProto(0),
+                        0 => MOTDNew(Ascii),
+                        1 => MOTDOld(Ascii),
+                        2 => LatestProto(u16)
+                    )
+                })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<ServerVar>;
+    }
+
+    impl Arbitrary for VoteType {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            use VoteType::*;
+            (0..=4)
+                .no_shrink()
+                .prop_flat_map(|i| {
+                    proto_msg_match!(i, def = VoteType::Pause,
+                        0 => Kick(Ascii),
+                        1 => Map(Option<Ascii>),
+                        2 => Pause(),
+                        3 => NewSeed(),
+                        4 => HedgehogsPerTeam(u8)
+                    )
+                })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<VoteType>;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/tests/parser.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,54 @@
+use hedgewars_network_protocol::{
+    parser::message,
+    types::GameCfg,
+    {messages::HwProtocolMessage::*, parser::HwProtocolError},
+};
+
+#[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 49471\n\n"),
+        Ok((&b""[..], Watch(49471)))
+    );
+    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!(
+        message(b"CFG\nSCHEME\na\nA\n\n"),
+        Ok((
+            &b""[..],
+            Cfg(GameCfg::Scheme("a".to_string(), vec!["A".to_string()]))
+        ))
+    );
+
+    assert_eq!(
+        message(b"QUIT\n1\n2\n\n"),
+        Err(nom::Err::Error(HwProtocolError::new()))
+    );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/tests/test.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,83 @@
+use proptest::{
+    arbitrary::any,
+    proptest,
+    strategy::{BoxedStrategy, Just, Strategy},
+};
+
+use hedgewars_network_protocol::messages::{HwProtocolMessage, HwProtocolMessage::*};
+use hedgewars_network_protocol::parser::message;
+use hedgewars_network_protocol::types::{GameCfg, ServerVar, TeamInfo, VoteType};
+
+use hedgewars_network_protocol::types::testing::*;
+use hedgewars_network_protocol::{proto_msg_case, proto_msg_match};
+
+pub fn gen_proto_msg() -> BoxedStrategy<HwProtocolMessage> where {
+    let res = (0..=55).no_shrink().prop_flat_map(|i| {
+        proto_msg_match!(i, def = Ping,
+            0 => Ping(),
+            1 => Pong(),
+            2 => Quit(Option<Ascii>),
+            4 => Global(Ascii),
+            5 => Watch(u32),
+            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(Option<Ascii>),
+            49 => CallVote(Option<VoteType>),
+            50 => Vote(bool),
+            51 => ForceVote(bool),
+            52 => Save(Ascii, Ascii),
+            53 => Delete(Ascii),
+            54 => SaveRoom(Ascii),
+            55 => LoadRoom(Ascii)
+        )
+    });
+    res.boxed()
+}
+
+proptest! {
+    #[test]
+    fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
+        println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
+        assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
+    }
+}
--- a/rust/hedgewars-server/Cargo.toml	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/Cargo.toml	Wed Jun 23 23:41:51 2021 +0200
@@ -1,7 +1,7 @@
 [package]
 edition = "2018"
 name = "hedgewars-server"
-version = "0.0.1"
+version = "0.9.0"
 authors = [ "Andrey Korotaev <a.korotaev@hedgewars.org>" ]
 
 [features]
@@ -26,6 +26,7 @@
 serde_derive = "1.0"
 openssl = { version = "0.10", optional = true }
 mysql = { version = "15.0", optional = true }
+hedgewars-network-protocol = { path = "../hedgewars-network-protocol" }
 
 [dev-dependencies]
 proptest = "1.0"
--- a/rust/hedgewars-server/src/core/room.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/core/room.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,10 +1,11 @@
 use super::{
     client::HwClient,
-    types::{
-        ClientId, GameCfg, GameCfg::*, RoomConfig, RoomId, TeamInfo, Voting, MAX_HEDGEHOGS_PER_TEAM,
-    },
+    types::{ClientId, RoomId, Voting},
 };
 use bitflags::*;
+use hedgewars_network_protocol::types::{
+    GameCfg, GameCfg::*, RoomConfig, TeamInfo, MAX_HEDGEHOGS_PER_TEAM,
+};
 use serde::{Deserialize, Serialize};
 use serde_derive::{Deserialize, Serialize};
 use serde_yaml;
--- a/rust/hedgewars-server/src/core/server.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/core/server.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -3,9 +3,10 @@
     client::HwClient,
     indexslab::IndexSlab,
     room::HwRoom,
-    types::{ClientId, GameCfg, RoomId, ServerVar, TeamInfo, Vote, VoteType, Voting},
+    types::{ClientId, RoomId, Voting},
 };
 use crate::utils;
+use hedgewars_network_protocol::types::{GameCfg, ServerVar, TeamInfo, Vote, VoteType};
 
 use bitflags::*;
 use log::*;
--- a/rust/hedgewars-server/src/core/types.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/core/types.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,158 +1,9 @@
+use hedgewars_network_protocol::types::{RoomConfig, TeamInfo, VoteType};
 use serde_derive::{Deserialize, Serialize};
 
 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(u16),
-}
-
-#[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, Default)]
-pub struct TeamInfo {
-    pub owner: String,
-    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, Default)]
-pub struct HedgehogInfo {
-    pub name: String,
-    pub hat: String,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub struct Ammo {
-    pub name: String,
-    pub settings: Option<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub struct Scheme {
-    pub name: String,
-    pub settings: Vec<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub struct RoomConfig {
-    pub feature_size: u32,
-    pub map_type: String,
-    pub map_generator: u32,
-    pub maze_size: u32,
-    pub seed: String,
-    pub template: u32,
-
-    pub ammo: Ammo,
-    pub scheme: Scheme,
-    pub script: String,
-    pub theme: String,
-    pub drawn_map: Option<String>,
-}
-
-impl RoomConfig {
-    pub 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,
-        }
-    }
-
-    pub fn set_config(&mut self, cfg: GameCfg) {
-        match cfg {
-            GameCfg::FeatureSize(s) => self.feature_size = s,
-            GameCfg::MapType(t) => self.map_type = t,
-            GameCfg::MapGenerator(g) => self.map_generator = g,
-            GameCfg::MazeSize(s) => self.maze_size = s,
-            GameCfg::Seed(s) => self.seed = s,
-            GameCfg::Template(t) => self.template = t,
-
-            GameCfg::Ammo(n, s) => {
-                self.ammo = Ammo {
-                    name: n,
-                    settings: s,
-                }
-            }
-            GameCfg::Scheme(n, s) => {
-                self.scheme = Scheme {
-                    name: n,
-                    settings: s,
-                }
-            }
-            GameCfg::Script(s) => self.script = s,
-            GameCfg::Theme(t) => self.theme = t,
-            GameCfg::DrawnMap(m) => self.drawn_map = Some(m),
-        };
-    }
-
-    pub fn to_map_config(&self) -> Vec<String> {
-        vec![
-            self.feature_size.to_string(),
-            self.map_type.to_string(),
-            self.map_generator.to_string(),
-            self.maze_size.to_string(),
-            self.seed.to_string(),
-            self.template.to_string(),
-        ]
-    }
-
-    pub fn to_game_config(&self) -> Vec<GameCfg> {
-        use GameCfg::*;
-        let mut v = vec![
-            Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()),
-            Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()),
-            Script(self.script.to_string()),
-            Theme(self.theme.to_string()),
-        ];
-        if let Some(ref m) = self.drawn_map {
-            v.push(DrawnMap(m.to_string()))
-        }
-        v
-    }
-}
-
 #[derive(Debug)]
 pub struct Replay {
     pub config: RoomConfig,
@@ -160,20 +11,6 @@
     pub message_log: Vec<String>,
 }
 
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum VoteType {
-    Kick(String),
-    Map(Option<String>),
-    Pause,
-    NewSeed,
-    HedgehogsPerTeam(u8),
-}
-
-pub struct Vote {
-    pub is_pro: bool,
-    pub is_forced: bool,
-}
-
 #[derive(Clone, Debug)]
 pub struct Voting {
     pub ttl: u32,
--- a/rust/hedgewars-server/src/handlers.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -10,19 +10,24 @@
     inanteroom::LoginResult,
     strings::*,
 };
+use crate::handlers::actions::ToPendingMessage;
 use crate::{
     core::{
         anteroom::HwAnteroom,
         room::RoomSave,
         server::HwServer,
-        types::{ClientId, GameCfg, Replay, RoomId, TeamInfo},
+        types::{ClientId, Replay, RoomId},
     },
-    protocol::messages::{
+    utils,
+};
+use hedgewars_network_protocol::{
+    messages::{
         global_chat, server_chat, HwProtocolMessage, HwProtocolMessage::EngineMessage,
         HwServerMessage, HwServerMessage::*,
     },
-    utils,
+    types::{GameCfg, TeamInfo},
 };
+
 use base64::encode;
 use log::*;
 use rand::{thread_rng, RngCore};
--- a/rust/hedgewars-server/src/handlers/actions.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/actions.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -4,11 +4,14 @@
         room::HwRoom,
         room::{GameInfo, RoomFlags},
         server::HwServer,
-        types::{ClientId, GameCfg, RoomId, VoteType},
+        types::{ClientId, RoomId},
     },
-    protocol::messages::{server_chat, HwProtocolMessage, HwServerMessage, HwServerMessage::*},
     utils::to_engine_msg,
 };
+use hedgewars_network_protocol::{
+    messages::{server_chat, HwProtocolMessage, HwServerMessage, HwServerMessage::*},
+    types::{GameCfg, VoteType},
+};
 use rand::{distributions::Uniform, thread_rng, Rng};
 use std::{io, io::Write, iter::once, mem::replace};
 
@@ -101,20 +104,28 @@
     }
 }
 
-impl HwServerMessage {
-    pub fn send(self, client_id: ClientId) -> PendingMessage {
+pub trait ToPendingMessage {
+    fn send(self, client_id: ClientId) -> PendingMessage;
+    fn send_many(self, client_ids: Vec<ClientId>) -> PendingMessage;
+    fn send_self(self) -> PendingMessage;
+    fn send_all(self) -> PendingMessage;
+    fn send_to_destination(self, destination: Destination) -> PendingMessage;
+}
+
+impl ToPendingMessage for HwServerMessage {
+    fn send(self, client_id: ClientId) -> PendingMessage {
         PendingMessage::send(self, client_id)
     }
-    pub fn send_many(self, client_ids: Vec<ClientId>) -> PendingMessage {
+    fn send_many(self, client_ids: Vec<ClientId>) -> PendingMessage {
         PendingMessage::send_many(self, client_ids)
     }
-    pub fn send_self(self) -> PendingMessage {
+    fn send_self(self) -> PendingMessage {
         PendingMessage::send_self(self)
     }
-    pub fn send_all(self) -> PendingMessage {
+    fn send_all(self) -> PendingMessage {
         PendingMessage::send_all(self)
     }
-    pub fn send_to_destination(self, destination: Destination) -> PendingMessage {
+    fn send_to_destination(self, destination: Destination) -> PendingMessage {
         PendingMessage {
             destination,
             message: self,
--- a/rust/hedgewars-server/src/handlers/checker.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/checker.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,10 +1,8 @@
 use log::*;
 use mio;
 
-use crate::{
-    core::{server::HwServer, types::ClientId},
-    protocol::messages::HwProtocolMessage,
-};
+use crate::core::{server::HwServer, types::ClientId};
+use hedgewars_network_protocol::messages::HwProtocolMessage;
 
 pub fn handle(_server: &mut HwServer, _client_id: ClientId, message: HwProtocolMessage) {
     match message {
--- a/rust/hedgewars-server/src/handlers/common.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/common.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,3 +1,8 @@
+use super::{
+    actions::{Destination, DestinationGroup},
+    Response,
+};
+use crate::handlers::actions::ToPendingMessage;
 use crate::{
     core::{
         client::HwClient,
@@ -6,23 +11,19 @@
             EndGameResult, HwRoomControl, HwServer, JoinRoomError, LeaveRoomResult, StartGameError,
             VoteError, VoteResult,
         },
-        types::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType, MAX_HEDGEHOGS_PER_TEAM},
+        types::{ClientId, RoomId},
     },
-    protocol::messages::{
+    utils::to_engine_msg,
+};
+use hedgewars_network_protocol::{
+    messages::{
         add_flags, remove_flags, server_chat,
         HwProtocolMessage::{self, Rnd},
         HwServerMessage::{self, *},
         ProtocolFlags as Flags,
     },
-    utils::to_engine_msg,
+    types::{GameCfg, RoomConfig, TeamInfo, Vote, VoteType, MAX_HEDGEHOGS_PER_TEAM},
 };
-
-use super::{
-    actions::{Destination, DestinationGroup},
-    Response,
-};
-
-use crate::core::types::RoomConfig;
 use rand::{self, seq::SliceRandom, thread_rng, Rng};
 use std::{iter::once, mem::replace};
 
@@ -644,7 +645,7 @@
 mod tests {
     use super::*;
     use crate::handlers::actions::PendingMessage;
-    use crate::protocol::messages::HwServerMessage::ChatMsg;
+    use hedgewars_network_protocol::messages::HwServerMessage::ChatMsg;
 
     fn reply2string(r: HwServerMessage) -> String {
         match r {
--- a/rust/hedgewars-server/src/handlers/inanteroom.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/inanteroom.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,6 +1,7 @@
 use mio;
 
 use super::strings::*;
+use crate::handlers::actions::ToPendingMessage;
 use crate::{
     core::{
         anteroom::{HwAnteroom, HwAnteroomClient},
@@ -8,10 +9,11 @@
         server::HwServer,
         types::ClientId,
     },
-    protocol::messages::{HwProtocolMessage, HwProtocolMessage::LoadRoom, HwServerMessage::*},
     utils::is_name_illegal,
 };
-
+use hedgewars_network_protocol::messages::{
+    HwProtocolMessage, HwProtocolMessage::LoadRoom, HwServerMessage::*,
+};
 use log::*;
 #[cfg(feature = "official-server")]
 use openssl::sha::sha1;
--- a/rust/hedgewars-server/src/handlers/inlobby.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/inlobby.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,15 +1,19 @@
 use super::{common::rnd_reply, strings::*};
+use crate::handlers::actions::ToPendingMessage;
 use crate::{
     core::{
         client::HwClient,
         server::{AccessError, CreateRoomError, HwServer, JoinRoomError},
-        types::{ClientId, ServerVar},
+        types::ClientId,
     },
-    protocol::messages::{
+    utils::is_name_illegal,
+};
+use hedgewars_network_protocol::{
+    messages::{
         add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
         ProtocolFlags as Flags,
     },
-    utils::is_name_illegal,
+    types::ServerVar,
 };
 use log::*;
 use std::{collections::HashSet, convert::identity};
@@ -20,7 +24,7 @@
     response: &mut super::Response,
     message: HwProtocolMessage,
 ) {
-    use crate::protocol::messages::HwProtocolMessage::*;
+    use hedgewars_network_protocol::messages::HwProtocolMessage::*;
 
     match message {
         CreateRoom(name, password) => match server.create_room(client_id, name, password) {
--- a/rust/hedgewars-server/src/handlers/inroom.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/inroom.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,6 +1,7 @@
 use super::{common::rnd_reply, strings::*};
 use crate::core::room::GameInfo;
 use crate::core::server::{AddTeamError, SetTeamCountError};
+use crate::handlers::actions::ToPendingMessage;
 use crate::{
     core::{
         room::{HwRoom, RoomFlags, MAX_TEAMS_IN_ROOM},
@@ -8,16 +9,19 @@
             ChangeMasterError, ChangeMasterResult, HwRoomControl, HwServer, LeaveRoomResult,
             ModifyTeamError, StartGameError,
         },
-        types,
-        types::{ClientId, GameCfg, RoomId, VoteType, Voting, MAX_HEDGEHOGS_PER_TEAM},
-    },
-    protocol::messages::{
-        add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
-        ProtocolFlags as Flags,
+        types::{ClientId, RoomId, Voting},
     },
     utils::{is_name_illegal, to_engine_msg},
 };
 use base64::{decode, encode};
+use hedgewars_network_protocol::{
+    messages::{
+        add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
+        ProtocolFlags as Flags,
+    },
+    types,
+    types::{GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM},
+};
 use log::*;
 use std::{cmp::min, iter::once, mem::swap};
 
@@ -87,7 +91,7 @@
 }
 
 fn room_message_flag(msg: &HwProtocolMessage) -> RoomFlags {
-    use crate::protocol::messages::HwProtocolMessage::*;
+    use hedgewars_network_protocol::messages::HwProtocolMessage::*;
     match msg {
         ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN,
         ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD,
@@ -104,7 +108,7 @@
     let (client, room) = room_control.get();
     let (client_id, room_id) = (client.id, room.id);
 
-    use crate::protocol::messages::HwProtocolMessage::*;
+    use hedgewars_network_protocol::messages::HwProtocolMessage::*;
     match message {
         Part(msg) => {
             let msg = match msg {
--- a/rust/hedgewars-server/src/protocol.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/protocol.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,13 +1,11 @@
-use self::parser::message;
+use hedgewars_network_protocol::{
+    messages::HwProtocolMessage,
+    parser::{malformed_message, message},
+};
 use log::*;
 use netbuf;
 use std::io::{Read, Result};
 
-pub mod messages;
-mod parser;
-#[cfg(test)]
-pub mod test;
-
 pub struct ProtocolDecoder {
     buf: netbuf::Buf,
     is_recovering: bool,
@@ -22,7 +20,7 @@
     }
 
     fn recover(&mut self) -> bool {
-        self.is_recovering = match parser::malformed_message(&self.buf[..]) {
+        self.is_recovering = match malformed_message(&self.buf[..]) {
             Ok((tail, ())) => {
                 let length = tail.len();
                 self.buf.consume(self.buf.len() - length);
@@ -44,11 +42,11 @@
         Ok(count)
     }
 
-    pub fn extract_messages(&mut self) -> Vec<messages::HwProtocolMessage> {
+    pub fn extract_messages(&mut self) -> Vec<HwProtocolMessage> {
         let mut messages = vec![];
         if !self.is_recovering {
             while !self.buf.is_empty() {
-                match parser::message(&self.buf[..]) {
+                match message(&self.buf[..]) {
                     Ok((tail, message)) => {
                         messages.push(message);
                         let length = tail.len();
--- a/rust/hedgewars-server/src/protocol/messages.rs	Wed Jun 23 15:32:48 2021 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,425 +0,0 @@
-use crate::core::types::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType};
-use std::{convert::From, iter::once, ops};
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum HwProtocolMessage {
-    // common messages
-    Ping,
-    Pong,
-    Quit(Option<String>),
-    Global(String),
-    Watch(u32),
-    ToggleServerRegisteredOnly,
-    SuperPower,
-    Info(String),
-    // anteroom messages
-    Nick(String),
-    Proto(u16),
-    Password(String, String),
-    Checker(u16, String, String),
-    // lobby messages
-    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,
-    // room messages
-    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(Option<String>),
-    CallVote(Option<VoteType>),
-    Vote(bool),
-    ForceVote(bool),
-    Save(String, String),
-    Delete(String),
-    SaveRoom(String),
-    LoadRoom(String),
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum ProtocolFlags {
-    InRoom,
-    RoomMaster,
-    Ready,
-    InGame,
-    Registered,
-    Admin,
-    Contributor,
-}
-
-impl ProtocolFlags {
-    #[inline]
-    fn flag_char(&self) -> char {
-        match self {
-            ProtocolFlags::InRoom => 'i',
-            ProtocolFlags::RoomMaster => 'h',
-            ProtocolFlags::Ready => 'r',
-            ProtocolFlags::InGame => 'g',
-            ProtocolFlags::Registered => 'u',
-            ProtocolFlags::Admin => 'a',
-            ProtocolFlags::Contributor => 'c',
-        }
-    }
-
-    #[inline]
-    fn format(prefix: char, flags: &[ProtocolFlags]) -> String {
-        once(prefix)
-            .chain(flags.iter().map(|f| f.flag_char()))
-            .collect()
-    }
-}
-
-#[inline]
-pub fn add_flags(flags: &[ProtocolFlags]) -> String {
-    ProtocolFlags::format('+', flags)
-}
-
-#[inline]
-pub fn remove_flags(flags: &[ProtocolFlags]) -> String {
-    ProtocolFlags::format('-', flags)
-}
-
-#[derive(Debug)]
-pub enum HwServerMessage {
-    Connected(u32),
-    Redirect(u16),
-
-    Ping,
-    Pong,
-    Bye(String),
-
-    Nick(String),
-    Proto(u16),
-    AskPassword(String),
-    ServerAuth(String),
-    LogonPassed,
-
-    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>),
-    Joining(String),
-    TeamAdd(Vec<String>),
-    TeamRemove(String),
-    TeamAccepted(String),
-    TeamColor(String, u8),
-    HedgehogsNumber(String, u8),
-    ConfigEntry(String, Vec<String>),
-    Kicked,
-    RunGame,
-    ForwardEngineMessage(Vec<String>),
-    RoundFinished,
-    ReplayStart,
-
-    Info(Vec<String>),
-    ServerMessage(String),
-    ServerVars(Vec<String>),
-    Notice(String),
-    Warning(String),
-    Error(String),
-    Unreachable,
-
-    //Deprecated messages
-    LegacyReady(bool, Vec<String>),
-}
-
-fn special_chat(nick: &str, msg: String) -> HwServerMessage {
-    HwServerMessage::ChatMsg {
-        nick: nick.to_string(),
-        msg,
-    }
-}
-
-pub fn server_chat(msg: String) -> HwServerMessage {
-    special_chat("[server]", msg)
-}
-
-pub fn global_chat(msg: String) -> HwServerMessage {
-    special_chat("(global notice)", msg)
-}
-
-impl ServerVar {
-    pub fn to_protocol(&self) -> Vec<String> {
-        use ServerVar::*;
-        match self {
-            MOTDNew(s) => vec!["MOTD_NEW".to_string(), s.clone()],
-            MOTDOld(s) => vec!["MOTD_OLD".to_string(), s.clone()],
-            LatestProto(n) => vec!["LATEST_PROTO".to_string(), n.to_string()],
-        }
-    }
-}
-
-impl VoteType {
-    pub fn to_protocol(&self) -> Vec<String> {
-        use VoteType::*;
-        match self {
-            Kick(nick) => vec!["KICK".to_string(), nick.clone()],
-            Map(None) => vec!["MAP".to_string()],
-            Map(Some(name)) => vec!["MAP".to_string(), name.clone()],
-            Pause => vec!["PAUSE".to_string()],
-            NewSeed => vec!["NEWSEED".to_string()],
-            HedgehogsPerTeam(count) => vec!["HEDGEHOGS".to_string(), count.to_string()],
-        }
-    }
-}
-
-impl GameCfg {
-    pub fn to_protocol(&self) -> (String, Vec<String>) {
-        use 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());
-                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)
-    }
-}
-
-impl TeamInfo {
-    pub fn to_protocol(&self) -> Vec<String> {
-        let mut info = vec![
-            self.name.clone(),
-            self.grave.clone(),
-            self.fort.clone(),
-            self.voice_pack.clone(),
-            self.flag.clone(),
-            self.owner.clone(),
-            self.difficulty.to_string(),
-        ];
-        let hogs = self
-            .hedgehogs
-            .iter()
-            .flat_map(|h| once(h.name.clone()).chain(once(h.hat.clone())));
-        info.extend(hogs);
-        info
-    }
-}
-
-macro_rules! const_braces {
-    ($e: expr) => {
-        "{}\n"
-    };
-}
-
-macro_rules! msg {
-    [$($part: expr),*] => {
-        format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
-    };
-}
-
-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(var) => construct_message(&["SET_SERVER_VAR"], &var.to_protocol()),
-            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| [&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(None) => msg!["CMD", "GREETING"],
-            Greeting(Some(msg)) => msg!["CMD", format!("GREETING {}", msg)],
-            CallVote(None) => msg!["CMD", "CALLVOTE"],
-            CallVote(Some(vote)) => {
-                msg!["CMD", format!("CALLVOTE {}", &vote.to_protocol().join(" "))]
-            }
-            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)],
-            _ => 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
-            ],
-            Redirect(port) => msg!["REDIRECT", port],
-            Bye(msg) => msg!["BYE", msg],
-            Nick(nick) => msg!["NICK", nick],
-            Proto(proto) => msg!["PROTO", proto],
-            AskPassword(salt) => msg!["ASKPASSWORD", salt],
-            ServerAuth(hash) => msg!["SERVER_AUTH", hash],
-            LogonPassed => msg!["LOGONPASSED"],
-            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),
-            Joining(name) => msg!["JOINING", name],
-            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],
-            Info(info) => construct_message(&["INFO"], &info),
-            ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
-            ServerVars(vars) => construct_message(&["SERVER_VARS"], &vars),
-            Notice(msg) => msg!["NOTICE", msg],
-            Warning(msg) => msg!["WARNING", msg],
-            Error(msg) => msg!["ERROR", msg],
-            ReplayStart => msg!["REPLAY_START"],
-
-            LegacyReady(is_ready, nicks) => {
-                construct_message(&[if *is_ready { "READY" } else { "NOT_READY" }], &nicks)
-            }
-
-            _ => msg!["ERROR", "UNIMPLEMENTED"],
-        }
-    }
-}
--- a/rust/hedgewars-server/src/protocol/parser.rs	Wed Jun 23 15:32:48 2021 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,576 +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::{
-    branch::alt,
-    bytes::complete::{tag, tag_no_case, take_until, take_while},
-    character::complete::{newline, not_line_ending},
-    combinator::{map, peek},
-    error::{ErrorKind, ParseError},
-    multi::separated_list0,
-    sequence::{delimited, pair, preceded, terminated, tuple},
-    Err, IResult,
-};
-
-use std::{
-    num::ParseIntError,
-    ops::Range,
-    str,
-    str::{FromStr, Utf8Error},
-};
-
-use super::messages::{HwProtocolMessage, HwProtocolMessage::*};
-use crate::core::types::{
-    GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType, MAX_HEDGEHOGS_PER_TEAM,
-};
-
-#[derive(Debug, PartialEq)]
-pub struct HwProtocolError {}
-
-impl HwProtocolError {
-    fn new() -> Self {
-        HwProtocolError {}
-    }
-}
-
-impl<I> ParseError<I> for HwProtocolError {
-    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
-        HwProtocolError::new()
-    }
-
-    fn append(input: I, kind: ErrorKind, other: Self) -> Self {
-        HwProtocolError::new()
-    }
-}
-
-impl From<Utf8Error> for HwProtocolError {
-    fn from(_: Utf8Error) -> Self {
-        HwProtocolError::new()
-    }
-}
-
-impl From<ParseIntError> for HwProtocolError {
-    fn from(_: ParseIntError) -> Self {
-        HwProtocolError::new()
-    }
-}
-
-pub type HwResult<'a, O> = IResult<&'a [u8], O, HwProtocolError>;
-
-fn end_of_message(input: &[u8]) -> HwResult<&[u8]> {
-    tag("\n\n")(input)
-}
-
-fn convert_utf8(input: &[u8]) -> HwResult<&str> {
-    match str::from_utf8(input) {
-        Ok(str) => Ok((b"", str)),
-        Err(utf_err) => Result::Err(Err::Failure(utf_err.into())),
-    }
-}
-
-fn convert_from_str<T>(str: &str) -> HwResult<T>
-where
-    T: FromStr<Err = ParseIntError>,
-{
-    match T::from_str(str) {
-        Ok(x) => Ok((b"", x)),
-        Err(format_err) => Result::Err(Err::Failure(format_err.into())),
-    }
-}
-
-fn str_line(input: &[u8]) -> HwResult<&str> {
-    let (i, text) = not_line_ending(input.clone())?;
-    if i != input {
-        Ok((i, convert_utf8(text)?.1))
-    } else {
-        Err(Err::Error(HwProtocolError::new()))
-    }
-}
-
-fn a_line(input: &[u8]) -> HwResult<String> {
-    map(str_line, String::from)(input)
-}
-
-fn cmd_arg(input: &[u8]) -> HwResult<String> {
-    let delimiters = b" \n";
-    let (i, str) = take_while(move |c| !delimiters.contains(&c))(input.clone())?;
-    if i != input {
-        Ok((i, convert_utf8(str)?.1.to_string()))
-    } else {
-        Err(Err::Error(HwProtocolError::new()))
-    }
-}
-
-fn u8_line(input: &[u8]) -> HwResult<u8> {
-    let (i, str) = str_line(input)?;
-    Ok((i, convert_from_str(str)?.1))
-}
-
-fn u16_line(input: &[u8]) -> HwResult<u16> {
-    let (i, str) = str_line(input)?;
-    Ok((i, convert_from_str(str)?.1))
-}
-
-fn u32_line(input: &[u8]) -> HwResult<u32> {
-    let (i, str) = str_line(input)?;
-    Ok((i, convert_from_str(str)?.1))
-}
-
-fn yes_no_line(input: &[u8]) -> HwResult<bool> {
-    alt((
-        map(tag_no_case(b"YES"), |_| true),
-        map(tag_no_case(b"NO"), |_| false),
-    ))(input)
-}
-
-fn opt_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
-    alt((
-        map(peek(end_of_message), |_| None),
-        map(preceded(tag("\n"), a_line), Some),
-    ))(input)
-}
-
-fn spaces(input: &[u8]) -> HwResult<&[u8]> {
-    preceded(tag(" "), take_while(|c| c == b' '))(input)
-}
-
-fn opt_space_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
-    alt((
-        map(peek(end_of_message), |_| None),
-        map(preceded(spaces, a_line), Some),
-    ))(input)
-}
-
-fn hedgehog_array(input: &[u8]) -> HwResult<[HedgehogInfo; 8]> {
-    fn hedgehog_line(input: &[u8]) -> HwResult<HedgehogInfo> {
-        map(
-            tuple((terminated(a_line, newline), a_line)),
-            |(name, hat)| HedgehogInfo { name, hat },
-        )(input)
-    }
-
-    let (i, (h1, h2, h3, h4, h5, h6, h7, h8)) = tuple((
-        terminated(hedgehog_line, newline),
-        terminated(hedgehog_line, newline),
-        terminated(hedgehog_line, newline),
-        terminated(hedgehog_line, newline),
-        terminated(hedgehog_line, newline),
-        terminated(hedgehog_line, newline),
-        terminated(hedgehog_line, newline),
-        hedgehog_line,
-    ))(input)?;
-
-    Ok((i, [h1, h2, h3, h4, h5, h6, h7, h8]))
-}
-
-fn voting(input: &[u8]) -> HwResult<VoteType> {
-    alt((
-        map(tag_no_case("PAUSE"), |_| VoteType::Pause),
-        map(tag_no_case("NEWSEED"), |_| VoteType::NewSeed),
-        map(
-            preceded(pair(tag_no_case("KICK"), spaces), a_line),
-            VoteType::Kick,
-        ),
-        map(
-            preceded(pair(tag_no_case("HEDGEHOGS"), spaces), u8_line),
-            VoteType::HedgehogsPerTeam,
-        ),
-        map(preceded(tag_no_case("MAP"), opt_space_arg), VoteType::Map),
-    ))(input)
-}
-
-fn no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    fn message<'a>(
-        name: &'a str,
-        msg: HwProtocolMessage,
-    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
-        move |i| map(tag(name), |_| msg.clone())(i)
-    }
-
-    alt((
-        message("PING", Ping),
-        message("PONG", Pong),
-        message("LIST", List),
-        message("BANLIST", BanList),
-        message("GET_SERVER_VAR", GetServerVar),
-        message("TOGGLE_READY", ToggleReady),
-        message("START_GAME", StartGame),
-        message("TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
-        message("TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
-        message("TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
-    ))(input)
-}
-
-fn single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    fn message<'a, T, F, G>(
-        name: &'a str,
-        parser: F,
-        constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
-    where
-        F: Fn(&[u8]) -> HwResult<T>,
-        G: Fn(T) -> HwProtocolMessage,
-    {
-        map(preceded(tag(name), parser), constructor)
-    }
-
-    alt((
-        message("NICK\n", a_line, Nick),
-        message("INFO\n", a_line, Info),
-        message("CHAT\n", a_line, Chat),
-        message("PART", opt_arg, Part),
-        message("FOLLOW\n", a_line, Follow),
-        message("KICK\n", a_line, Kick),
-        message("UNBAN\n", a_line, Unban),
-        message("EM\n", a_line, EngineMessage),
-        message("TEAMCHAT\n", a_line, TeamChat),
-        message("ROOM_NAME\n", a_line, RoomName),
-        message("REMOVE_TEAM\n", a_line, RemoveTeam),
-        message("ROUNDFINISHED", opt_arg, |_| RoundFinished),
-        message("PROTO\n", u16_line, Proto),
-        message("QUIT", opt_arg, Quit),
-    ))(input)
-}
-
-fn cmd_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
-    fn cmd_no_arg<'a>(
-        name: &'a str,
-        msg: HwProtocolMessage,
-    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
-        move |i| map(tag_no_case(name), |_| msg.clone())(i)
-    }
-
-    fn cmd_single_arg<'a, T, F, G>(
-        name: &'a str,
-        parser: F,
-        constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
-    where
-        F: Fn(&'a [u8]) -> HwResult<'a, T>,
-        G: Fn(T) -> HwProtocolMessage,
-    {
-        map(
-            preceded(pair(tag_no_case(name), spaces), parser),
-            constructor,
-        )
-    }
-
-    fn cmd_no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-        alt((
-            cmd_no_arg("STATS", Stats),
-            cmd_no_arg("FIX", Fix),
-            cmd_no_arg("UNFIX", Unfix),
-            cmd_no_arg("REGISTERED_ONLY", ToggleServerRegisteredOnly),
-            cmd_no_arg("SUPER_POWER", SuperPower),
-        ))(input)
-    }
-
-    fn cmd_single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-        alt((
-            cmd_single_arg("RESTART_SERVER", |i| tag("YES")(i), |_| RestartServer),
-            cmd_single_arg("DELEGATE", a_line, Delegate),
-            cmd_single_arg("DELETE", a_line, Delete),
-            cmd_single_arg("SAVEROOM", a_line, SaveRoom),
-            cmd_single_arg("LOADROOM", a_line, LoadRoom),
-            cmd_single_arg("GLOBAL", a_line, Global),
-            cmd_single_arg("WATCH", u32_line, Watch),
-            cmd_single_arg("VOTE", yes_no_line, Vote),
-            cmd_single_arg("FORCE", yes_no_line, ForceVote),
-            cmd_single_arg("INFO", a_line, Info),
-            cmd_single_arg("MAXTEAMS", u8_line, MaxTeams),
-            cmd_single_arg("CALLVOTE", voting, |v| CallVote(Some(v))),
-        ))(input)
-    }
-
-    preceded(
-        tag("CMD\n"),
-        alt((
-            cmd_no_arg_message,
-            cmd_single_arg_message,
-            map(tag_no_case("CALLVOTE"), |_| CallVote(None)),
-            map(preceded(tag_no_case("GREETING"), opt_space_arg), Greeting),
-            map(preceded(tag_no_case("PART"), opt_space_arg), Part),
-            map(preceded(tag_no_case("QUIT"), opt_space_arg), Quit),
-            map(
-                preceded(
-                    tag_no_case("SAVE"),
-                    pair(preceded(spaces, cmd_arg), preceded(spaces, cmd_arg)),
-                ),
-                |(n, l)| Save(n, l),
-            ),
-            map(
-                preceded(
-                    tag_no_case("RND"),
-                    alt((
-                        map(peek(end_of_message), |_| vec![]),
-                        preceded(spaces, separated_list0(spaces, cmd_arg)),
-                    )),
-                ),
-                Rnd,
-            ),
-        )),
-    )(input)
-}
-
-fn config_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
-    fn cfg_single_arg<'a, T, F, G>(
-        name: &'a str,
-        parser: F,
-        constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, GameCfg>
-    where
-        F: Fn(&[u8]) -> HwResult<T>,
-        G: Fn(T) -> GameCfg,
-    {
-        map(preceded(pair(tag(name), newline), parser), constructor)
-    }
-
-    let (i, cfg) = preceded(
-        tag("CFG\n"),
-        alt((
-            cfg_single_arg("THEME", a_line, GameCfg::Theme),
-            cfg_single_arg("SCRIPT", a_line, GameCfg::Script),
-            cfg_single_arg("MAP", a_line, GameCfg::MapType),
-            cfg_single_arg("MAPGEN", u32_line, GameCfg::MapGenerator),
-            cfg_single_arg("MAZE_SIZE", u32_line, GameCfg::MazeSize),
-            cfg_single_arg("TEMPLATE", u32_line, GameCfg::Template),
-            cfg_single_arg("FEATURE_SIZE", u32_line, GameCfg::FeatureSize),
-            cfg_single_arg("SEED", a_line, GameCfg::Seed),
-            cfg_single_arg("DRAWNMAP", a_line, GameCfg::DrawnMap),
-            preceded(pair(tag("AMMO"), newline), |i| {
-                let (i, name) = a_line(i)?;
-                let (i, value) = opt_arg(i)?;
-                Ok((i, GameCfg::Ammo(name, value)))
-            }),
-            preceded(
-                pair(tag("SCHEME"), newline),
-                map(
-                    pair(
-                        a_line,
-                        alt((
-                            map(peek(end_of_message), |_| None),
-                            map(preceded(newline, separated_list0(newline, a_line)), Some),
-                        )),
-                    ),
-                    |(name, values)| GameCfg::Scheme(name, values.unwrap_or_default()),
-                ),
-            ),
-        )),
-    )(input)?;
-    Ok((i, Cfg(cfg)))
-}
-
-fn server_var_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    map(
-        preceded(
-            tag("SET_SERVER_VAR\n"),
-            alt((
-                map(preceded(tag("MOTD_NEW\n"), a_line), ServerVar::MOTDNew),
-                map(preceded(tag("MOTD_OLD\n"), a_line), ServerVar::MOTDOld),
-                map(
-                    preceded(tag("LATEST_PROTO\n"), u16_line),
-                    ServerVar::LatestProto,
-                ),
-            )),
-        ),
-        SetServerVar,
-    )(input)
-}
-
-fn complex_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    alt((
-        preceded(
-            pair(tag("PASSWORD"), newline),
-            map(pair(terminated(a_line, newline), a_line), |(pass, salt)| {
-                Password(pass, salt)
-            }),
-        ),
-        preceded(
-            pair(tag("CHECKER"), newline),
-            map(
-                tuple((
-                    terminated(u16_line, newline),
-                    terminated(a_line, newline),
-                    a_line,
-                )),
-                |(protocol, name, pass)| Checker(protocol, name, pass),
-            ),
-        ),
-        preceded(
-            pair(tag("CREATE_ROOM"), newline),
-            map(pair(a_line, opt_arg), |(name, pass)| CreateRoom(name, pass)),
-        ),
-        preceded(
-            pair(tag("JOIN_ROOM"), newline),
-            map(pair(a_line, opt_arg), |(name, pass)| JoinRoom(name, pass)),
-        ),
-        preceded(
-            pair(tag("ADD_TEAM"), newline),
-            map(
-                tuple((
-                    terminated(a_line, newline),
-                    terminated(u8_line, newline),
-                    terminated(a_line, newline),
-                    terminated(a_line, newline),
-                    terminated(a_line, newline),
-                    terminated(a_line, newline),
-                    terminated(u8_line, newline),
-                    hedgehog_array,
-                )),
-                |(name, color, grave, fort, voice_pack, flag, difficulty, hedgehogs)| {
-                    AddTeam(Box::new(TeamInfo {
-                        owner: String::new(),
-                        name,
-                        color,
-                        grave,
-                        fort,
-                        voice_pack,
-                        flag,
-                        difficulty,
-                        hedgehogs,
-                        hedgehogs_number: 0,
-                    }))
-                },
-            ),
-        ),
-        preceded(
-            pair(tag("HH_NUM"), newline),
-            map(
-                pair(terminated(a_line, newline), u8_line),
-                |(name, count)| SetHedgehogsNumber(name, count),
-            ),
-        ),
-        preceded(
-            pair(tag("TEAM_COLOR"), newline),
-            map(
-                pair(terminated(a_line, newline), u8_line),
-                |(name, color)| SetTeamColor(name, color),
-            ),
-        ),
-        preceded(
-            pair(tag("BAN"), newline),
-            map(
-                tuple((
-                    terminated(a_line, newline),
-                    terminated(a_line, newline),
-                    u32_line,
-                )),
-                |(name, reason, time)| Ban(name, reason, time),
-            ),
-        ),
-        preceded(
-            pair(tag("BAN_IP"), newline),
-            map(
-                tuple((
-                    terminated(a_line, newline),
-                    terminated(a_line, newline),
-                    u32_line,
-                )),
-                |(ip, reason, time)| BanIp(ip, reason, time),
-            ),
-        ),
-        preceded(
-            pair(tag("BAN_NICK"), newline),
-            map(
-                tuple((
-                    terminated(a_line, newline),
-                    terminated(a_line, newline),
-                    u32_line,
-                )),
-                |(nick, reason, time)| BanNick(nick, reason, time),
-            ),
-        ),
-    ))(input)
-}
-
-pub fn malformed_message(input: &[u8]) -> HwResult<()> {
-    map(terminated(take_until(&b"\n\n"[..]), end_of_message), |_| ())(input)
-}
-
-pub fn message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    delimited(
-        take_while(|c| c == b'\n'),
-        alt((
-            no_arg_message,
-            single_arg_message,
-            cmd_message,
-            config_message,
-            server_var_message,
-            complex_message,
-        )),
-        end_of_message,
-    )(input)
-}
-
-#[cfg(test)]
-mod test {
-    use super::message;
-    use crate::{
-        core::types::GameCfg,
-        protocol::{messages::HwProtocolMessage::*, parser::HwProtocolError, test::gen_proto_msg},
-    };
-    use proptest::{proptest, proptest_helper};
-
-    #[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 49471\n\n"),
-            Ok((&b""[..], Watch(49471)))
-        );
-        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!(
-            message(b"CFG\nSCHEME\na\nA\n\n"),
-            Ok((
-                &b""[..],
-                Cfg(GameCfg::Scheme("a".to_string(), vec!["A".to_string()]))
-            ))
-        );
-
-        assert_eq!(
-            message(b"QUIT\n1\n2\n\n"),
-            Err(nom::Err::Error(HwProtocolError::new()))
-        );
-    }
-}
--- a/rust/hedgewars-server/src/protocol/test.rs	Wed Jun 23 15:32:48 2021 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-use proptest::{
-    arbitrary::{any, any_with, Arbitrary, StrategyFor},
-    strategy::{BoxedStrategy, Just, Map, Strategy},
-    test_runner::{Reason, TestRunner},
-};
-
-use crate::core::types::{GameCfg, HedgehogInfo, ServerVar, ServerVar::*, TeamInfo, VoteType};
-
-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::core::types::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 {
-                    owner: String::new(),
-                    name,
-                    color,
-                    grave,
-                    fort,
-                    voice_pack,
-                    flag,
-                    difficulty,
-                    hedgehogs,
-                    hedgehogs_number: 0,
-                }
-            })
-            .boxed()
-    }
-
-    type Strategy = BoxedStrategy<TeamInfo>;
-}
-
-impl Arbitrary for ServerVar {
-    type Parameters = ();
-
-    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
-        (0..=2)
-            .no_shrink()
-            .prop_flat_map(|i| {
-                proto_msg_match!(i, def = ServerVar::LatestProto(0),
-                    0 => MOTDNew(Ascii),
-                    1 => MOTDOld(Ascii),
-                    2 => LatestProto(u16)
-                )
-            })
-            .boxed()
-    }
-
-    type Strategy = BoxedStrategy<ServerVar>;
-}
-
-impl Arbitrary for VoteType {
-    type Parameters = ();
-
-    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
-        use VoteType::*;
-        (0..=4)
-            .no_shrink()
-            .prop_flat_map(|i| {
-                proto_msg_match!(i, def = VoteType::Pause,
-                    0 => Kick(Ascii),
-                    1 => Map(Option<Ascii>),
-                    2 => Pause(),
-                    3 => NewSeed(),
-                    4 => HedgehogsPerTeam(u8)
-                )
-            })
-            .boxed()
-    }
-
-    type Strategy = BoxedStrategy<VoteType>;
-}
-
-pub fn gen_proto_msg() -> BoxedStrategy<HwProtocolMessage> where {
-    let res = (0..=55).no_shrink().prop_flat_map(|i| {
-        proto_msg_match!(i, def = Ping,
-            0 => Ping(),
-            1 => Pong(),
-            2 => Quit(Option<Ascii>),
-            4 => Global(Ascii),
-            5 => Watch(u32),
-            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(Option<Ascii>),
-            49 => CallVote(Option<VoteType>),
-            50 => Vote(bool),
-            51 => ForceVote(bool),
-            52 => Save(Ascii, Ascii),
-            53 => Delete(Ascii),
-            54 => SaveRoom(Ascii),
-            55 => LoadRoom(Ascii)
-        )
-    });
-    res.boxed()
-}
--- a/rust/hedgewars-server/src/server/demo.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/server/demo.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -1,6 +1,6 @@
-use crate::{
-    core::types::{Ammo, GameCfg, HedgehogInfo, Replay, RoomConfig, Scheme, TeamInfo},
-    server::haskell::HaskellValue,
+use crate::{core::types::Replay, server::haskell::HaskellValue};
+use hedgewars_network_protocol::types::{
+    Ammo, GameCfg, HedgehogInfo, RoomConfig, Scheme, TeamInfo,
 };
 use std::{
     collections::HashMap,
--- a/rust/hedgewars-server/src/server/network.rs	Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/server/network.rs	Wed Jun 23 23:41:51 2021 +0200
@@ -27,9 +27,10 @@
     },
     handlers,
     handlers::{IoResult, IoTask, ServerState},
-    protocol::{messages::HwServerMessage::Redirect, messages::*, ProtocolDecoder},
+    protocol::ProtocolDecoder,
     utils,
 };
+use hedgewars_network_protocol::{messages::HwServerMessage::Redirect, messages::*};
 
 #[cfg(feature = "official-server")]
 use super::io::{IoThread, RequestId};