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