gameServer2/src/protocol/parser.rs
branchui-scaling
changeset 15283 c4fd2813b127
parent 13390 0135e64c6c66
parent 15279 7ab5cf405686
child 15663 d92eeb468dad
equal deleted inserted replaced
13390:0135e64c6c66 15283:c4fd2813b127
     1 use nom::*;
       
     2 
       
     3 use std::str;
       
     4 use std::str::FromStr;
       
     5 use super::messages::HWProtocolMessage;
       
     6 use super::messages::HWProtocolMessage::*;
       
     7 
       
     8 use proptest::test_runner::{TestRunner, Reason};
       
     9 use proptest::arbitrary::{any, any_with, Arbitrary, StrategyFor};
       
    10 use proptest::strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree};
       
    11 use proptest::string::RegexGeneratorValueTree;
       
    12 use std::ops::Range;
       
    13 
       
    14 named!(end_of_message, tag!("\n\n"));
       
    15 named!(str_line<&[u8],   &str>, map_res!(not_line_ending, str::from_utf8));
       
    16 named!(  a_line<&[u8], String>, map!(str_line, String::from));
       
    17 named!( u8_line<&[u8],     u8>, map_res!(str_line, FromStr::from_str));
       
    18 named!(u32_line<&[u8],    u32>, map_res!(str_line, FromStr::from_str));
       
    19 named!(opt_param<&[u8], Option<String> >, opt!(map!(flat_map!(preceded!(eol, str_line), non_empty), String::from)));
       
    20 
       
    21 named!(basic_message<&[u8], HWProtocolMessage>, alt!(
       
    22       do_parse!(tag!("PING") >> (Ping))
       
    23     | do_parse!(tag!("PONG") >> (Pong))
       
    24     | do_parse!(tag!("LIST") >> (List))
       
    25     | do_parse!(tag!("BANLIST")        >> (BanList))
       
    26     | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
       
    27     | do_parse!(tag!("TOGGLE_READY")   >> (ToggleReady))
       
    28     | do_parse!(tag!("START_GAME")     >> (StartGame))
       
    29     | do_parse!(tag!("ROUNDFINISHED")  >> (RoundFinished))
       
    30     | do_parse!(tag!("TOGGLE_RESTRICT_JOINS")  >> (ToggleRestrictJoin))
       
    31     | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS")  >> (ToggleRestrictTeams))
       
    32     | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
       
    33 ));
       
    34 
       
    35 named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
       
    36       do_parse!(tag!("NICK")    >> eol >> n: a_line >> (Nick(n)))
       
    37     | do_parse!(tag!("INFO")    >> eol >> n: a_line >> (Info(n)))
       
    38     | do_parse!(tag!("CHAT")    >> eol >> m: a_line >> (Chat(m)))
       
    39     | do_parse!(tag!("FOLLOW")  >> eol >> n: a_line >> (Follow(n)))
       
    40     | do_parse!(tag!("KICK")    >> eol >> n: a_line >> (Kick(n)))
       
    41     | do_parse!(tag!("UNBAN")   >> eol >> n: a_line >> (Unban(n)))
       
    42     | do_parse!(tag!("EM")      >> eol >> m: a_line >> (EngineMessage(m)))
       
    43     | do_parse!(tag!("TEAMCHAT")    >> eol >> m: a_line >> (TeamChat(m)))
       
    44     | do_parse!(tag!("ROOM_NAME")   >> eol >> n: a_line >> (RoomName(n)))
       
    45     | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
       
    46 
       
    47     | do_parse!(tag!("PROTO")   >> eol >> d: u32_line >> (Proto(d)))
       
    48 
       
    49     | do_parse!(tag!("QUIT")   >> msg: opt_param >> (Quit(msg)))
       
    50 ));
       
    51 
       
    52 named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
       
    53       do_parse!(tag_no_case!("STATS") >> (Stats))
       
    54     | do_parse!(tag_no_case!("FIX")   >> (Fix))
       
    55     | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
       
    56     | do_parse!(tag_no_case!("RESTART_SERVER") >> eol >> tag!("YES") >> (RestartServer))
       
    57     | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
       
    58     | do_parse!(tag_no_case!("SUPER_POWER")     >> (SuperPower))
       
    59     | do_parse!(tag_no_case!("PART")     >> m: opt_param >> (Part(m)))
       
    60     | do_parse!(tag_no_case!("QUIT")     >> m: opt_param >> (Quit(m)))
       
    61     | do_parse!(tag_no_case!("DELEGATE") >> eol >> n: a_line  >> (Delegate(n)))
       
    62     | do_parse!(tag_no_case!("SAVEROOM") >> eol >> r: a_line  >> (SaveRoom(r)))
       
    63     | do_parse!(tag_no_case!("LOADROOM") >> eol >> r: a_line  >> (LoadRoom(r)))
       
    64     | do_parse!(tag_no_case!("DELETE")   >> eol >> r: a_line  >> (Delete(r)))
       
    65     | do_parse!(tag_no_case!("GLOBAL")   >> eol >> m: a_line  >> (Global(m)))
       
    66     | do_parse!(tag_no_case!("WATCH")    >> eol >> i: a_line  >> (Watch(i)))
       
    67     | do_parse!(tag_no_case!("GREETING") >> eol >> m: a_line  >> (Greeting(m)))
       
    68     | do_parse!(tag_no_case!("VOTE")     >> eol >> m: a_line  >> (Vote(m)))
       
    69     | do_parse!(tag_no_case!("FORCE")    >> eol >> m: a_line  >> (ForceVote(m)))
       
    70     | do_parse!(tag_no_case!("INFO")     >> eol >> n: a_line  >> (Info(n)))
       
    71     | do_parse!(tag_no_case!("MAXTEAMS") >> eol >> n: u8_line >> (MaxTeams(n)))
       
    72 )));
       
    73 
       
    74 named!(complex_message<&[u8], HWProtocolMessage>, alt!(
       
    75       do_parse!(tag!("PASSWORD")  >> eol >>
       
    76                     p: a_line     >> eol >>
       
    77                     s: a_line     >>
       
    78                     (Password(p, s)))
       
    79     | do_parse!(tag!("CHECKER")   >> eol >>
       
    80                     i: u32_line   >> eol >>
       
    81                     n: a_line     >> eol >>
       
    82                     p: a_line     >>
       
    83                     (Checker(i, n, p)))
       
    84     | do_parse!(tag!("CREATE_ROOM") >> eol >>
       
    85                     n: a_line       >>
       
    86                     p: opt_param    >>
       
    87                     (CreateRoom(n, p)))
       
    88     | do_parse!(tag!("JOIN")        >> eol >>
       
    89                     n: a_line       >>
       
    90                     p: opt_param    >>
       
    91                     (Join(n, p)))
       
    92     | do_parse!(tag!("BAN")    >> eol >>
       
    93                     n: a_line     >> eol >>
       
    94                     r: a_line     >> eol >>
       
    95                     t: u32_line   >>
       
    96                     (Ban(n, r, t)))
       
    97     | do_parse!(tag!("BAN_IP")    >> eol >>
       
    98                     n: a_line     >> eol >>
       
    99                     r: a_line     >> eol >>
       
   100                     t: u32_line   >>
       
   101                     (BanIP(n, r, t)))
       
   102     | do_parse!(tag!("BAN_NICK")    >> eol >>
       
   103                     n: a_line     >> eol >>
       
   104                     r: a_line     >> eol >>
       
   105                     t: u32_line   >>
       
   106                     (BanNick(n, r, t)))
       
   107 ));
       
   108 
       
   109 named!(malformed_message<&[u8], HWProtocolMessage>,
       
   110     do_parse!(separated_list!(eol, a_line) >> (Malformed)));
       
   111 
       
   112 named!(empty_message<&[u8], HWProtocolMessage>,
       
   113     do_parse!(alt!(end_of_message | eol) >> (Empty)));
       
   114 
       
   115 named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
       
   116     alt!(
       
   117           basic_message
       
   118         | one_param_message
       
   119         | cmd_message
       
   120         | complex_message
       
   121         ), end_of_message
       
   122     )
       
   123     | terminated!(malformed_message, end_of_message)
       
   124     | empty_message
       
   125     )
       
   126 );
       
   127 
       
   128 named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message)));
       
   129 
       
   130 // Due to inability to define From between Options
       
   131 trait Into2<T>: Sized { fn into2(self) -> T; }
       
   132 impl <T> Into2<T> for T { fn into2(self) -> T { self } }
       
   133 impl Into2<String> for Ascii { fn into2(self) -> String { self.0 } }
       
   134 impl Into2<Option<String>> for Option<Ascii>{
       
   135     fn into2(self) -> Option<String> { self.map(|x| {x.0}) }
       
   136 }
       
   137 
       
   138 macro_rules! proto_msg_case {
       
   139     ($val: ident()) =>
       
   140         (Just($val));
       
   141     ($val: ident($arg: ty)) =>
       
   142         (any::<$arg>().prop_map(|v| {$val(v.into2())}));
       
   143     ($val: ident($arg1: ty, $arg2: ty)) =>
       
   144         (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())}));
       
   145     ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) =>
       
   146         (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())}));
       
   147 }
       
   148 
       
   149 macro_rules! proto_msg_match {
       
   150     ($var: expr, def = $default: ident, $($num: expr => $constr: ident $res: tt),*) => (
       
   151         match $var {
       
   152             $($num => (proto_msg_case!($constr $res)).boxed()),*,
       
   153             _ => Just($default).boxed()
       
   154         }
       
   155     )
       
   156 }
       
   157 
       
   158 #[derive(Debug)]
       
   159 struct Ascii(String);
       
   160 
       
   161 struct AsciiValueTree(RegexGeneratorValueTree<String>);
       
   162 
       
   163 impl ValueTree for AsciiValueTree {
       
   164     type Value = Ascii;
       
   165 
       
   166     fn current(&self) -> Self::Value { Ascii(self.0.current()) }
       
   167     fn simplify(&mut self) -> bool { self.0.simplify() }
       
   168     fn complicate(&mut self) -> bool { self.0.complicate() }
       
   169 }
       
   170 
       
   171 impl Arbitrary for Ascii {
       
   172     type Parameters = <String as Arbitrary>::Parameters;
       
   173 
       
   174     fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
       
   175         any_with::<String>(args)
       
   176             .prop_filter("not ascii", |s| {
       
   177                 s.len() > 0 && s.is_ascii() &&
       
   178                 s.find(|c| {
       
   179                     ['\0', '\n', '\x20'].contains(&c)
       
   180                 }).is_none()})
       
   181             .prop_map(Ascii)
       
   182             .boxed()
       
   183     }
       
   184 
       
   185     type Strategy = BoxedStrategy<Ascii>;
       
   186     type ValueTree = Box<ValueTree<Value = Ascii>>;
       
   187 }
       
   188 
       
   189 fn gen_proto_msg() -> BoxedStrategy<HWProtocolMessage> where {
       
   190     let res = (0..58).no_shrink().prop_flat_map(|i| {
       
   191         proto_msg_match!(i, def = Malformed,
       
   192         0 => Ping(),
       
   193         1 => Pong(),
       
   194         2 => Quit(Option<Ascii>),
       
   195         //3 => Cmd
       
   196         4 => Global(Ascii),
       
   197         5 => Watch(Ascii),
       
   198         6 => ToggleServerRegisteredOnly(),
       
   199         7 => SuperPower(),
       
   200         8 => Info(Ascii),
       
   201         9 => Nick(Ascii),
       
   202         10 => Proto(u32),
       
   203         11 => Password(Ascii, Ascii),
       
   204         12 => Checker(u32, Ascii, Ascii),
       
   205         13 => List(),
       
   206         14 => Chat(Ascii),
       
   207         15 => CreateRoom(Ascii, Option<Ascii>),
       
   208         16 => Join(Ascii, Option<Ascii>),
       
   209         17 => Follow(Ascii),
       
   210         //18 => Rnd(Vec<String>),
       
   211         19 => Kick(Ascii),
       
   212         20 => Ban(Ascii, Ascii, u32),
       
   213         21 => BanIP(Ascii, Ascii, u32),
       
   214         22 => BanNick(Ascii, Ascii, u32),
       
   215         23 => BanList(),
       
   216         24 => Unban(Ascii),
       
   217         //25 => SetServerVar(ServerVar),
       
   218         26 => GetServerVar(),
       
   219         27 => RestartServer(),
       
   220         28 => Stats(),
       
   221         29 => Part(Option<Ascii>),
       
   222         //30 => Cfg(GameCfg),
       
   223         //31 => AddTeam(TeamInfo),
       
   224         32 => RemoveTeam(Ascii),
       
   225         //33 => SetHedgehogsNumber(String, u8),
       
   226         //34 => SetTeamColor(String, u8),
       
   227         35 => ToggleReady(),
       
   228         36 => StartGame(),
       
   229         37 => EngineMessage(Ascii),
       
   230         38 => RoundFinished(),
       
   231         39 => ToggleRestrictJoin(),
       
   232         40 => ToggleRestrictTeams(),
       
   233         41 => ToggleRegisteredOnly(),
       
   234         42 => RoomName(Ascii),
       
   235         43 => Delegate(Ascii),
       
   236         44 => TeamChat(Ascii),
       
   237         45 => MaxTeams(u8),
       
   238         46 => Fix(),
       
   239         47 => Unfix(),
       
   240         48 => Greeting(Ascii),
       
   241         //49 => CallVote(Option<(String, Option<String>)>),
       
   242         50 => Vote(String),
       
   243         51 => ForceVote(Ascii),
       
   244         //52 => Save(String, String),
       
   245         53 => Delete(Ascii),
       
   246         54 => SaveRoom(Ascii),
       
   247         55 => LoadRoom(Ascii),
       
   248         56 => Malformed(),
       
   249         57 => Empty()
       
   250     )});
       
   251     res.boxed()
       
   252 }
       
   253 
       
   254 proptest! {
       
   255     #[test]
       
   256     fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
       
   257         println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
       
   258         assert_eq!(message(msg.to_raw_protocol().as_bytes()), IResult::Done(&b""[..], msg.clone()))
       
   259     }
       
   260 }
       
   261 
       
   262 #[test]
       
   263 fn parse_test() {
       
   264     assert_eq!(message(b"PING\n\n"),          IResult::Done(&b""[..], Ping));
       
   265     assert_eq!(message(b"START_GAME\n\n"),    IResult::Done(&b""[..], StartGame));
       
   266     assert_eq!(message(b"NICK\nit's me\n\n"), IResult::Done(&b""[..], Nick("it's me".to_string())));
       
   267     assert_eq!(message(b"PROTO\n51\n\n"),     IResult::Done(&b""[..], Proto(51)));
       
   268     assert_eq!(message(b"QUIT\nbye-bye\n\n"), IResult::Done(&b""[..], Quit(Some("bye-bye".to_string()))));
       
   269     assert_eq!(message(b"QUIT\n\n"),          IResult::Done(&b""[..], Quit(None)));
       
   270     assert_eq!(message(b"CMD\nwatch\ndemo\n\n"), IResult::Done(&b""[..], Watch("demo".to_string())));
       
   271     assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), IResult::Done(&b""[..], Ban("me".to_string(), "bad".to_string(), 77)));
       
   272 
       
   273     assert_eq!(message(b"CMD\nPART\n\n"),      IResult::Done(&b""[..], Part(None)));
       
   274     assert_eq!(message(b"CMD\nPART\n_msg_\n\n"), IResult::Done(&b""[..], Part(Some("_msg_".to_string()))));
       
   275 
       
   276     assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"),    IResult::Done(&b""[..], vec![Malformed]));
       
   277 
       
   278     assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), IResult::Done(&b"P"[..], vec![Ping, Ping]));
       
   279     assert_eq!(extract_messages(b"SING\n\nPING\n\n"),  IResult::Done(&b""[..],  vec![Malformed, Ping]));
       
   280     assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"),  IResult::Done(&b""[..],  vec![Empty, Empty, Ping]));
       
   281     assert_eq!(extract_messages(b"\n\n\nPING\n\n"),    IResult::Done(&b""[..],  vec![Empty, Empty, Ping]));
       
   282 }