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