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()) { |