rust/hedgewars-server/src/protocol/parser.rs
changeset 14775 09d46ab83361
parent 14457 98ef2913ec73
child 14777 8015a6e4ca3c
equal deleted inserted replaced
14774:d0ad9fd923fd 14775:09d46ab83361
     5  *
     5  *
     6  * For example, a nullary command like PING will be actually sent as `PING\n\n`.
     6  * For example, a nullary command like PING will be actually sent as `PING\n\n`.
     7  * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
     7  * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
     8  */
     8  */
     9 use nom::*;
     9 use nom::*;
       
    10 use std::{
       
    11     num::ParseIntError,
       
    12     ops::Range,
       
    13     str,
       
    14     str::{FromStr, Utf8Error},
       
    15 };
    10 
    16 
    11 use super::messages::{HWProtocolMessage, HWProtocolMessage::*};
    17 use super::messages::{HWProtocolMessage, HWProtocolMessage::*};
    12 use crate::server::coretypes::{GameCfg, HedgehogInfo, TeamInfo, VoteType, MAX_HEDGEHOGS_PER_TEAM};
    18 use crate::server::coretypes::{GameCfg, HedgehogInfo, TeamInfo, VoteType, MAX_HEDGEHOGS_PER_TEAM};
    13 use std::{ops::Range, str, str::FromStr};
    19 
    14 #[cfg(test)]
    20 #[cfg(test)]
    15 use {
    21 use {
    16     super::test::gen_proto_msg,
    22     super::test::gen_proto_msg,
    17     proptest::{proptest, proptest_helper},
    23     proptest::{proptest, proptest_helper},
    18 };
    24 };
    19 
    25 
    20 named!(end_of_message, tag!("\n\n"));
    26 #[derive(Debug, PartialEq)]
    21 named!(str_line<&[u8],   &str>, map_res!(not_line_ending, str::from_utf8));
    27 pub struct HWProtocolError {}
    22 named!(  a_line<&[u8], String>, map!(str_line, String::from));
    28 
    23 named!(cmd_arg<&[u8], String>,
    29 impl HWProtocolError {
    24     map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from));
    30     fn new() -> Self {
    25 named!( u8_line<&[u8],     u8>, map_res!(str_line, FromStr::from_str));
    31         HWProtocolError {}
    26 named!(u16_line<&[u8],    u16>, map_res!(str_line, FromStr::from_str));
    32     }
    27 named!(u32_line<&[u8],    u32>, map_res!(str_line, FromStr::from_str));
    33 }
    28 named!(yes_no_line<&[u8], bool>, alt!(
    34 
    29       do_parse!(tag_no_case!("YES") >> (true))
    35 impl<I> ParseError<I> for HWProtocolError {
    30     | do_parse!(tag_no_case!("NO") >> (false))));
    36     fn from_error_kind(input: I, kind: ErrorKind) -> Self {
    31 named!(opt_param<&[u8], Option<String> >, alt!(
    37         HWProtocolError::new()
    32       do_parse!(peek!(tag!("\n\n")) >> (None))
    38     }
    33     | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string())))));
    39 
    34 named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" ")));
    40     fn append(input: I, kind: ErrorKind, other: Self) -> Self {
    35 named!(opt_space_param<&[u8], Option<String> >, alt!(
    41         HWProtocolError::new()
    36       do_parse!(peek!(tag!("\n\n")) >> (None))
    42     }
    37     | do_parse!(spaces >> s: str_line >> (Some(s.to_string())))));
    43 }
    38 named!(hog_line<&[u8], HedgehogInfo>,
    44 
    39     do_parse!(name: str_line >> eol >> hat: str_line >>
    45 impl From<Utf8Error> for HWProtocolError {
    40         (HedgehogInfo{name: name.to_string(), hat: hat.to_string()})));
    46     fn from(_: Utf8Error) -> Self {
    41 named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>,
    47         HWProtocolError::new()
    42     do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >>
    48     }
    43               h3: hog_line >> eol >> h4: hog_line >> eol >>
    49 }
    44               h5: hog_line >> eol >> h6: hog_line >> eol >>
    50 
    45               h7: hog_line >> eol >> h8: hog_line >>
    51 impl From<ParseIntError> for HWProtocolError {
    46               ([h1, h2, h3, h4, h5, h6, h7, h8])));
    52     fn from(_: ParseIntError) -> Self {
    47 named!(voting<&[u8], VoteType>, alt!(
    53         HWProtocolError::new()
    48       do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >>
    54     }
    49         (VoteType::Kick(n)))
    55 }
    50     | do_parse!(tag_no_case!("MAP") >>
    56 
    51         n: opt!(preceded!(spaces, a_line)) >>
    57 pub type HWResult<'a, O> = IResult<&'a [u8], O, HWProtocolError>;
    52         (VoteType::Map(n)))
    58 
    53     | do_parse!(tag_no_case!("PAUSE") >>
    59 fn end_of_message(input: &[u8]) -> HWResult<&[u8]> {
    54         (VoteType::Pause))
    60     tag("\n\n")(input)
    55     | do_parse!(tag_no_case!("NEWSEED") >>
    61 }
    56         (VoteType::NewSeed))
    62 
    57     | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >>
    63 fn convert_utf8(input: &[u8]) -> HWResult<&str> {
    58         (VoteType::HedgehogsPerTeam(n)))));
    64     match str::from_utf8(input) {
    59 
    65         Ok(str) => Ok((b"", str)),
    60 /** Recognizes messages which do not take any parameters */
    66         Err(utf_err) => Result::Err(Err::Failure(utf_err.into())),
    61 named!(basic_message<&[u8], HWProtocolMessage>, alt!(
    67     }
    62       do_parse!(tag!("PING") >> (Ping))
    68 }
    63     | do_parse!(tag!("PONG") >> (Pong))
    69 
    64     | do_parse!(tag!("LIST") >> (List))
    70 fn convert_from_str<T>(str: &str) -> HWResult<T>
    65     | do_parse!(tag!("BANLIST")        >> (BanList))
    71 where
    66     | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
    72     T: FromStr<Err = ParseIntError>,
    67     | do_parse!(tag!("TOGGLE_READY")   >> (ToggleReady))
    73 {
    68     | do_parse!(tag!("START_GAME")     >> (StartGame))
    74     match T::from_str(str) {
    69     | do_parse!(tag!("ROUNDFINISHED")  >> _m: opt_param >> (RoundFinished))
    75         Ok(x) => Ok((b"", x)),
    70     | do_parse!(tag!("TOGGLE_RESTRICT_JOINS")  >> (ToggleRestrictJoin))
    76         Err(format_err) => Result::Err(Err::Failure(format_err.into())),
    71     | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS")  >> (ToggleRestrictTeams))
    77     }
    72     | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
    78 }
    73 ));
    79 
    74 
    80 fn str_line(input: &[u8]) -> HWResult<&str> {
    75 /** Recognizes messages which take exactly one parameter */
    81     let (i, text) = not_line_ending(input)?;
    76 named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
    82     Ok((i, convert_utf8(text)?.1))
    77       do_parse!(tag!("NICK")    >> eol >> n: a_line >> (Nick(n)))
    83 }
    78     | do_parse!(tag!("INFO")    >> eol >> n: a_line >> (Info(n)))
    84 
    79     | do_parse!(tag!("CHAT")    >> eol >> m: a_line >> (Chat(m)))
    85 fn a_line(input: &[u8]) -> HWResult<String> {
    80     | do_parse!(tag!("PART")    >> msg: opt_param   >> (Part(msg)))
    86     let (i, str) = str_line(input)?;
    81     | do_parse!(tag!("FOLLOW")  >> eol >> n: a_line >> (Follow(n)))
    87     Ok((i, str.to_string()))
    82     | do_parse!(tag!("KICK")    >> eol >> n: a_line >> (Kick(n)))
    88 }
    83     | do_parse!(tag!("UNBAN")   >> eol >> n: a_line >> (Unban(n)))
    89 
    84     | do_parse!(tag!("EM")      >> eol >> m: a_line >> (EngineMessage(m)))
    90 fn hw_tag<'a>(tag_str: &'a str) -> impl Fn(&'a [u8]) -> HWResult<'a, ()> {
    85     | do_parse!(tag!("TEAMCHAT")    >> eol >> m: a_line >> (TeamChat(m)))
    91     move |i| tag(tag_str)(i).map(|(i, _)| (i, ()))
    86     | do_parse!(tag!("ROOM_NAME")   >> eol >> n: a_line >> (RoomName(n)))
    92 }
    87     | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
    93 
    88 
    94 fn hw_tag_no_case<'a>(tag_str: &'a str) -> impl Fn(&'a [u8]) -> HWResult<'a, ()> {
    89     | do_parse!(tag!("PROTO")   >> eol >> d: u16_line >> (Proto(d)))
    95     move |i| tag_no_case(tag_str)(i).map(|(i, _)| (i, ()))
    90 
    96 }
    91     | do_parse!(tag!("QUIT")   >> msg: opt_param >> (Quit(msg)))
    97 
    92 ));
    98 fn cmd_arg(input: &[u8]) -> HWResult<String> {
    93 
    99     let delimiters = b" \n";
    94 /** Recognizes messages preceded with CMD */
   100     let (i, str) = take_while(move |c| !delimiters.contains(&c))(input)?;
    95 named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
   101     Ok((i, convert_utf8(str)?.1.to_string()))
    96       do_parse!(tag_no_case!("STATS") >> (Stats))
   102 }
    97     | do_parse!(tag_no_case!("FIX")   >> (Fix))
   103 
    98     | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
   104 fn u8_line(input: &[u8]) -> HWResult<u8> {
    99     | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer))
   105     let (i, str) = str_line(input)?;
   100     | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
   106     Ok((i, convert_from_str(str)?.1))
   101     | do_parse!(tag_no_case!("SUPER_POWER")     >> (SuperPower))
   107 }
   102     | do_parse!(tag_no_case!("PART")     >> m: opt_space_param >> (Part(m)))
   108 
   103     | do_parse!(tag_no_case!("QUIT")     >> m: opt_space_param >> (Quit(m)))
   109 fn u16_line(input: &[u8]) -> HWResult<u16> {
   104     | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line  >> (Delegate(n)))
   110     let (i, str) = str_line(input)?;
   105     | do_parse!(tag_no_case!("SAVE")     >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l)))
   111     Ok((i, convert_from_str(str)?.1))
   106     | do_parse!(tag_no_case!("DELETE")   >> spaces >> n: a_line  >> (Delete(n)))
   112 }
   107     | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line  >> (SaveRoom(r)))
   113 
   108     | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line  >> (LoadRoom(r)))
   114 fn u32_line(input: &[u8]) -> HWResult<u32> {
   109     | do_parse!(tag_no_case!("GLOBAL")   >> spaces >> m: a_line  >> (Global(m)))
   115     let (i, str) = str_line(input)?;
   110     | do_parse!(tag_no_case!("WATCH")    >> spaces >> i: a_line  >> (Watch(i)))
   116     Ok((i, convert_from_str(str)?.1))
   111     | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line  >> (Greeting(m)))
   117 }
   112     | do_parse!(tag_no_case!("VOTE")     >> spaces >> m: yes_no_line >> (Vote(m)))
   118 
   113     | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: yes_no_line >> (ForceVote(m)))
   119 fn yes_no_line(input: &[u8]) -> HWResult<bool> {
   114     | do_parse!(tag_no_case!("INFO")     >> spaces >> n: a_line  >> (Info(n)))
   120     alt((
   115     | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n)))
   121         |i| tag_no_case(b"YES")(i).map(|(i, _)| (i, true)),
   116     | do_parse!(tag_no_case!("CALLVOTE") >>
   122         |i| tag_no_case(b"NO")(i).map(|(i, _)| (i, false)),
   117         v: opt!(preceded!(spaces, voting)) >> (CallVote(v)))
   123     ))(input)
   118     | do_parse!(
   124 }
   119         tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >>
   125 
   120         v: str_line >>
   126 fn opt_arg<'a>(input: &'a [u8]) -> HWResult<'a, Option<String>> {
   121         (Rnd(v.split_whitespace().map(String::from).collect())))
   127     alt((
   122 )));
   128         |i: &'a [u8]| peek!(i, end_of_message).map(|(i, _)| (i, None)),
   123 
   129         |i| precededc(i, hw_tag("\n"), a_line).map(|(i, v)| (i, Some(v))),
   124 named!(complex_message<&[u8], HWProtocolMessage>, alt!(
   130     ))(input)
   125       do_parse!(tag!("PASSWORD")  >> eol >>
   131 }
   126                     p: a_line     >> eol >>
   132 
   127                     s: a_line     >>
   133 fn spaces(input: &[u8]) -> HWResult<&[u8]> {
   128                     (Password(p, s)))
   134     precededc(input, hw_tag(" "), |i| take_while(|c| c == b' ')(i))
   129     | do_parse!(tag!("CHECKER")   >> eol >>
   135 }
   130                     i: u16_line   >> eol >>
   136 
   131                     n: a_line     >> eol >>
   137 fn opt_space_arg<'a>(input: &'a [u8]) -> HWResult<'a, Option<String>> {
   132                     p: a_line     >>
   138     alt((
   133                     (Checker(i, n, p)))
   139         |i: &'a [u8]| peek!(i, end_of_message).map(|(i, _)| (i, None)),
   134     | do_parse!(tag!("CREATE_ROOM") >> eol >>
   140         |i| precededc(i, spaces, a_line).map(|(i, v)| (i, Some(v))),
   135                     n: a_line       >>
   141     ))(input)
   136                     p: opt_param    >>
   142 }
   137                     (CreateRoom(n, p)))
   143 
   138     | do_parse!(tag!("JOIN_ROOM")   >> eol >>
   144 fn hedgehog_array(input: &[u8]) -> HWResult<[HedgehogInfo; 8]> {
   139                     n: a_line       >>
   145     fn hedgehog_line(input: &[u8]) -> HWResult<HedgehogInfo> {
   140                     p: opt_param    >>
   146         let (i, name) = terminatedc(input, a_line, eol)?;
   141                     (JoinRoom(n, p)))
   147         let (i, hat) = a_line(i)?;
   142     | do_parse!(tag!("ADD_TEAM")    >> eol >>
   148         Ok((i, HedgehogInfo { name, hat }))
   143                     name: a_line    >> eol >>
   149     }
   144                     color: u8_line  >> eol >>
   150 
   145                     grave: a_line   >> eol >>
   151     let (i, h1) = terminatedc(input, hedgehog_line, eol)?;
   146                     fort: a_line    >> eol >>
   152     let (i, h2) = terminatedc(i, hedgehog_line, eol)?;
   147                     voice_pack: a_line >> eol >>
   153     let (i, h3) = terminatedc(i, hedgehog_line, eol)?;
   148                     flag: a_line    >> eol >>
   154     let (i, h4) = terminatedc(i, hedgehog_line, eol)?;
   149                     difficulty: u8_line >> eol >>
   155     let (i, h5) = terminatedc(i, hedgehog_line, eol)?;
   150                     hedgehogs: _8_hogs >>
   156     let (i, h6) = terminatedc(i, hedgehog_line, eol)?;
   151                     (AddTeam(Box::new(TeamInfo{
   157     let (i, h7) = terminatedc(i, hedgehog_line, eol)?;
   152                         name, color, grave, fort,
   158     let (i, h8) = hedgehog_line(i)?;
   153                         voice_pack, flag, difficulty,
   159 
   154                         hedgehogs, hedgehogs_number: 0
   160     Ok((i, [h1, h2, h3, h4, h5, h6, h7, h8]))
   155                      }))))
   161 }
   156     | do_parse!(tag!("HH_NUM")    >> eol >>
   162 
   157                     n: a_line     >> eol >>
   163 fn voting(input: &[u8]) -> HWResult<VoteType> {
   158                     c: u8_line    >>
   164     alt((
   159                     (SetHedgehogsNumber(n, c)))
   165         |i| tag_no_case("PAUSE")(i).map(|(i, _)| (i, VoteType::Pause)),
   160     | do_parse!(tag!("TEAM_COLOR")    >> eol >>
   166         |i| tag_no_case("NEWSEED")(i).map(|(i, _)| (i, VoteType::NewSeed)),
   161                     n: a_line     >> eol >>
   167         |i| {
   162                     c: u8_line    >>
   168             precededc(i, |i| precededc(i, hw_tag_no_case("KICK"), spaces), a_line)
   163                     (SetTeamColor(n, c)))
   169                 .map(|(i, s)| (i, VoteType::Kick(s)))
   164     | do_parse!(tag!("BAN")    >> eol >>
   170         },
   165                     n: a_line     >> eol >>
   171         |i| {
   166                     r: a_line     >> eol >>
   172             precededc(
   167                     t: u32_line   >>
   173                 i,
   168                     (Ban(n, r, t)))
   174                 |i| precededc(i, hw_tag_no_case("HEDGEHOGS"), spaces),
   169     | do_parse!(tag!("BAN_IP")    >> eol >>
   175                 u8_line,
   170                     n: a_line     >> eol >>
   176             )
   171                     r: a_line     >> eol >>
   177             .map(|(i, n)| (i, VoteType::HedgehogsPerTeam(n)))
   172                     t: u32_line   >>
   178         },
   173                     (BanIP(n, r, t)))
   179         |i| precededc(i, hw_tag_no_case("MAP"), opt_space_arg).map(|(i, v)| (i, VoteType::Map(v))),
   174     | do_parse!(tag!("BAN_NICK")    >> eol >>
   180     ))(input)
   175                     n: a_line     >> eol >>
   181 }
   176                     r: a_line     >> eol >>
   182 
   177                     t: u32_line   >>
   183 fn no_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
   178                     (BanNick(n, r, t)))
   184     fn messagec<'a>(
   179 ));
   185         input: &'a [u8],
   180 
   186         name: &'a str,
   181 named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!(
   187         msg: HWProtocolMessage,
   182       do_parse!(tag!("THEME")    >> eol >>
   188     ) -> HWResult<'a, HWProtocolMessage> {
   183                 name: a_line     >>
   189         tag(name)(input).map(|(i, _)| (i, msg.clone()))
   184                 (GameCfg::Theme(name)))
   190     }
   185     | do_parse!(tag!("SCRIPT")   >> eol >>
   191 
   186                 name: a_line     >>
   192     alt((
   187                 (GameCfg::Script(name)))
   193         |i| messagec(i, "PING", Ping),
   188     | do_parse!(tag!("AMMO")     >> eol >>
   194         |i| messagec(i, "PONG", Pong),
   189                 name: a_line     >>
   195         |i| messagec(i, "LIST", List),
   190                 value: opt_param >>
   196         |i| messagec(i, "BANLIST", BanList),
   191                 (GameCfg::Ammo(name, value)))
   197         |i| messagec(i, "GET_SERVER_VAR", GetServerVar),
   192     | do_parse!(tag!("SCHEME")   >> eol >>
   198         |i| messagec(i, "TOGGLE_READY", ToggleReady),
   193                 name: a_line     >>
   199         |i| messagec(i, "START_GAME", StartGame),
   194                 values: opt!(preceded!(eol, separated_list!(eol, a_line))) >>
   200         |i| messagec(i, "TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
   195                 (GameCfg::Scheme(name, values.unwrap_or_default())))
   201         |i| messagec(i, "TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
   196     | do_parse!(tag!("FEATURE_SIZE") >> eol >>
   202         |i| messagec(i, "TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
   197                 value: u32_line    >>
   203     ))(input)
   198                 (GameCfg::FeatureSize(value)))
   204 }
   199     | do_parse!(tag!("MAP")      >> eol >>
   205 
   200                 value: a_line    >>
   206 fn single_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
   201                 (GameCfg::MapType(value)))
   207     fn messagec<'a, T, F, G>(
   202     | do_parse!(tag!("MAPGEN")   >> eol >>
   208         input: &'a [u8],
   203                 value: u32_line  >>
   209         name: &'a str,
   204                 (GameCfg::MapGenerator(value)))
   210         parser: F,
   205     | do_parse!(tag!("MAZE_SIZE") >> eol >>
   211         constructor: G,
   206                 value: u32_line   >>
   212     ) -> HWResult<'a, HWProtocolMessage>
   207                 (GameCfg::MazeSize(value)))
   213     where
   208     | do_parse!(tag!("SEED")     >> eol >>
   214         F: Fn(&[u8]) -> HWResult<T>,
   209                 value: a_line    >>
   215         G: Fn(T) -> HWProtocolMessage,
   210                 (GameCfg::Seed(value)))
   216     {
   211     | do_parse!(tag!("TEMPLATE") >> eol >>
   217         precededc(input, hw_tag(name), parser).map(|(i, v)| (i, constructor(v)))
   212                 value: u32_line  >>
   218     }
   213                 (GameCfg::Template(value)))
   219 
   214     | do_parse!(tag!("DRAWNMAP") >> eol >>
   220     alt((
   215                 value: a_line    >>
   221         |i| messagec(i, "NICK\n", a_line, Nick),
   216                 (GameCfg::DrawnMap(value)))
   222         |i| messagec(i, "INFO\n", a_line, Info),
   217 ), Cfg)));
   223         |i| messagec(i, "CHAT\n", a_line, Chat),
   218 
   224         |i| messagec(i, "PART", opt_arg, Part),
   219 named!(malformed_message<&[u8], HWProtocolMessage>,
   225         |i| messagec(i, "FOLLOW\n", a_line, Follow),
   220     do_parse!(separated_list!(eol, a_line) >> (Malformed)));
   226         |i| messagec(i, "KICK\n", a_line, Kick),
   221 
   227         |i| messagec(i, "UNBAN\n", a_line, Unban),
   222 named!(empty_message<&[u8], HWProtocolMessage>,
   228         |i| messagec(i, "EM\n", a_line, EngineMessage),
   223     do_parse!(alt!(end_of_message | eol) >> (Empty)));
   229         |i| messagec(i, "TEAMCHAT\n", a_line, TeamChat),
   224 
   230         |i| messagec(i, "ROOM_NAME\n", a_line, RoomName),
   225 named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
   231         |i| messagec(i, "REMOVE_TEAM\n", a_line, RemoveTeam),
   226     alt!(
   232         |i| messagec(i, "ROUNDFINISHED", opt_arg, |_| RoundFinished),
   227           basic_message
   233         |i| messagec(i, "PROTO\n", u16_line, Proto),
   228         | one_param_message
   234         |i| messagec(i, "QUIT", opt_arg, Quit),
   229         | cmd_message
   235     ))(input)
   230         | complex_message
   236 }
   231         | cfg_message
   237 
   232         ), end_of_message
   238 fn cmd_message<'a>(input: &'a [u8]) -> HWResult<'a, HWProtocolMessage> {
       
   239     fn cmdc_no_arg<'a>(
       
   240         input: &'a [u8],
       
   241         name: &'a str,
       
   242         msg: HWProtocolMessage,
       
   243     ) -> HWResult<'a, HWProtocolMessage> {
       
   244         tag_no_case(name)(input).map(|(i, _)| (i, msg.clone()))
       
   245     }
       
   246 
       
   247     fn cmdc_single_arg<'a, T, F, G>(
       
   248         input: &'a [u8],
       
   249         name: &'a str,
       
   250         parser: F,
       
   251         constructor: G,
       
   252     ) -> HWResult<'a, HWProtocolMessage>
       
   253     where
       
   254         F: Fn(&'a [u8]) -> HWResult<'a, T>,
       
   255         G: Fn(T) -> HWProtocolMessage,
       
   256     {
       
   257         precededc(input, |i| pairc(i, hw_tag_no_case(name), spaces), parser)
       
   258             .map(|(i, v)| (i, constructor(v)))
       
   259     }
       
   260 
       
   261     fn cmd_no_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
       
   262         alt((
       
   263             |i| cmdc_no_arg(i, "STATS", Stats),
       
   264             |i| cmdc_no_arg(i, "FIX", Fix),
       
   265             |i| cmdc_no_arg(i, "UNFIX", Unfix),
       
   266             |i| cmdc_no_arg(i, "REGISTERED_ONLY", ToggleServerRegisteredOnly),
       
   267             |i| cmdc_no_arg(i, "SUPER_POWER", SuperPower),
       
   268         ))(input)
       
   269     }
       
   270 
       
   271     fn cmd_single_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
       
   272         alt((
       
   273             |i| cmdc_single_arg(i, "RESTART_SERVER", |i| tag("YES")(i), |_| RestartServer),
       
   274             |i| cmdc_single_arg(i, "DELEGATE", a_line, Delegate),
       
   275             |i| cmdc_single_arg(i, "DELETE", a_line, Delete),
       
   276             |i| cmdc_single_arg(i, "SAVEROOM", a_line, SaveRoom),
       
   277             |i| cmdc_single_arg(i, "LOADROOM", a_line, LoadRoom),
       
   278             |i| cmdc_single_arg(i, "GLOBAL", a_line, Global),
       
   279             |i| cmdc_single_arg(i, "WATCH", a_line, Watch),
       
   280             |i| cmdc_single_arg(i, "GREETING", a_line, Greeting),
       
   281             |i| cmdc_single_arg(i, "VOTE", yes_no_line, Vote),
       
   282             |i| cmdc_single_arg(i, "FORCE", yes_no_line, ForceVote),
       
   283             |i| cmdc_single_arg(i, "INFO", a_line, Info),
       
   284             |i| cmdc_single_arg(i, "MAXTEAMS", u8_line, MaxTeams),
       
   285             |i| cmdc_single_arg(i, "CALLVOTE", |i| opt!(i, voting), CallVote),
       
   286         ))(input)
       
   287     }
       
   288 
       
   289     precededc(
       
   290         input,
       
   291         hw_tag("CMD\n"),
       
   292         alt((
       
   293             cmd_no_arg_message,
       
   294             cmd_single_arg_message,
       
   295             |i| precededc(i, hw_tag_no_case("PART"), opt_space_arg).map(|(i, s)| (i, Part(s))),
       
   296             |i| precededc(i, hw_tag_no_case("QUIT"), opt_space_arg).map(|(i, s)| (i, Quit(s))),
       
   297             |i| {
       
   298                 precededc(i, hw_tag_no_case("SAVE"), |i| {
       
   299                     pairc(
       
   300                         i,
       
   301                         |i| precededc(i, spaces, cmd_arg),
       
   302                         |i| precededc(i, spaces, cmd_arg),
       
   303                     )
       
   304                 })
       
   305                 .map(|(i, (n, l))| (i, Save(n, l)))
       
   306             },
       
   307             |i| {
       
   308                 let (i, _) = tag_no_case("RND")(i)?;
       
   309                 let (i, _) = alt((spaces, |i: &'a [u8]| peek!(i, end_of_message)))(i)?;
       
   310                 let (i, v) = str_line(i)?;
       
   311                 Ok((i, Rnd(v.split_whitespace().map(String::from).collect())))
       
   312             },
       
   313         )),
   233     )
   314     )
   234     | terminated!(malformed_message, end_of_message)
   315 }
   235     | empty_message
   316 
   236     )
   317 fn config_message<'a>(input: &'a [u8]) -> HWResult<'a, HWProtocolMessage> {
   237 );
   318     fn cfgc_single_arg<'a, T, F, G>(
   238 
   319         input: &'a [u8],
   239 named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message)));
   320         name: &'a str,
       
   321         parser: F,
       
   322         constructor: G,
       
   323     ) -> HWResult<'a, GameCfg>
       
   324     where
       
   325         F: Fn(&[u8]) -> HWResult<T>,
       
   326         G: Fn(T) -> GameCfg,
       
   327     {
       
   328         precededc(input, |i| terminatedc(i, hw_tag(name), eol), parser)
       
   329             .map(|(i, v)| (i, constructor(v)))
       
   330     }
       
   331 
       
   332     let (i, cfg) = precededc(
       
   333         input,
       
   334         hw_tag("CFG\n"),
       
   335         alt((
       
   336             |i| cfgc_single_arg(i, "THEME", a_line, GameCfg::Theme),
       
   337             |i| cfgc_single_arg(i, "SCRIPT", a_line, GameCfg::Script),
       
   338             |i| cfgc_single_arg(i, "MAP", a_line, GameCfg::MapType),
       
   339             |i| cfgc_single_arg(i, "MAPGEN", u32_line, GameCfg::MapGenerator),
       
   340             |i| cfgc_single_arg(i, "MAZE_SIZE", u32_line, GameCfg::MazeSize),
       
   341             |i| cfgc_single_arg(i, "TEMPLATE", u32_line, GameCfg::Template),
       
   342             |i| cfgc_single_arg(i, "FEATURE_SIZE", u32_line, GameCfg::FeatureSize),
       
   343             |i| cfgc_single_arg(i, "SEED", a_line, GameCfg::Seed),
       
   344             |i| cfgc_single_arg(i, "DRAWNMAP", a_line, GameCfg::DrawnMap),
       
   345             |i| {
       
   346                 precededc(
       
   347                     i,
       
   348                     |i| terminatedc(i, hw_tag("AMMO"), eol),
       
   349                     |i| {
       
   350                         let (i, name) = a_line(i)?;
       
   351                         let (i, value) = opt_arg(i)?;
       
   352                         Ok((i, GameCfg::Ammo(name, value)))
       
   353                     },
       
   354                 )
       
   355             },
       
   356             |i| {
       
   357                 precededc(
       
   358                     i,
       
   359                     |i| terminatedc(i, hw_tag("SCHEME"), eol),
       
   360                     |i| {
       
   361                         let (i, name) = a_line(i)?;
       
   362                         let (i, values) = alt((
       
   363                             |i: &'a [u8]| peek!(i, end_of_message).map(|(i, _)| (i, None)),
       
   364                             |i| {
       
   365                                 precededc(i, eol, |i| separated_list(eol, a_line)(i))
       
   366                                     .map(|(i, v)| (i, Some(v)))
       
   367                             },
       
   368                         ))(i)?;
       
   369                         Ok((i, GameCfg::Scheme(name, values.unwrap_or_default())))
       
   370                     },
       
   371                 )
       
   372             },
       
   373         )),
       
   374     )?;
       
   375     Ok((i, Cfg(cfg)))
       
   376 }
       
   377 
       
   378 fn complex_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
       
   379     alt((
       
   380         |i| {
       
   381             precededc(
       
   382                 i,
       
   383                 |i| terminatedc(i, hw_tag("PASSWORD"), eol),
       
   384                 |i| {
       
   385                     let (i, pass) = terminatedc(i, a_line, eol)?;
       
   386                     let (i, salt) = a_line(i)?;
       
   387                     Ok((i, Password(pass, salt)))
       
   388                 },
       
   389             )
       
   390         },
       
   391         |i| {
       
   392             precededc(
       
   393                 i,
       
   394                 |i| terminatedc(i, hw_tag("CHECKER"), eol),
       
   395                 |i| {
       
   396                     let (i, protocol) = terminatedc(i, u16_line, eol)?;
       
   397                     let (i, name) = terminatedc(i, a_line, eol)?;
       
   398                     let (i, pass) = a_line(i)?;
       
   399                     Ok((i, Checker(protocol, name, pass)))
       
   400                 },
       
   401             )
       
   402         },
       
   403         |i| {
       
   404             precededc(
       
   405                 i,
       
   406                 |i| terminatedc(i, hw_tag("CREATE_ROOM"), eol),
       
   407                 |i| {
       
   408                     let (i, name) = a_line(i)?;
       
   409                     let (i, pass) = opt_arg(i)?;
       
   410                     Ok((i, CreateRoom(name, pass)))
       
   411                 },
       
   412             )
       
   413         },
       
   414         |i| {
       
   415             precededc(
       
   416                 i,
       
   417                 |i| terminatedc(i, hw_tag("JOIN_ROOM"), eol),
       
   418                 |i| {
       
   419                     let (i, name) = a_line(i)?;
       
   420                     let (i, pass) = opt_arg(i)?;
       
   421                     Ok((i, JoinRoom(name, pass)))
       
   422                 },
       
   423             )
       
   424         },
       
   425         |i| {
       
   426             precededc(
       
   427                 i,
       
   428                 |i| terminatedc(i, hw_tag("ADD_TEAM"), eol),
       
   429                 |i| {
       
   430                     let (i, name) = terminatedc(i, a_line, eol)?;
       
   431                     let (i, color) = terminatedc(i, u8_line, eol)?;
       
   432                     let (i, grave) = terminatedc(i, a_line, eol)?;
       
   433                     let (i, fort) = terminatedc(i, a_line, eol)?;
       
   434                     let (i, voice_pack) = terminatedc(i, a_line, eol)?;
       
   435                     let (i, flag) = terminatedc(i, a_line, eol)?;
       
   436                     let (i, difficulty) = terminatedc(i, u8_line, eol)?;
       
   437                     let (i, hedgehogs) = hedgehog_array(i)?;
       
   438                     Ok((
       
   439                         i,
       
   440                         AddTeam(Box::new(TeamInfo {
       
   441                             name,
       
   442                             color,
       
   443                             grave,
       
   444                             fort,
       
   445                             voice_pack,
       
   446                             flag,
       
   447                             difficulty,
       
   448                             hedgehogs,
       
   449                             hedgehogs_number: 0,
       
   450                         })),
       
   451                     ))
       
   452                 },
       
   453             )
       
   454         },
       
   455         |i| {
       
   456             precededc(
       
   457                 i,
       
   458                 |i| terminatedc(i, hw_tag("HH_NUM"), eol),
       
   459                 |i| {
       
   460                     let (i, name) = terminatedc(i, a_line, eol)?;
       
   461                     let (i, count) = u8_line(i)?;
       
   462                     Ok((i, SetHedgehogsNumber(name, count)))
       
   463                 },
       
   464             )
       
   465         },
       
   466         |i| {
       
   467             precededc(
       
   468                 i,
       
   469                 |i| terminatedc(i, hw_tag("TEAM_COLOR"), eol),
       
   470                 |i| {
       
   471                     let (i, name) = terminatedc(i, a_line, eol)?;
       
   472                     let (i, color) = u8_line(i)?;
       
   473                     Ok((i, SetTeamColor(name, color)))
       
   474                 },
       
   475             )
       
   476         },
       
   477         |i| {
       
   478             precededc(
       
   479                 i,
       
   480                 |i| terminatedc(i, hw_tag("BAN"), eol),
       
   481                 |i| {
       
   482                     let (i, n) = terminatedc(i, a_line, eol)?;
       
   483                     let (i, r) = terminatedc(i, a_line, eol)?;
       
   484                     let (i, t) = u32_line(i)?;
       
   485                     Ok((i, Ban(n, r, t)))
       
   486                 },
       
   487             )
       
   488         },
       
   489         |i| {
       
   490             precededc(
       
   491                 i,
       
   492                 |i| terminatedc(i, hw_tag("BAN_IP"), eol),
       
   493                 |i| {
       
   494                     let (i, n) = terminatedc(i, a_line, eol)?;
       
   495                     let (i, r) = terminatedc(i, a_line, eol)?;
       
   496                     let (i, t) = u32_line(i)?;
       
   497                     Ok((i, BanIP(n, r, t)))
       
   498                 },
       
   499             )
       
   500         },
       
   501         |i| {
       
   502             precededc(
       
   503                 i,
       
   504                 |i| terminatedc(i, hw_tag("BAN_NICK"), eol),
       
   505                 |i| {
       
   506                     let (i, n) = terminatedc(i, a_line, eol)?;
       
   507                     let (i, r) = terminatedc(i, a_line, eol)?;
       
   508                     let (i, t) = u32_line(i)?;
       
   509                     Ok((i, BanNick(n, r, t)))
       
   510                 },
       
   511             )
       
   512         },
       
   513     ))(input)
       
   514 }
       
   515 
       
   516 fn empty_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
       
   517     let (i, _) = alt((end_of_message, eol))(input)?;
       
   518     Ok((i, Empty))
       
   519 }
       
   520 
       
   521 fn malformed_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
       
   522     let (i, _) = separated_listc(input, eol, a_line)?;
       
   523     Ok((i, Malformed))
       
   524 }
       
   525 
       
   526 fn message(input: &[u8]) -> HWResult<HWProtocolMessage> {
       
   527     alt((
       
   528         |i| {
       
   529             terminatedc(
       
   530                 i,
       
   531                 alt((
       
   532                     no_arg_message,
       
   533                     single_arg_message,
       
   534                     cmd_message,
       
   535                     config_message,
       
   536                     complex_message,
       
   537                 )),
       
   538                 end_of_message,
       
   539             )
       
   540         },
       
   541         |i| terminatedc(i, malformed_message, end_of_message),
       
   542         empty_message,
       
   543     ))(input)
       
   544 }
       
   545 
       
   546 pub fn extract_messages<'a>(input: &'a [u8]) -> HWResult<Vec<HWProtocolMessage>> {
       
   547     many0(|i: &'a [u8]| complete!(i, message))(input)
       
   548 }
   240 
   549 
   241 #[cfg(test)]
   550 #[cfg(test)]
   242 proptest! {
   551 proptest! {
   243     #[test]
   552     #[test]
   244     fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
   553     fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {