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 } |
|