--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/Cargo.toml Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,12 @@
+[package]
+name = "hedgewars-network-protocol"
+version = "0.1.0"
+authors = ["Andrey Korotaev <a.korotaev@hedgewars.org>"]
+edition = "2018"
+
+[dependencies]
+nom = "6"
+serde_derive = "1.0"
+serde = "1.0"
+
+proptest = "1.0"
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/lib.rs Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,3 @@
+pub mod messages;
+pub mod parser;
+pub mod types;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/messages.rs Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,421 @@
+use crate::types::{GameCfg, ServerVar, TeamInfo, VoteType};
+use std::iter::once;
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum HwProtocolMessage {
+ // common messages
+ Ping,
+ Pong,
+ Quit(Option<String>),
+ Global(String),
+ Watch(u32),
+ ToggleServerRegisteredOnly,
+ SuperPower,
+ Info(String),
+ // anteroom messages
+ Nick(String),
+ Proto(u16),
+ Password(String, String),
+ Checker(u16, String, String),
+ // lobby messages
+ List,
+ Chat(String),
+ CreateRoom(String, Option<String>),
+ JoinRoom(String, Option<String>),
+ Follow(String),
+ Rnd(Vec<String>),
+ Kick(String),
+ Ban(String, String, u32),
+ BanIp(String, String, u32),
+ BanNick(String, String, u32),
+ BanList,
+ Unban(String),
+ SetServerVar(ServerVar),
+ GetServerVar,
+ RestartServer,
+ Stats,
+ // room messages
+ Part(Option<String>),
+ Cfg(GameCfg),
+ AddTeam(Box<TeamInfo>),
+ RemoveTeam(String),
+ SetHedgehogsNumber(String, u8),
+ SetTeamColor(String, u8),
+ ToggleReady,
+ StartGame,
+ EngineMessage(String),
+ RoundFinished,
+ ToggleRestrictJoin,
+ ToggleRestrictTeams,
+ ToggleRegisteredOnly,
+ RoomName(String),
+ Delegate(String),
+ TeamChat(String),
+ MaxTeams(u8),
+ Fix,
+ Unfix,
+ Greeting(Option<String>),
+ CallVote(Option<VoteType>),
+ Vote(bool),
+ ForceVote(bool),
+ Save(String, String),
+ Delete(String),
+ SaveRoom(String),
+ LoadRoom(String),
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum ProtocolFlags {
+ InRoom,
+ RoomMaster,
+ Ready,
+ InGame,
+ Registered,
+ Admin,
+ Contributor,
+}
+
+impl ProtocolFlags {
+ #[inline]
+ fn flag_char(&self) -> char {
+ match self {
+ ProtocolFlags::InRoom => 'i',
+ ProtocolFlags::RoomMaster => 'h',
+ ProtocolFlags::Ready => 'r',
+ ProtocolFlags::InGame => 'g',
+ ProtocolFlags::Registered => 'u',
+ ProtocolFlags::Admin => 'a',
+ ProtocolFlags::Contributor => 'c',
+ }
+ }
+
+ #[inline]
+ fn format(prefix: char, flags: &[ProtocolFlags]) -> String {
+ once(prefix)
+ .chain(flags.iter().map(|f| f.flag_char()))
+ .collect()
+ }
+}
+
+#[inline]
+pub fn add_flags(flags: &[ProtocolFlags]) -> String {
+ ProtocolFlags::format('+', flags)
+}
+
+#[inline]
+pub fn remove_flags(flags: &[ProtocolFlags]) -> String {
+ ProtocolFlags::format('-', flags)
+}
+
+#[derive(Debug)]
+pub enum HwServerMessage {
+ Connected(u32),
+ Redirect(u16),
+
+ Ping,
+ Pong,
+ Bye(String),
+
+ Nick(String),
+ Proto(u16),
+ AskPassword(String),
+ ServerAuth(String),
+ LogonPassed,
+
+ LobbyLeft(String, String),
+ LobbyJoined(Vec<String>),
+ ChatMsg { nick: String, msg: String },
+ ClientFlags(String, Vec<String>),
+ Rooms(Vec<String>),
+ RoomAdd(Vec<String>),
+ RoomJoined(Vec<String>),
+ RoomLeft(String, String),
+ RoomRemove(String),
+ RoomUpdated(String, Vec<String>),
+ Joining(String),
+ TeamAdd(Vec<String>),
+ TeamRemove(String),
+ TeamAccepted(String),
+ TeamColor(String, u8),
+ HedgehogsNumber(String, u8),
+ ConfigEntry(String, Vec<String>),
+ Kicked,
+ RunGame,
+ ForwardEngineMessage(Vec<String>),
+ RoundFinished,
+ ReplayStart,
+
+ Info(Vec<String>),
+ ServerMessage(String),
+ ServerVars(Vec<String>),
+ Notice(String),
+ Warning(String),
+ Error(String),
+ Unreachable,
+
+ //Deprecated messages
+ LegacyReady(bool, Vec<String>),
+}
+
+fn special_chat(nick: &str, msg: String) -> HwServerMessage {
+ HwServerMessage::ChatMsg {
+ nick: nick.to_string(),
+ msg,
+ }
+}
+
+pub fn server_chat(msg: String) -> HwServerMessage {
+ special_chat("[server]", msg)
+}
+
+pub fn global_chat(msg: String) -> HwServerMessage {
+ special_chat("(global notice)", msg)
+}
+
+impl ServerVar {
+ pub fn to_protocol(&self) -> Vec<String> {
+ use ServerVar::*;
+ match self {
+ MOTDNew(s) => vec!["MOTD_NEW".to_string(), s.clone()],
+ MOTDOld(s) => vec!["MOTD_OLD".to_string(), s.clone()],
+ LatestProto(n) => vec!["LATEST_PROTO".to_string(), n.to_string()],
+ }
+ }
+}
+
+impl VoteType {
+ pub fn to_protocol(&self) -> Vec<String> {
+ use VoteType::*;
+ match self {
+ Kick(nick) => vec!["KICK".to_string(), nick.clone()],
+ Map(None) => vec!["MAP".to_string()],
+ Map(Some(name)) => vec!["MAP".to_string(), name.clone()],
+ Pause => vec!["PAUSE".to_string()],
+ NewSeed => vec!["NEWSEED".to_string()],
+ HedgehogsPerTeam(count) => vec!["HEDGEHOGS".to_string(), count.to_string()],
+ }
+ }
+}
+
+impl GameCfg {
+ pub fn to_protocol(&self) -> (String, Vec<String>) {
+ use GameCfg::*;
+ match self {
+ FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]),
+ MapType(t) => ("MAP".to_string(), vec![t.to_string()]),
+ MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]),
+ MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]),
+ Seed(s) => ("SEED".to_string(), vec![s.to_string()]),
+ Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]),
+
+ Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]),
+ Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]),
+ Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]),
+ Scheme(n, s) => ("SCHEME".to_string(), {
+ let mut v = vec![n.to_string()];
+ v.extend(s.clone());
+ v
+ }),
+ Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]),
+ Theme(t) => ("THEME".to_string(), vec![t.to_string()]),
+ DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()]),
+ }
+ }
+
+ pub fn to_server_msg(&self) -> HwServerMessage {
+ let (name, args) = self.to_protocol();
+ HwServerMessage::ConfigEntry(name, args)
+ }
+}
+
+impl TeamInfo {
+ pub fn to_protocol(&self) -> Vec<String> {
+ let mut info = vec![
+ self.name.clone(),
+ self.grave.clone(),
+ self.fort.clone(),
+ self.voice_pack.clone(),
+ self.flag.clone(),
+ self.owner.clone(),
+ self.difficulty.to_string(),
+ ];
+ let hogs = self
+ .hedgehogs
+ .iter()
+ .flat_map(|h| once(h.name.clone()).chain(once(h.hat.clone())));
+ info.extend(hogs);
+ info
+ }
+}
+
+macro_rules! const_braces {
+ ($e: expr) => {
+ "{}\n"
+ };
+}
+
+macro_rules! msg {
+ [$($part: expr),*] => {
+ format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
+ };
+}
+
+impl HwProtocolMessage {
+ /** Converts the message to a raw `String`, which can be sent over the network.
+ *
+ * This is the inverse of the `message` parser.
+ */
+ pub fn to_raw_protocol(&self) -> String {
+ use self::HwProtocolMessage::*;
+ match self {
+ Ping => msg!["PING"],
+ Pong => msg!["PONG"],
+ Quit(None) => msg!["QUIT"],
+ Quit(Some(msg)) => msg!["QUIT", msg],
+ Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)],
+ Watch(name) => msg!["CMD", format!("WATCH {}", name)],
+ ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
+ SuperPower => msg!["CMD", "SUPER_POWER"],
+ Info(info) => msg!["CMD", format!("INFO {}", info)],
+ Nick(nick) => msg!("NICK", nick),
+ Proto(version) => msg!["PROTO", version],
+ Password(p, s) => msg!["PASSWORD", p, s],
+ Checker(i, n, p) => msg!["CHECKER", i, n, p],
+ List => msg!["LIST"],
+ Chat(msg) => msg!["CHAT", msg],
+ CreateRoom(name, None) => msg!["CREATE_ROOM", name],
+ CreateRoom(name, Some(password)) => msg!["CREATE_ROOM", name, password],
+ JoinRoom(name, None) => msg!["JOIN_ROOM", name],
+ JoinRoom(name, Some(password)) => msg!["JOIN_ROOM", name, password],
+ Follow(name) => msg!["FOLLOW", name],
+ Rnd(args) => {
+ if args.is_empty() {
+ msg!["CMD", "RND"]
+ } else {
+ msg!["CMD", format!("RND {}", args.join(" "))]
+ }
+ }
+ Kick(name) => msg!["KICK", name],
+ Ban(name, reason, time) => msg!["BAN", name, reason, time],
+ BanIp(ip, reason, time) => msg!["BAN_IP", ip, reason, time],
+ BanNick(nick, reason, time) => msg!("BAN_NICK", nick, reason, time),
+ BanList => msg!["BANLIST"],
+ Unban(name) => msg!["UNBAN", name],
+ SetServerVar(var) => construct_message(&["SET_SERVER_VAR"], &var.to_protocol()),
+ GetServerVar => msg!["GET_SERVER_VAR"],
+ RestartServer => msg!["CMD", "RESTART_SERVER YES"],
+ Stats => msg!["CMD", "STATS"],
+ Part(None) => msg!["PART"],
+ Part(Some(msg)) => msg!["PART", msg],
+ Cfg(config) => {
+ let (name, args) = config.to_protocol();
+ msg!["CFG", name, args.join("\n")]
+ }
+ AddTeam(info) => msg![
+ "ADD_TEAM",
+ info.name,
+ info.color,
+ info.grave,
+ info.fort,
+ info.voice_pack,
+ info.flag,
+ info.difficulty,
+ &(info.hedgehogs.iter())
+ .flat_map(|h| [&h.name[..], &h.hat[..]])
+ .collect::<Vec<_>>()
+ .join("\n")
+ ],
+ RemoveTeam(name) => msg!["REMOVE_TEAM", name],
+ SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number],
+ SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color],
+ ToggleReady => msg!["TOGGLE_READY"],
+ StartGame => msg!["START_GAME"],
+ EngineMessage(msg) => msg!["EM", msg],
+ RoundFinished => msg!["ROUNDFINISHED"],
+ ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"],
+ ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"],
+ ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"],
+ RoomName(name) => msg!["ROOM_NAME", name],
+ Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)],
+ TeamChat(msg) => msg!["TEAMCHAT", msg],
+ MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)],
+ Fix => msg!["CMD", "FIX"],
+ Unfix => msg!["CMD", "UNFIX"],
+ Greeting(None) => msg!["CMD", "GREETING"],
+ Greeting(Some(msg)) => msg!["CMD", format!("GREETING {}", msg)],
+ CallVote(None) => msg!["CMD", "CALLVOTE"],
+ CallVote(Some(vote)) => {
+ msg!["CMD", format!("CALLVOTE {}", &vote.to_protocol().join(" "))]
+ }
+ Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg { "YES" } else { "NO" })],
+ ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg { "YES" } else { "NO" })],
+ Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)],
+ Delete(name) => msg!["CMD", format!("DELETE {}", name)],
+ SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)],
+ LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)],
+ }
+ }
+}
+
+fn construct_message(header: &[&str], msg: &[String]) -> String {
+ let mut v: Vec<_> = header.iter().cloned().collect();
+ v.extend(msg.iter().map(|s| &s[..]));
+ v.push("\n");
+ v.join("\n")
+}
+
+impl HwServerMessage {
+ pub fn to_raw_protocol(&self) -> String {
+ use self::HwServerMessage::*;
+ match self {
+ Ping => msg!["PING"],
+ Pong => msg!["PONG"],
+ Connected(protocol_version) => msg![
+ "CONNECTED",
+ "Hedgewars server https://www.hedgewars.org/",
+ protocol_version
+ ],
+ Redirect(port) => msg!["REDIRECT", port],
+ Bye(msg) => msg!["BYE", msg],
+ Nick(nick) => msg!["NICK", nick],
+ Proto(proto) => msg!["PROTO", proto],
+ AskPassword(salt) => msg!["ASKPASSWORD", salt],
+ ServerAuth(hash) => msg!["SERVER_AUTH", hash],
+ LogonPassed => msg!["LOGONPASSED"],
+ LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg],
+ LobbyJoined(nicks) => construct_message(&["LOBBY:JOINED"], &nicks),
+ ClientFlags(flags, nicks) => construct_message(&["CLIENT_FLAGS", flags], &nicks),
+ Rooms(info) => construct_message(&["ROOMS"], &info),
+ RoomAdd(info) => construct_message(&["ROOM", "ADD"], &info),
+ RoomJoined(nicks) => construct_message(&["JOINED"], &nicks),
+ RoomLeft(nick, msg) => msg!["LEFT", nick, msg],
+ RoomRemove(name) => msg!["ROOM", "DEL", name],
+ RoomUpdated(name, info) => construct_message(&["ROOM", "UPD", name], &info),
+ Joining(name) => msg!["JOINING", name],
+ TeamAdd(info) => construct_message(&["ADD_TEAM"], &info),
+ TeamRemove(name) => msg!["REMOVE_TEAM", name],
+ TeamAccepted(name) => msg!["TEAM_ACCEPTED", name],
+ TeamColor(name, color) => msg!["TEAM_COLOR", name, color],
+ HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
+ ConfigEntry(name, values) => construct_message(&["CFG", name], &values),
+ Kicked => msg!["KICKED"],
+ RunGame => msg!["RUN_GAME"],
+ ForwardEngineMessage(em) => construct_message(&["EM"], &em),
+ RoundFinished => msg!["ROUND_FINISHED"],
+ ChatMsg { nick, msg } => msg!["CHAT", nick, msg],
+ Info(info) => construct_message(&["INFO"], &info),
+ ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
+ ServerVars(vars) => construct_message(&["SERVER_VARS"], &vars),
+ Notice(msg) => msg!["NOTICE", msg],
+ Warning(msg) => msg!["WARNING", msg],
+ Error(msg) => msg!["ERROR", msg],
+ ReplayStart => msg!["REPLAY_START"],
+
+ LegacyReady(is_ready, nicks) => {
+ construct_message(&[if *is_ready { "READY" } else { "NOT_READY" }], &nicks)
+ }
+
+ _ => msg!["ERROR", "UNIMPLEMENTED"],
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/parser.rs Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,505 @@
+/** The parsers for the chat and multiplayer protocol. The main parser is `message`.
+ * # Protocol
+ * All messages consist of `\n`-separated strings. The end of a message is
+ * indicated by a double newline - `\n\n`.
+ *
+ * For example, a nullary command like PING will be actually sent as `PING\n\n`.
+ * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
+ */
+use nom::{
+ branch::alt,
+ bytes::complete::{tag, tag_no_case, take_until, take_while},
+ character::complete::{newline, not_line_ending},
+ combinator::{map, peek},
+ error::{ErrorKind, ParseError},
+ multi::separated_list0,
+ sequence::{delimited, pair, preceded, terminated, tuple},
+ Err, IResult,
+};
+
+use std::{
+ num::ParseIntError,
+ str,
+ str::{FromStr, Utf8Error},
+};
+
+use crate::messages::{HwProtocolMessage, HwProtocolMessage::*};
+use crate::types::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType};
+
+#[derive(Debug, PartialEq)]
+pub struct HwProtocolError {}
+
+impl HwProtocolError {
+ pub fn new() -> Self {
+ HwProtocolError {}
+ }
+}
+
+impl<I> ParseError<I> for HwProtocolError {
+ fn from_error_kind(_input: I, _kind: ErrorKind) -> Self {
+ HwProtocolError::new()
+ }
+
+ fn append(_input: I, _kind: ErrorKind, _other: Self) -> Self {
+ HwProtocolError::new()
+ }
+}
+
+impl From<Utf8Error> for HwProtocolError {
+ fn from(_: Utf8Error) -> Self {
+ HwProtocolError::new()
+ }
+}
+
+impl From<ParseIntError> for HwProtocolError {
+ fn from(_: ParseIntError) -> Self {
+ HwProtocolError::new()
+ }
+}
+
+pub type HwResult<'a, O> = IResult<&'a [u8], O, HwProtocolError>;
+
+fn end_of_message(input: &[u8]) -> HwResult<&[u8]> {
+ tag("\n\n")(input)
+}
+
+fn convert_utf8(input: &[u8]) -> HwResult<&str> {
+ match str::from_utf8(input) {
+ Ok(str) => Ok((b"", str)),
+ Err(utf_err) => Result::Err(Err::Failure(utf_err.into())),
+ }
+}
+
+fn convert_from_str<T>(str: &str) -> HwResult<T>
+where
+ T: FromStr<Err = ParseIntError>,
+{
+ match T::from_str(str) {
+ Ok(x) => Ok((b"", x)),
+ Err(format_err) => Result::Err(Err::Failure(format_err.into())),
+ }
+}
+
+fn str_line(input: &[u8]) -> HwResult<&str> {
+ let (i, text) = not_line_ending(<&[u8]>::clone(&input))?;
+ if i != input {
+ Ok((i, convert_utf8(text)?.1))
+ } else {
+ Err(Err::Error(HwProtocolError::new()))
+ }
+}
+
+fn a_line(input: &[u8]) -> HwResult<String> {
+ map(str_line, String::from)(input)
+}
+
+fn cmd_arg(input: &[u8]) -> HwResult<String> {
+ let delimiters = b" \n";
+ let (i, str) = take_while(move |c| !delimiters.contains(&c))(<&[u8]>::clone(&input))?;
+ if i != input {
+ Ok((i, convert_utf8(str)?.1.to_string()))
+ } else {
+ Err(Err::Error(HwProtocolError::new()))
+ }
+}
+
+fn u8_line(input: &[u8]) -> HwResult<u8> {
+ let (i, str) = str_line(input)?;
+ Ok((i, convert_from_str(str)?.1))
+}
+
+fn u16_line(input: &[u8]) -> HwResult<u16> {
+ let (i, str) = str_line(input)?;
+ Ok((i, convert_from_str(str)?.1))
+}
+
+fn u32_line(input: &[u8]) -> HwResult<u32> {
+ let (i, str) = str_line(input)?;
+ Ok((i, convert_from_str(str)?.1))
+}
+
+fn yes_no_line(input: &[u8]) -> HwResult<bool> {
+ alt((
+ map(tag_no_case(b"YES"), |_| true),
+ map(tag_no_case(b"NO"), |_| false),
+ ))(input)
+}
+
+fn opt_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
+ alt((
+ map(peek(end_of_message), |_| None),
+ map(preceded(tag("\n"), a_line), Some),
+ ))(input)
+}
+
+fn spaces(input: &[u8]) -> HwResult<&[u8]> {
+ preceded(tag(" "), take_while(|c| c == b' '))(input)
+}
+
+fn opt_space_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
+ alt((
+ map(peek(end_of_message), |_| None),
+ map(preceded(spaces, a_line), Some),
+ ))(input)
+}
+
+fn hedgehog_array(input: &[u8]) -> HwResult<[HedgehogInfo; 8]> {
+ fn hedgehog_line(input: &[u8]) -> HwResult<HedgehogInfo> {
+ map(
+ tuple((terminated(a_line, newline), a_line)),
+ |(name, hat)| HedgehogInfo { name, hat },
+ )(input)
+ }
+
+ let (i, (h1, h2, h3, h4, h5, h6, h7, h8)) = tuple((
+ terminated(hedgehog_line, newline),
+ terminated(hedgehog_line, newline),
+ terminated(hedgehog_line, newline),
+ terminated(hedgehog_line, newline),
+ terminated(hedgehog_line, newline),
+ terminated(hedgehog_line, newline),
+ terminated(hedgehog_line, newline),
+ hedgehog_line,
+ ))(input)?;
+
+ Ok((i, [h1, h2, h3, h4, h5, h6, h7, h8]))
+}
+
+fn voting(input: &[u8]) -> HwResult<VoteType> {
+ alt((
+ map(tag_no_case("PAUSE"), |_| VoteType::Pause),
+ map(tag_no_case("NEWSEED"), |_| VoteType::NewSeed),
+ map(
+ preceded(pair(tag_no_case("KICK"), spaces), a_line),
+ VoteType::Kick,
+ ),
+ map(
+ preceded(pair(tag_no_case("HEDGEHOGS"), spaces), u8_line),
+ VoteType::HedgehogsPerTeam,
+ ),
+ map(preceded(tag_no_case("MAP"), opt_space_arg), VoteType::Map),
+ ))(input)
+}
+
+fn no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ fn message<'a>(
+ name: &'a str,
+ msg: HwProtocolMessage,
+ ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+ move |i| map(tag(name), |_| msg.clone())(i)
+ }
+
+ alt((
+ message("PING", Ping),
+ message("PONG", Pong),
+ message("LIST", List),
+ message("BANLIST", BanList),
+ message("GET_SERVER_VAR", GetServerVar),
+ message("TOGGLE_READY", ToggleReady),
+ message("START_GAME", StartGame),
+ message("TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
+ message("TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
+ message("TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
+ ))(input)
+}
+
+fn single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ fn message<'a, T, F, G>(
+ name: &'a str,
+ parser: F,
+ constructor: G,
+ ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
+ where
+ F: Fn(&[u8]) -> HwResult<T>,
+ G: Fn(T) -> HwProtocolMessage,
+ {
+ map(preceded(tag(name), parser), constructor)
+ }
+
+ alt((
+ message("NICK\n", a_line, Nick),
+ message("INFO\n", a_line, Info),
+ message("CHAT\n", a_line, Chat),
+ message("PART", opt_arg, Part),
+ message("FOLLOW\n", a_line, Follow),
+ message("KICK\n", a_line, Kick),
+ message("UNBAN\n", a_line, Unban),
+ message("EM\n", a_line, EngineMessage),
+ message("TEAMCHAT\n", a_line, TeamChat),
+ message("ROOM_NAME\n", a_line, RoomName),
+ message("REMOVE_TEAM\n", a_line, RemoveTeam),
+ message("ROUNDFINISHED", opt_arg, |_| RoundFinished),
+ message("PROTO\n", u16_line, Proto),
+ message("QUIT", opt_arg, Quit),
+ ))(input)
+}
+
+fn cmd_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+ fn cmd_no_arg<'a>(
+ name: &'a str,
+ msg: HwProtocolMessage,
+ ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+ move |i| map(tag_no_case(name), |_| msg.clone())(i)
+ }
+
+ fn cmd_single_arg<'a, T, F, G>(
+ name: &'a str,
+ parser: F,
+ constructor: G,
+ ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
+ where
+ F: Fn(&'a [u8]) -> HwResult<'a, T>,
+ G: Fn(T) -> HwProtocolMessage,
+ {
+ map(
+ preceded(pair(tag_no_case(name), spaces), parser),
+ constructor,
+ )
+ }
+
+ fn cmd_no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ alt((
+ cmd_no_arg("STATS", Stats),
+ cmd_no_arg("FIX", Fix),
+ cmd_no_arg("UNFIX", Unfix),
+ cmd_no_arg("REGISTERED_ONLY", ToggleServerRegisteredOnly),
+ cmd_no_arg("SUPER_POWER", SuperPower),
+ ))(input)
+ }
+
+ fn cmd_single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ alt((
+ cmd_single_arg("RESTART_SERVER", |i| tag("YES")(i), |_| RestartServer),
+ cmd_single_arg("DELEGATE", a_line, Delegate),
+ cmd_single_arg("DELETE", a_line, Delete),
+ cmd_single_arg("SAVEROOM", a_line, SaveRoom),
+ cmd_single_arg("LOADROOM", a_line, LoadRoom),
+ cmd_single_arg("GLOBAL", a_line, Global),
+ cmd_single_arg("WATCH", u32_line, Watch),
+ cmd_single_arg("VOTE", yes_no_line, Vote),
+ cmd_single_arg("FORCE", yes_no_line, ForceVote),
+ cmd_single_arg("INFO", a_line, Info),
+ cmd_single_arg("MAXTEAMS", u8_line, MaxTeams),
+ cmd_single_arg("CALLVOTE", voting, |v| CallVote(Some(v))),
+ ))(input)
+ }
+
+ preceded(
+ tag("CMD\n"),
+ alt((
+ cmd_no_arg_message,
+ cmd_single_arg_message,
+ map(tag_no_case("CALLVOTE"), |_| CallVote(None)),
+ map(preceded(tag_no_case("GREETING"), opt_space_arg), Greeting),
+ map(preceded(tag_no_case("PART"), opt_space_arg), Part),
+ map(preceded(tag_no_case("QUIT"), opt_space_arg), Quit),
+ map(
+ preceded(
+ tag_no_case("SAVE"),
+ pair(preceded(spaces, cmd_arg), preceded(spaces, cmd_arg)),
+ ),
+ |(n, l)| Save(n, l),
+ ),
+ map(
+ preceded(
+ tag_no_case("RND"),
+ alt((
+ map(peek(end_of_message), |_| vec![]),
+ preceded(spaces, separated_list0(spaces, cmd_arg)),
+ )),
+ ),
+ Rnd,
+ ),
+ )),
+ )(input)
+}
+
+fn config_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+ fn cfg_single_arg<'a, T, F, G>(
+ name: &'a str,
+ parser: F,
+ constructor: G,
+ ) -> impl FnMut(&'a [u8]) -> HwResult<'a, GameCfg>
+ where
+ F: Fn(&[u8]) -> HwResult<T>,
+ G: Fn(T) -> GameCfg,
+ {
+ map(preceded(pair(tag(name), newline), parser), constructor)
+ }
+
+ let (i, cfg) = preceded(
+ tag("CFG\n"),
+ alt((
+ cfg_single_arg("THEME", a_line, GameCfg::Theme),
+ cfg_single_arg("SCRIPT", a_line, GameCfg::Script),
+ cfg_single_arg("MAP", a_line, GameCfg::MapType),
+ cfg_single_arg("MAPGEN", u32_line, GameCfg::MapGenerator),
+ cfg_single_arg("MAZE_SIZE", u32_line, GameCfg::MazeSize),
+ cfg_single_arg("TEMPLATE", u32_line, GameCfg::Template),
+ cfg_single_arg("FEATURE_SIZE", u32_line, GameCfg::FeatureSize),
+ cfg_single_arg("SEED", a_line, GameCfg::Seed),
+ cfg_single_arg("DRAWNMAP", a_line, GameCfg::DrawnMap),
+ preceded(pair(tag("AMMO"), newline), |i| {
+ let (i, name) = a_line(i)?;
+ let (i, value) = opt_arg(i)?;
+ Ok((i, GameCfg::Ammo(name, value)))
+ }),
+ preceded(
+ pair(tag("SCHEME"), newline),
+ map(
+ pair(
+ a_line,
+ alt((
+ map(peek(end_of_message), |_| None),
+ map(preceded(newline, separated_list0(newline, a_line)), Some),
+ )),
+ ),
+ |(name, values)| GameCfg::Scheme(name, values.unwrap_or_default()),
+ ),
+ ),
+ )),
+ )(input)?;
+ Ok((i, Cfg(cfg)))
+}
+
+fn server_var_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ map(
+ preceded(
+ tag("SET_SERVER_VAR\n"),
+ alt((
+ map(preceded(tag("MOTD_NEW\n"), a_line), ServerVar::MOTDNew),
+ map(preceded(tag("MOTD_OLD\n"), a_line), ServerVar::MOTDOld),
+ map(
+ preceded(tag("LATEST_PROTO\n"), u16_line),
+ ServerVar::LatestProto,
+ ),
+ )),
+ ),
+ SetServerVar,
+ )(input)
+}
+
+fn complex_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ alt((
+ preceded(
+ pair(tag("PASSWORD"), newline),
+ map(pair(terminated(a_line, newline), a_line), |(pass, salt)| {
+ Password(pass, salt)
+ }),
+ ),
+ preceded(
+ pair(tag("CHECKER"), newline),
+ map(
+ tuple((
+ terminated(u16_line, newline),
+ terminated(a_line, newline),
+ a_line,
+ )),
+ |(protocol, name, pass)| Checker(protocol, name, pass),
+ ),
+ ),
+ preceded(
+ pair(tag("CREATE_ROOM"), newline),
+ map(pair(a_line, opt_arg), |(name, pass)| CreateRoom(name, pass)),
+ ),
+ preceded(
+ pair(tag("JOIN_ROOM"), newline),
+ map(pair(a_line, opt_arg), |(name, pass)| JoinRoom(name, pass)),
+ ),
+ preceded(
+ pair(tag("ADD_TEAM"), newline),
+ map(
+ tuple((
+ terminated(a_line, newline),
+ terminated(u8_line, newline),
+ terminated(a_line, newline),
+ terminated(a_line, newline),
+ terminated(a_line, newline),
+ terminated(a_line, newline),
+ terminated(u8_line, newline),
+ hedgehog_array,
+ )),
+ |(name, color, grave, fort, voice_pack, flag, difficulty, hedgehogs)| {
+ AddTeam(Box::new(TeamInfo {
+ owner: String::new(),
+ name,
+ color,
+ grave,
+ fort,
+ voice_pack,
+ flag,
+ difficulty,
+ hedgehogs,
+ hedgehogs_number: 0,
+ }))
+ },
+ ),
+ ),
+ preceded(
+ pair(tag("HH_NUM"), newline),
+ map(
+ pair(terminated(a_line, newline), u8_line),
+ |(name, count)| SetHedgehogsNumber(name, count),
+ ),
+ ),
+ preceded(
+ pair(tag("TEAM_COLOR"), newline),
+ map(
+ pair(terminated(a_line, newline), u8_line),
+ |(name, color)| SetTeamColor(name, color),
+ ),
+ ),
+ preceded(
+ pair(tag("BAN"), newline),
+ map(
+ tuple((
+ terminated(a_line, newline),
+ terminated(a_line, newline),
+ u32_line,
+ )),
+ |(name, reason, time)| Ban(name, reason, time),
+ ),
+ ),
+ preceded(
+ pair(tag("BAN_IP"), newline),
+ map(
+ tuple((
+ terminated(a_line, newline),
+ terminated(a_line, newline),
+ u32_line,
+ )),
+ |(ip, reason, time)| BanIp(ip, reason, time),
+ ),
+ ),
+ preceded(
+ pair(tag("BAN_NICK"), newline),
+ map(
+ tuple((
+ terminated(a_line, newline),
+ terminated(a_line, newline),
+ u32_line,
+ )),
+ |(nick, reason, time)| BanNick(nick, reason, time),
+ ),
+ ),
+ ))(input)
+}
+
+pub fn malformed_message(input: &[u8]) -> HwResult<()> {
+ map(terminated(take_until(&b"\n\n"[..]), end_of_message), |_| ())(input)
+}
+
+pub fn message(input: &[u8]) -> HwResult<HwProtocolMessage> {
+ delimited(
+ take_while(|c| c == b'\n'),
+ alt((
+ no_arg_message,
+ single_arg_message,
+ cmd_message,
+ config_message,
+ server_var_message,
+ complex_message,
+ )),
+ end_of_message,
+ )(input)
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/src/types.rs Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,358 @@
+use serde_derive::{Deserialize, Serialize};
+
+pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8;
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum ServerVar {
+ MOTDNew(String),
+ MOTDOld(String),
+ LatestProto(u16),
+}
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum GameCfg {
+ FeatureSize(u32),
+ MapType(String),
+ MapGenerator(u32),
+ MazeSize(u32),
+ Seed(String),
+ Template(u32),
+
+ Ammo(String, Option<String>),
+ Scheme(String, Vec<String>),
+ Script(String),
+ Theme(String),
+ DrawnMap(String),
+}
+
+#[derive(PartialEq, Eq, Clone, Debug, Default)]
+pub struct TeamInfo {
+ pub owner: String,
+ pub name: String,
+ pub color: u8,
+ pub grave: String,
+ pub fort: String,
+ pub voice_pack: String,
+ pub flag: String,
+ pub difficulty: u8,
+ pub hedgehogs_number: u8,
+ pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize],
+}
+
+#[derive(PartialEq, Eq, Clone, Debug, Default)]
+pub struct HedgehogInfo {
+ pub name: String,
+ pub hat: String,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct Ammo {
+ pub name: String,
+ pub settings: Option<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct Scheme {
+ pub name: String,
+ pub settings: Vec<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct RoomConfig {
+ pub feature_size: u32,
+ pub map_type: String,
+ pub map_generator: u32,
+ pub maze_size: u32,
+ pub seed: String,
+ pub template: u32,
+
+ pub ammo: Ammo,
+ pub scheme: Scheme,
+ pub script: String,
+ pub theme: String,
+ pub drawn_map: Option<String>,
+}
+
+impl RoomConfig {
+ pub fn new() -> RoomConfig {
+ RoomConfig {
+ feature_size: 12,
+ map_type: "+rnd+".to_string(),
+ map_generator: 0,
+ maze_size: 0,
+ seed: "seed".to_string(),
+ template: 0,
+
+ ammo: Ammo {
+ name: "Default".to_string(),
+ settings: None,
+ },
+ scheme: Scheme {
+ name: "Default".to_string(),
+ settings: Vec::new(),
+ },
+ script: "Normal".to_string(),
+ theme: "\u{1f994}".to_string(),
+ drawn_map: None,
+ }
+ }
+
+ pub fn set_config(&mut self, cfg: GameCfg) {
+ match cfg {
+ GameCfg::FeatureSize(s) => self.feature_size = s,
+ GameCfg::MapType(t) => self.map_type = t,
+ GameCfg::MapGenerator(g) => self.map_generator = g,
+ GameCfg::MazeSize(s) => self.maze_size = s,
+ GameCfg::Seed(s) => self.seed = s,
+ GameCfg::Template(t) => self.template = t,
+
+ GameCfg::Ammo(n, s) => {
+ self.ammo = Ammo {
+ name: n,
+ settings: s,
+ }
+ }
+ GameCfg::Scheme(n, s) => {
+ self.scheme = Scheme {
+ name: n,
+ settings: s,
+ }
+ }
+ GameCfg::Script(s) => self.script = s,
+ GameCfg::Theme(t) => self.theme = t,
+ GameCfg::DrawnMap(m) => self.drawn_map = Some(m),
+ };
+ }
+
+ pub fn to_map_config(&self) -> Vec<String> {
+ vec![
+ self.feature_size.to_string(),
+ self.map_type.to_string(),
+ self.map_generator.to_string(),
+ self.maze_size.to_string(),
+ self.seed.to_string(),
+ self.template.to_string(),
+ ]
+ }
+
+ pub fn to_game_config(&self) -> Vec<GameCfg> {
+ use GameCfg::*;
+ let mut v = vec![
+ Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()),
+ Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()),
+ Script(self.script.to_string()),
+ Theme(self.theme.to_string()),
+ ];
+ if let Some(ref m) = self.drawn_map {
+ v.push(DrawnMap(m.to_string()))
+ }
+ v
+ }
+}
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum VoteType {
+ Kick(String),
+ Map(Option<String>),
+ Pause,
+ NewSeed,
+ HedgehogsPerTeam(u8),
+}
+
+pub struct Vote {
+ pub is_pro: bool,
+ pub is_forced: bool,
+}
+
+//#[cfg(test)]
+#[macro_use]
+pub mod testing {
+ use crate::types::ServerVar::*;
+ use crate::types::*;
+ use proptest::{
+ arbitrary::{any, Arbitrary},
+ strategy::{BoxedStrategy, Just, Strategy},
+ };
+
+ // Due to inability to define From between Options
+ pub trait Into2<T>: Sized {
+ fn into2(self) -> T;
+ }
+ impl<T> Into2<T> for T {
+ fn into2(self) -> T {
+ self
+ }
+ }
+ impl Into2<Vec<String>> for Vec<Ascii> {
+ fn into2(self) -> Vec<String> {
+ self.into_iter().map(|x| x.0).collect()
+ }
+ }
+ impl Into2<String> for Ascii {
+ fn into2(self) -> String {
+ self.0
+ }
+ }
+ impl Into2<Option<String>> for Option<Ascii> {
+ fn into2(self) -> Option<String> {
+ self.map(|x| x.0)
+ }
+ }
+
+ #[macro_export]
+ macro_rules! proto_msg_case {
+ ($val: ident()) => {
+ Just($val)
+ };
+ ($val: ident($arg: ty)) => {
+ any::<$arg>().prop_map(|v| $val(v.into2()))
+ };
+ ($val: ident($arg1: ty, $arg2: ty)) => {
+ any::<($arg1, $arg2)>().prop_map(|v| $val(v.0.into2(), v.1.into2()))
+ };
+ ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => {
+ any::<($arg1, $arg2, $arg3)>().prop_map(|v| $val(v.0.into2(), v.1.into2(), v.2.into2()))
+ };
+ }
+
+ #[macro_export]
+ macro_rules! proto_msg_match {
+ ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => (
+ match $var {
+ $($num => (proto_msg_case!($constr $res)).boxed()),*,
+ _ => Just($default).boxed()
+ }
+ )
+}
+
+ /// Wrapper type for generating non-empty strings
+ #[derive(Debug)]
+ pub struct Ascii(String);
+
+ impl Arbitrary for Ascii {
+ type Parameters = <String as Arbitrary>::Parameters;
+
+ fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+ "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
+ }
+
+ type Strategy = BoxedStrategy<Ascii>;
+ }
+
+ impl Arbitrary for GameCfg {
+ type Parameters = ();
+
+ fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
+ use crate::types::GameCfg::*;
+ (0..10)
+ .no_shrink()
+ .prop_flat_map(|i| {
+ proto_msg_match!(i, def = FeatureSize(0),
+ 0 => FeatureSize(u32),
+ 1 => MapType(Ascii),
+ 2 => MapGenerator(u32),
+ 3 => MazeSize(u32),
+ 4 => Seed(Ascii),
+ 5 => Template(u32),
+ 6 => Ammo(Ascii, Option<Ascii>),
+ 7 => Scheme(Ascii, Vec<Ascii>),
+ 8 => Script(Ascii),
+ 9 => Theme(Ascii),
+ 10 => DrawnMap(Ascii))
+ })
+ .boxed()
+ }
+
+ type Strategy = BoxedStrategy<GameCfg>;
+ }
+
+ impl Arbitrary for TeamInfo {
+ type Parameters = ();
+
+ fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
+ (
+ "[a-z]+",
+ 0u8..127u8,
+ "[a-z]+",
+ "[a-z]+",
+ "[a-z]+",
+ "[a-z]+",
+ 0u8..127u8,
+ )
+ .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| {
+ fn hog(n: u8) -> HedgehogInfo {
+ HedgehogInfo {
+ name: format!("hog{}", n),
+ hat: format!("hat{}", n),
+ }
+ }
+ let hedgehogs = [
+ hog(1),
+ hog(2),
+ hog(3),
+ hog(4),
+ hog(5),
+ hog(6),
+ hog(7),
+ hog(8),
+ ];
+ TeamInfo {
+ owner: String::new(),
+ name,
+ color,
+ grave,
+ fort,
+ voice_pack,
+ flag,
+ difficulty,
+ hedgehogs,
+ hedgehogs_number: 0,
+ }
+ })
+ .boxed()
+ }
+
+ type Strategy = BoxedStrategy<TeamInfo>;
+ }
+
+ impl Arbitrary for ServerVar {
+ type Parameters = ();
+
+ fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+ (0..=2)
+ .no_shrink()
+ .prop_flat_map(|i| {
+ proto_msg_match!(i, def = ServerVar::LatestProto(0),
+ 0 => MOTDNew(Ascii),
+ 1 => MOTDOld(Ascii),
+ 2 => LatestProto(u16)
+ )
+ })
+ .boxed()
+ }
+
+ type Strategy = BoxedStrategy<ServerVar>;
+ }
+
+ impl Arbitrary for VoteType {
+ type Parameters = ();
+
+ fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+ use VoteType::*;
+ (0..=4)
+ .no_shrink()
+ .prop_flat_map(|i| {
+ proto_msg_match!(i, def = VoteType::Pause,
+ 0 => Kick(Ascii),
+ 1 => Map(Option<Ascii>),
+ 2 => Pause(),
+ 3 => NewSeed(),
+ 4 => HedgehogsPerTeam(u8)
+ )
+ })
+ .boxed()
+ }
+
+ type Strategy = BoxedStrategy<VoteType>;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/tests/parser.rs Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,54 @@
+use hedgewars_network_protocol::{
+ parser::message,
+ types::GameCfg,
+ {messages::HwProtocolMessage::*, parser::HwProtocolError},
+};
+
+#[test]
+fn parse_test() {
+ assert_eq!(message(b"PING\n\n"), Ok((&b""[..], Ping)));
+ assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame)));
+ assert_eq!(
+ message(b"NICK\nit's me\n\n"),
+ Ok((&b""[..], Nick("it's me".to_string())))
+ );
+ assert_eq!(message(b"PROTO\n51\n\n"), Ok((&b""[..], Proto(51))));
+ assert_eq!(
+ message(b"QUIT\nbye-bye\n\n"),
+ Ok((&b""[..], Quit(Some("bye-bye".to_string()))))
+ );
+ assert_eq!(message(b"QUIT\n\n"), Ok((&b""[..], Quit(None))));
+ assert_eq!(
+ message(b"CMD\nwatch 49471\n\n"),
+ Ok((&b""[..], Watch(49471)))
+ );
+ assert_eq!(
+ message(b"BAN\nme\nbad\n77\n\n"),
+ Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77)))
+ );
+
+ assert_eq!(message(b"CMD\nPART\n\n"), Ok((&b""[..], Part(None))));
+ assert_eq!(
+ message(b"CMD\nPART _msg_\n\n"),
+ Ok((&b""[..], Part(Some("_msg_".to_string()))))
+ );
+
+ assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![]))));
+ assert_eq!(
+ message(b"CMD\nRND A B\n\n"),
+ Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")])))
+ );
+
+ assert_eq!(
+ message(b"CFG\nSCHEME\na\nA\n\n"),
+ Ok((
+ &b""[..],
+ Cfg(GameCfg::Scheme("a".to_string(), vec!["A".to_string()]))
+ ))
+ );
+
+ assert_eq!(
+ message(b"QUIT\n1\n2\n\n"),
+ Err(nom::Err::Error(HwProtocolError::new()))
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-network-protocol/tests/test.rs Wed Jun 23 23:41:51 2021 +0200
@@ -0,0 +1,83 @@
+use proptest::{
+ arbitrary::any,
+ proptest,
+ strategy::{BoxedStrategy, Just, Strategy},
+};
+
+use hedgewars_network_protocol::messages::{HwProtocolMessage, HwProtocolMessage::*};
+use hedgewars_network_protocol::parser::message;
+use hedgewars_network_protocol::types::{GameCfg, ServerVar, TeamInfo, VoteType};
+
+use hedgewars_network_protocol::types::testing::*;
+use hedgewars_network_protocol::{proto_msg_case, proto_msg_match};
+
+pub fn gen_proto_msg() -> BoxedStrategy<HwProtocolMessage> where {
+ let res = (0..=55).no_shrink().prop_flat_map(|i| {
+ proto_msg_match!(i, def = Ping,
+ 0 => Ping(),
+ 1 => Pong(),
+ 2 => Quit(Option<Ascii>),
+ 4 => Global(Ascii),
+ 5 => Watch(u32),
+ 6 => ToggleServerRegisteredOnly(),
+ 7 => SuperPower(),
+ 8 => Info(Ascii),
+ 9 => Nick(Ascii),
+ 10 => Proto(u16),
+ 11 => Password(Ascii, Ascii),
+ 12 => Checker(u16, Ascii, Ascii),
+ 13 => List(),
+ 14 => Chat(Ascii),
+ 15 => CreateRoom(Ascii, Option<Ascii>),
+ 16 => JoinRoom(Ascii, Option<Ascii>),
+ 17 => Follow(Ascii),
+ 18 => Rnd(Vec<Ascii>),
+ 19 => Kick(Ascii),
+ 20 => Ban(Ascii, Ascii, u32),
+ 21 => BanIp(Ascii, Ascii, u32),
+ 22 => BanNick(Ascii, Ascii, u32),
+ 23 => BanList(),
+ 24 => Unban(Ascii),
+ 25 => SetServerVar(ServerVar),
+ 26 => GetServerVar(),
+ 27 => RestartServer(),
+ 28 => Stats(),
+ 29 => Part(Option<Ascii>),
+ 30 => Cfg(GameCfg),
+ 31 => AddTeam(Box<TeamInfo>),
+ 32 => RemoveTeam(Ascii),
+ 33 => SetHedgehogsNumber(Ascii, u8),
+ 34 => SetTeamColor(Ascii, u8),
+ 35 => ToggleReady(),
+ 36 => StartGame(),
+ 37 => EngineMessage(Ascii),
+ 38 => RoundFinished(),
+ 39 => ToggleRestrictJoin(),
+ 40 => ToggleRestrictTeams(),
+ 41 => ToggleRegisteredOnly(),
+ 42 => RoomName(Ascii),
+ 43 => Delegate(Ascii),
+ 44 => TeamChat(Ascii),
+ 45 => MaxTeams(u8),
+ 46 => Fix(),
+ 47 => Unfix(),
+ 48 => Greeting(Option<Ascii>),
+ 49 => CallVote(Option<VoteType>),
+ 50 => Vote(bool),
+ 51 => ForceVote(bool),
+ 52 => Save(Ascii, Ascii),
+ 53 => Delete(Ascii),
+ 54 => SaveRoom(Ascii),
+ 55 => LoadRoom(Ascii)
+ )
+ });
+ res.boxed()
+}
+
+proptest! {
+ #[test]
+ fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
+ println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
+ assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
+ }
+}
--- a/rust/hedgewars-server/Cargo.toml Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/Cargo.toml Wed Jun 23 23:41:51 2021 +0200
@@ -1,7 +1,7 @@
[package]
edition = "2018"
name = "hedgewars-server"
-version = "0.0.1"
+version = "0.9.0"
authors = [ "Andrey Korotaev <a.korotaev@hedgewars.org>" ]
[features]
@@ -26,6 +26,7 @@
serde_derive = "1.0"
openssl = { version = "0.10", optional = true }
mysql = { version = "15.0", optional = true }
+hedgewars-network-protocol = { path = "../hedgewars-network-protocol" }
[dev-dependencies]
proptest = "1.0"
--- a/rust/hedgewars-server/src/core/room.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/core/room.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,10 +1,11 @@
use super::{
client::HwClient,
- types::{
- ClientId, GameCfg, GameCfg::*, RoomConfig, RoomId, TeamInfo, Voting, MAX_HEDGEHOGS_PER_TEAM,
- },
+ types::{ClientId, RoomId, Voting},
};
use bitflags::*;
+use hedgewars_network_protocol::types::{
+ GameCfg, GameCfg::*, RoomConfig, TeamInfo, MAX_HEDGEHOGS_PER_TEAM,
+};
use serde::{Deserialize, Serialize};
use serde_derive::{Deserialize, Serialize};
use serde_yaml;
--- a/rust/hedgewars-server/src/core/server.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/core/server.rs Wed Jun 23 23:41:51 2021 +0200
@@ -3,9 +3,10 @@
client::HwClient,
indexslab::IndexSlab,
room::HwRoom,
- types::{ClientId, GameCfg, RoomId, ServerVar, TeamInfo, Vote, VoteType, Voting},
+ types::{ClientId, RoomId, Voting},
};
use crate::utils;
+use hedgewars_network_protocol::types::{GameCfg, ServerVar, TeamInfo, Vote, VoteType};
use bitflags::*;
use log::*;
--- a/rust/hedgewars-server/src/core/types.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/core/types.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,158 +1,9 @@
+use hedgewars_network_protocol::types::{RoomConfig, TeamInfo, VoteType};
use serde_derive::{Deserialize, Serialize};
pub type ClientId = usize;
pub type RoomId = usize;
-pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8;
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum ServerVar {
- MOTDNew(String),
- MOTDOld(String),
- LatestProto(u16),
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum GameCfg {
- FeatureSize(u32),
- MapType(String),
- MapGenerator(u32),
- MazeSize(u32),
- Seed(String),
- Template(u32),
-
- Ammo(String, Option<String>),
- Scheme(String, Vec<String>),
- Script(String),
- Theme(String),
- DrawnMap(String),
-}
-
-#[derive(PartialEq, Eq, Clone, Debug, Default)]
-pub struct TeamInfo {
- pub owner: String,
- pub name: String,
- pub color: u8,
- pub grave: String,
- pub fort: String,
- pub voice_pack: String,
- pub flag: String,
- pub difficulty: u8,
- pub hedgehogs_number: u8,
- pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize],
-}
-
-#[derive(PartialEq, Eq, Clone, Debug, Default)]
-pub struct HedgehogInfo {
- pub name: String,
- pub hat: String,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub struct Ammo {
- pub name: String,
- pub settings: Option<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub struct Scheme {
- pub name: String,
- pub settings: Vec<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub struct RoomConfig {
- pub feature_size: u32,
- pub map_type: String,
- pub map_generator: u32,
- pub maze_size: u32,
- pub seed: String,
- pub template: u32,
-
- pub ammo: Ammo,
- pub scheme: Scheme,
- pub script: String,
- pub theme: String,
- pub drawn_map: Option<String>,
-}
-
-impl RoomConfig {
- pub fn new() -> RoomConfig {
- RoomConfig {
- feature_size: 12,
- map_type: "+rnd+".to_string(),
- map_generator: 0,
- maze_size: 0,
- seed: "seed".to_string(),
- template: 0,
-
- ammo: Ammo {
- name: "Default".to_string(),
- settings: None,
- },
- scheme: Scheme {
- name: "Default".to_string(),
- settings: Vec::new(),
- },
- script: "Normal".to_string(),
- theme: "\u{1f994}".to_string(),
- drawn_map: None,
- }
- }
-
- pub fn set_config(&mut self, cfg: GameCfg) {
- match cfg {
- GameCfg::FeatureSize(s) => self.feature_size = s,
- GameCfg::MapType(t) => self.map_type = t,
- GameCfg::MapGenerator(g) => self.map_generator = g,
- GameCfg::MazeSize(s) => self.maze_size = s,
- GameCfg::Seed(s) => self.seed = s,
- GameCfg::Template(t) => self.template = t,
-
- GameCfg::Ammo(n, s) => {
- self.ammo = Ammo {
- name: n,
- settings: s,
- }
- }
- GameCfg::Scheme(n, s) => {
- self.scheme = Scheme {
- name: n,
- settings: s,
- }
- }
- GameCfg::Script(s) => self.script = s,
- GameCfg::Theme(t) => self.theme = t,
- GameCfg::DrawnMap(m) => self.drawn_map = Some(m),
- };
- }
-
- pub fn to_map_config(&self) -> Vec<String> {
- vec![
- self.feature_size.to_string(),
- self.map_type.to_string(),
- self.map_generator.to_string(),
- self.maze_size.to_string(),
- self.seed.to_string(),
- self.template.to_string(),
- ]
- }
-
- pub fn to_game_config(&self) -> Vec<GameCfg> {
- use GameCfg::*;
- let mut v = vec![
- Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()),
- Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()),
- Script(self.script.to_string()),
- Theme(self.theme.to_string()),
- ];
- if let Some(ref m) = self.drawn_map {
- v.push(DrawnMap(m.to_string()))
- }
- v
- }
-}
-
#[derive(Debug)]
pub struct Replay {
pub config: RoomConfig,
@@ -160,20 +11,6 @@
pub message_log: Vec<String>,
}
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum VoteType {
- Kick(String),
- Map(Option<String>),
- Pause,
- NewSeed,
- HedgehogsPerTeam(u8),
-}
-
-pub struct Vote {
- pub is_pro: bool,
- pub is_forced: bool,
-}
-
#[derive(Clone, Debug)]
pub struct Voting {
pub ttl: u32,
--- a/rust/hedgewars-server/src/handlers.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers.rs Wed Jun 23 23:41:51 2021 +0200
@@ -10,19 +10,24 @@
inanteroom::LoginResult,
strings::*,
};
+use crate::handlers::actions::ToPendingMessage;
use crate::{
core::{
anteroom::HwAnteroom,
room::RoomSave,
server::HwServer,
- types::{ClientId, GameCfg, Replay, RoomId, TeamInfo},
+ types::{ClientId, Replay, RoomId},
},
- protocol::messages::{
+ utils,
+};
+use hedgewars_network_protocol::{
+ messages::{
global_chat, server_chat, HwProtocolMessage, HwProtocolMessage::EngineMessage,
HwServerMessage, HwServerMessage::*,
},
- utils,
+ types::{GameCfg, TeamInfo},
};
+
use base64::encode;
use log::*;
use rand::{thread_rng, RngCore};
--- a/rust/hedgewars-server/src/handlers/actions.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/actions.rs Wed Jun 23 23:41:51 2021 +0200
@@ -4,11 +4,14 @@
room::HwRoom,
room::{GameInfo, RoomFlags},
server::HwServer,
- types::{ClientId, GameCfg, RoomId, VoteType},
+ types::{ClientId, RoomId},
},
- protocol::messages::{server_chat, HwProtocolMessage, HwServerMessage, HwServerMessage::*},
utils::to_engine_msg,
};
+use hedgewars_network_protocol::{
+ messages::{server_chat, HwProtocolMessage, HwServerMessage, HwServerMessage::*},
+ types::{GameCfg, VoteType},
+};
use rand::{distributions::Uniform, thread_rng, Rng};
use std::{io, io::Write, iter::once, mem::replace};
@@ -101,20 +104,28 @@
}
}
-impl HwServerMessage {
- pub fn send(self, client_id: ClientId) -> PendingMessage {
+pub trait ToPendingMessage {
+ fn send(self, client_id: ClientId) -> PendingMessage;
+ fn send_many(self, client_ids: Vec<ClientId>) -> PendingMessage;
+ fn send_self(self) -> PendingMessage;
+ fn send_all(self) -> PendingMessage;
+ fn send_to_destination(self, destination: Destination) -> PendingMessage;
+}
+
+impl ToPendingMessage for HwServerMessage {
+ fn send(self, client_id: ClientId) -> PendingMessage {
PendingMessage::send(self, client_id)
}
- pub fn send_many(self, client_ids: Vec<ClientId>) -> PendingMessage {
+ fn send_many(self, client_ids: Vec<ClientId>) -> PendingMessage {
PendingMessage::send_many(self, client_ids)
}
- pub fn send_self(self) -> PendingMessage {
+ fn send_self(self) -> PendingMessage {
PendingMessage::send_self(self)
}
- pub fn send_all(self) -> PendingMessage {
+ fn send_all(self) -> PendingMessage {
PendingMessage::send_all(self)
}
- pub fn send_to_destination(self, destination: Destination) -> PendingMessage {
+ fn send_to_destination(self, destination: Destination) -> PendingMessage {
PendingMessage {
destination,
message: self,
--- a/rust/hedgewars-server/src/handlers/checker.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/checker.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,10 +1,8 @@
use log::*;
use mio;
-use crate::{
- core::{server::HwServer, types::ClientId},
- protocol::messages::HwProtocolMessage,
-};
+use crate::core::{server::HwServer, types::ClientId};
+use hedgewars_network_protocol::messages::HwProtocolMessage;
pub fn handle(_server: &mut HwServer, _client_id: ClientId, message: HwProtocolMessage) {
match message {
--- a/rust/hedgewars-server/src/handlers/common.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/common.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,3 +1,8 @@
+use super::{
+ actions::{Destination, DestinationGroup},
+ Response,
+};
+use crate::handlers::actions::ToPendingMessage;
use crate::{
core::{
client::HwClient,
@@ -6,23 +11,19 @@
EndGameResult, HwRoomControl, HwServer, JoinRoomError, LeaveRoomResult, StartGameError,
VoteError, VoteResult,
},
- types::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType, MAX_HEDGEHOGS_PER_TEAM},
+ types::{ClientId, RoomId},
},
- protocol::messages::{
+ utils::to_engine_msg,
+};
+use hedgewars_network_protocol::{
+ messages::{
add_flags, remove_flags, server_chat,
HwProtocolMessage::{self, Rnd},
HwServerMessage::{self, *},
ProtocolFlags as Flags,
},
- utils::to_engine_msg,
+ types::{GameCfg, RoomConfig, TeamInfo, Vote, VoteType, MAX_HEDGEHOGS_PER_TEAM},
};
-
-use super::{
- actions::{Destination, DestinationGroup},
- Response,
-};
-
-use crate::core::types::RoomConfig;
use rand::{self, seq::SliceRandom, thread_rng, Rng};
use std::{iter::once, mem::replace};
@@ -644,7 +645,7 @@
mod tests {
use super::*;
use crate::handlers::actions::PendingMessage;
- use crate::protocol::messages::HwServerMessage::ChatMsg;
+ use hedgewars_network_protocol::messages::HwServerMessage::ChatMsg;
fn reply2string(r: HwServerMessage) -> String {
match r {
--- a/rust/hedgewars-server/src/handlers/inanteroom.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/inanteroom.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,6 +1,7 @@
use mio;
use super::strings::*;
+use crate::handlers::actions::ToPendingMessage;
use crate::{
core::{
anteroom::{HwAnteroom, HwAnteroomClient},
@@ -8,10 +9,11 @@
server::HwServer,
types::ClientId,
},
- protocol::messages::{HwProtocolMessage, HwProtocolMessage::LoadRoom, HwServerMessage::*},
utils::is_name_illegal,
};
-
+use hedgewars_network_protocol::messages::{
+ HwProtocolMessage, HwProtocolMessage::LoadRoom, HwServerMessage::*,
+};
use log::*;
#[cfg(feature = "official-server")]
use openssl::sha::sha1;
--- a/rust/hedgewars-server/src/handlers/inlobby.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/inlobby.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,15 +1,19 @@
use super::{common::rnd_reply, strings::*};
+use crate::handlers::actions::ToPendingMessage;
use crate::{
core::{
client::HwClient,
server::{AccessError, CreateRoomError, HwServer, JoinRoomError},
- types::{ClientId, ServerVar},
+ types::ClientId,
},
- protocol::messages::{
+ utils::is_name_illegal,
+};
+use hedgewars_network_protocol::{
+ messages::{
add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
ProtocolFlags as Flags,
},
- utils::is_name_illegal,
+ types::ServerVar,
};
use log::*;
use std::{collections::HashSet, convert::identity};
@@ -20,7 +24,7 @@
response: &mut super::Response,
message: HwProtocolMessage,
) {
- use crate::protocol::messages::HwProtocolMessage::*;
+ use hedgewars_network_protocol::messages::HwProtocolMessage::*;
match message {
CreateRoom(name, password) => match server.create_room(client_id, name, password) {
--- a/rust/hedgewars-server/src/handlers/inroom.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/handlers/inroom.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,6 +1,7 @@
use super::{common::rnd_reply, strings::*};
use crate::core::room::GameInfo;
use crate::core::server::{AddTeamError, SetTeamCountError};
+use crate::handlers::actions::ToPendingMessage;
use crate::{
core::{
room::{HwRoom, RoomFlags, MAX_TEAMS_IN_ROOM},
@@ -8,16 +9,19 @@
ChangeMasterError, ChangeMasterResult, HwRoomControl, HwServer, LeaveRoomResult,
ModifyTeamError, StartGameError,
},
- types,
- types::{ClientId, GameCfg, RoomId, VoteType, Voting, MAX_HEDGEHOGS_PER_TEAM},
- },
- protocol::messages::{
- add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
- ProtocolFlags as Flags,
+ types::{ClientId, RoomId, Voting},
},
utils::{is_name_illegal, to_engine_msg},
};
use base64::{decode, encode};
+use hedgewars_network_protocol::{
+ messages::{
+ add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
+ ProtocolFlags as Flags,
+ },
+ types,
+ types::{GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM},
+};
use log::*;
use std::{cmp::min, iter::once, mem::swap};
@@ -87,7 +91,7 @@
}
fn room_message_flag(msg: &HwProtocolMessage) -> RoomFlags {
- use crate::protocol::messages::HwProtocolMessage::*;
+ use hedgewars_network_protocol::messages::HwProtocolMessage::*;
match msg {
ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN,
ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD,
@@ -104,7 +108,7 @@
let (client, room) = room_control.get();
let (client_id, room_id) = (client.id, room.id);
- use crate::protocol::messages::HwProtocolMessage::*;
+ use hedgewars_network_protocol::messages::HwProtocolMessage::*;
match message {
Part(msg) => {
let msg = match msg {
--- a/rust/hedgewars-server/src/protocol.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/protocol.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,13 +1,11 @@
-use self::parser::message;
+use hedgewars_network_protocol::{
+ messages::HwProtocolMessage,
+ parser::{malformed_message, message},
+};
use log::*;
use netbuf;
use std::io::{Read, Result};
-pub mod messages;
-mod parser;
-#[cfg(test)]
-pub mod test;
-
pub struct ProtocolDecoder {
buf: netbuf::Buf,
is_recovering: bool,
@@ -22,7 +20,7 @@
}
fn recover(&mut self) -> bool {
- self.is_recovering = match parser::malformed_message(&self.buf[..]) {
+ self.is_recovering = match malformed_message(&self.buf[..]) {
Ok((tail, ())) => {
let length = tail.len();
self.buf.consume(self.buf.len() - length);
@@ -44,11 +42,11 @@
Ok(count)
}
- pub fn extract_messages(&mut self) -> Vec<messages::HwProtocolMessage> {
+ pub fn extract_messages(&mut self) -> Vec<HwProtocolMessage> {
let mut messages = vec![];
if !self.is_recovering {
while !self.buf.is_empty() {
- match parser::message(&self.buf[..]) {
+ match message(&self.buf[..]) {
Ok((tail, message)) => {
messages.push(message);
let length = tail.len();
--- a/rust/hedgewars-server/src/protocol/messages.rs Wed Jun 23 15:32:48 2021 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,425 +0,0 @@
-use crate::core::types::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType};
-use std::{convert::From, iter::once, ops};
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum HwProtocolMessage {
- // common messages
- Ping,
- Pong,
- Quit(Option<String>),
- Global(String),
- Watch(u32),
- ToggleServerRegisteredOnly,
- SuperPower,
- Info(String),
- // anteroom messages
- Nick(String),
- Proto(u16),
- Password(String, String),
- Checker(u16, String, String),
- // lobby messages
- List,
- Chat(String),
- CreateRoom(String, Option<String>),
- JoinRoom(String, Option<String>),
- Follow(String),
- Rnd(Vec<String>),
- Kick(String),
- Ban(String, String, u32),
- BanIp(String, String, u32),
- BanNick(String, String, u32),
- BanList,
- Unban(String),
- SetServerVar(ServerVar),
- GetServerVar,
- RestartServer,
- Stats,
- // room messages
- Part(Option<String>),
- Cfg(GameCfg),
- AddTeam(Box<TeamInfo>),
- RemoveTeam(String),
- SetHedgehogsNumber(String, u8),
- SetTeamColor(String, u8),
- ToggleReady,
- StartGame,
- EngineMessage(String),
- RoundFinished,
- ToggleRestrictJoin,
- ToggleRestrictTeams,
- ToggleRegisteredOnly,
- RoomName(String),
- Delegate(String),
- TeamChat(String),
- MaxTeams(u8),
- Fix,
- Unfix,
- Greeting(Option<String>),
- CallVote(Option<VoteType>),
- Vote(bool),
- ForceVote(bool),
- Save(String, String),
- Delete(String),
- SaveRoom(String),
- LoadRoom(String),
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum ProtocolFlags {
- InRoom,
- RoomMaster,
- Ready,
- InGame,
- Registered,
- Admin,
- Contributor,
-}
-
-impl ProtocolFlags {
- #[inline]
- fn flag_char(&self) -> char {
- match self {
- ProtocolFlags::InRoom => 'i',
- ProtocolFlags::RoomMaster => 'h',
- ProtocolFlags::Ready => 'r',
- ProtocolFlags::InGame => 'g',
- ProtocolFlags::Registered => 'u',
- ProtocolFlags::Admin => 'a',
- ProtocolFlags::Contributor => 'c',
- }
- }
-
- #[inline]
- fn format(prefix: char, flags: &[ProtocolFlags]) -> String {
- once(prefix)
- .chain(flags.iter().map(|f| f.flag_char()))
- .collect()
- }
-}
-
-#[inline]
-pub fn add_flags(flags: &[ProtocolFlags]) -> String {
- ProtocolFlags::format('+', flags)
-}
-
-#[inline]
-pub fn remove_flags(flags: &[ProtocolFlags]) -> String {
- ProtocolFlags::format('-', flags)
-}
-
-#[derive(Debug)]
-pub enum HwServerMessage {
- Connected(u32),
- Redirect(u16),
-
- Ping,
- Pong,
- Bye(String),
-
- Nick(String),
- Proto(u16),
- AskPassword(String),
- ServerAuth(String),
- LogonPassed,
-
- LobbyLeft(String, String),
- LobbyJoined(Vec<String>),
- ChatMsg { nick: String, msg: String },
- ClientFlags(String, Vec<String>),
- Rooms(Vec<String>),
- RoomAdd(Vec<String>),
- RoomJoined(Vec<String>),
- RoomLeft(String, String),
- RoomRemove(String),
- RoomUpdated(String, Vec<String>),
- Joining(String),
- TeamAdd(Vec<String>),
- TeamRemove(String),
- TeamAccepted(String),
- TeamColor(String, u8),
- HedgehogsNumber(String, u8),
- ConfigEntry(String, Vec<String>),
- Kicked,
- RunGame,
- ForwardEngineMessage(Vec<String>),
- RoundFinished,
- ReplayStart,
-
- Info(Vec<String>),
- ServerMessage(String),
- ServerVars(Vec<String>),
- Notice(String),
- Warning(String),
- Error(String),
- Unreachable,
-
- //Deprecated messages
- LegacyReady(bool, Vec<String>),
-}
-
-fn special_chat(nick: &str, msg: String) -> HwServerMessage {
- HwServerMessage::ChatMsg {
- nick: nick.to_string(),
- msg,
- }
-}
-
-pub fn server_chat(msg: String) -> HwServerMessage {
- special_chat("[server]", msg)
-}
-
-pub fn global_chat(msg: String) -> HwServerMessage {
- special_chat("(global notice)", msg)
-}
-
-impl ServerVar {
- pub fn to_protocol(&self) -> Vec<String> {
- use ServerVar::*;
- match self {
- MOTDNew(s) => vec!["MOTD_NEW".to_string(), s.clone()],
- MOTDOld(s) => vec!["MOTD_OLD".to_string(), s.clone()],
- LatestProto(n) => vec!["LATEST_PROTO".to_string(), n.to_string()],
- }
- }
-}
-
-impl VoteType {
- pub fn to_protocol(&self) -> Vec<String> {
- use VoteType::*;
- match self {
- Kick(nick) => vec!["KICK".to_string(), nick.clone()],
- Map(None) => vec!["MAP".to_string()],
- Map(Some(name)) => vec!["MAP".to_string(), name.clone()],
- Pause => vec!["PAUSE".to_string()],
- NewSeed => vec!["NEWSEED".to_string()],
- HedgehogsPerTeam(count) => vec!["HEDGEHOGS".to_string(), count.to_string()],
- }
- }
-}
-
-impl GameCfg {
- pub fn to_protocol(&self) -> (String, Vec<String>) {
- use GameCfg::*;
- match self {
- FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]),
- MapType(t) => ("MAP".to_string(), vec![t.to_string()]),
- MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]),
- MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]),
- Seed(s) => ("SEED".to_string(), vec![s.to_string()]),
- Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]),
-
- Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]),
- Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]),
- Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]),
- Scheme(n, s) => ("SCHEME".to_string(), {
- let mut v = vec![n.to_string()];
- v.extend(s.clone());
- v
- }),
- Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]),
- Theme(t) => ("THEME".to_string(), vec![t.to_string()]),
- DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()]),
- }
- }
-
- pub fn to_server_msg(&self) -> HwServerMessage {
- use self::HwServerMessage::ConfigEntry;
- let (name, args) = self.to_protocol();
- HwServerMessage::ConfigEntry(name, args)
- }
-}
-
-impl TeamInfo {
- pub fn to_protocol(&self) -> Vec<String> {
- let mut info = vec![
- self.name.clone(),
- self.grave.clone(),
- self.fort.clone(),
- self.voice_pack.clone(),
- self.flag.clone(),
- self.owner.clone(),
- self.difficulty.to_string(),
- ];
- let hogs = self
- .hedgehogs
- .iter()
- .flat_map(|h| once(h.name.clone()).chain(once(h.hat.clone())));
- info.extend(hogs);
- info
- }
-}
-
-macro_rules! const_braces {
- ($e: expr) => {
- "{}\n"
- };
-}
-
-macro_rules! msg {
- [$($part: expr),*] => {
- format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
- };
-}
-
-impl HwProtocolMessage {
- /** Converts the message to a raw `String`, which can be sent over the network.
- *
- * This is the inverse of the `message` parser.
- */
- #[cfg(test)]
- pub(crate) fn to_raw_protocol(&self) -> String {
- use self::HwProtocolMessage::*;
- match self {
- Ping => msg!["PING"],
- Pong => msg!["PONG"],
- Quit(None) => msg!["QUIT"],
- Quit(Some(msg)) => msg!["QUIT", msg],
- Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)],
- Watch(name) => msg!["CMD", format!("WATCH {}", name)],
- ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
- SuperPower => msg!["CMD", "SUPER_POWER"],
- Info(info) => msg!["CMD", format!("INFO {}", info)],
- Nick(nick) => msg!("NICK", nick),
- Proto(version) => msg!["PROTO", version],
- Password(p, s) => msg!["PASSWORD", p, s],
- Checker(i, n, p) => msg!["CHECKER", i, n, p],
- List => msg!["LIST"],
- Chat(msg) => msg!["CHAT", msg],
- CreateRoom(name, None) => msg!["CREATE_ROOM", name],
- CreateRoom(name, Some(password)) => msg!["CREATE_ROOM", name, password],
- JoinRoom(name, None) => msg!["JOIN_ROOM", name],
- JoinRoom(name, Some(password)) => msg!["JOIN_ROOM", name, password],
- Follow(name) => msg!["FOLLOW", name],
- Rnd(args) => {
- if args.is_empty() {
- msg!["CMD", "RND"]
- } else {
- msg!["CMD", format!("RND {}", args.join(" "))]
- }
- }
- Kick(name) => msg!["KICK", name],
- Ban(name, reason, time) => msg!["BAN", name, reason, time],
- BanIp(ip, reason, time) => msg!["BAN_IP", ip, reason, time],
- BanNick(nick, reason, time) => msg!("BAN_NICK", nick, reason, time),
- BanList => msg!["BANLIST"],
- Unban(name) => msg!["UNBAN", name],
- SetServerVar(var) => construct_message(&["SET_SERVER_VAR"], &var.to_protocol()),
- GetServerVar => msg!["GET_SERVER_VAR"],
- RestartServer => msg!["CMD", "RESTART_SERVER YES"],
- Stats => msg!["CMD", "STATS"],
- Part(None) => msg!["PART"],
- Part(Some(msg)) => msg!["PART", msg],
- Cfg(config) => {
- let (name, args) = config.to_protocol();
- msg!["CFG", name, args.join("\n")]
- }
- AddTeam(info) => msg![
- "ADD_TEAM",
- info.name,
- info.color,
- info.grave,
- info.fort,
- info.voice_pack,
- info.flag,
- info.difficulty,
- info.hedgehogs
- .iter()
- .flat_map(|h| [&h.name[..], &h.hat[..]])
- .collect::<Vec<_>>()
- .join("\n")
- ],
- RemoveTeam(name) => msg!["REMOVE_TEAM", name],
- SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number],
- SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color],
- ToggleReady => msg!["TOGGLE_READY"],
- StartGame => msg!["START_GAME"],
- EngineMessage(msg) => msg!["EM", msg],
- RoundFinished => msg!["ROUNDFINISHED"],
- ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"],
- ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"],
- ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"],
- RoomName(name) => msg!["ROOM_NAME", name],
- Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)],
- TeamChat(msg) => msg!["TEAMCHAT", msg],
- MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)],
- Fix => msg!["CMD", "FIX"],
- Unfix => msg!["CMD", "UNFIX"],
- Greeting(None) => msg!["CMD", "GREETING"],
- Greeting(Some(msg)) => msg!["CMD", format!("GREETING {}", msg)],
- CallVote(None) => msg!["CMD", "CALLVOTE"],
- CallVote(Some(vote)) => {
- msg!["CMD", format!("CALLVOTE {}", &vote.to_protocol().join(" "))]
- }
- Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg { "YES" } else { "NO" })],
- ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg { "YES" } else { "NO" })],
- Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)],
- Delete(name) => msg!["CMD", format!("DELETE {}", name)],
- SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)],
- LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)],
- _ => panic!("Protocol message not yet implemented"),
- }
- }
-}
-
-fn construct_message(header: &[&str], msg: &[String]) -> String {
- let mut v: Vec<_> = header.iter().cloned().collect();
- v.extend(msg.iter().map(|s| &s[..]));
- v.push("\n");
- v.join("\n")
-}
-
-impl HwServerMessage {
- pub fn to_raw_protocol(&self) -> String {
- use self::HwServerMessage::*;
- match self {
- Ping => msg!["PING"],
- Pong => msg!["PONG"],
- Connected(protocol_version) => msg![
- "CONNECTED",
- "Hedgewars server https://www.hedgewars.org/",
- protocol_version
- ],
- Redirect(port) => msg!["REDIRECT", port],
- Bye(msg) => msg!["BYE", msg],
- Nick(nick) => msg!["NICK", nick],
- Proto(proto) => msg!["PROTO", proto],
- AskPassword(salt) => msg!["ASKPASSWORD", salt],
- ServerAuth(hash) => msg!["SERVER_AUTH", hash],
- LogonPassed => msg!["LOGONPASSED"],
- LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg],
- LobbyJoined(nicks) => construct_message(&["LOBBY:JOINED"], &nicks),
- ClientFlags(flags, nicks) => construct_message(&["CLIENT_FLAGS", flags], &nicks),
- Rooms(info) => construct_message(&["ROOMS"], &info),
- RoomAdd(info) => construct_message(&["ROOM", "ADD"], &info),
- RoomJoined(nicks) => construct_message(&["JOINED"], &nicks),
- RoomLeft(nick, msg) => msg!["LEFT", nick, msg],
- RoomRemove(name) => msg!["ROOM", "DEL", name],
- RoomUpdated(name, info) => construct_message(&["ROOM", "UPD", name], &info),
- Joining(name) => msg!["JOINING", name],
- TeamAdd(info) => construct_message(&["ADD_TEAM"], &info),
- TeamRemove(name) => msg!["REMOVE_TEAM", name],
- TeamAccepted(name) => msg!["TEAM_ACCEPTED", name],
- TeamColor(name, color) => msg!["TEAM_COLOR", name, color],
- HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
- ConfigEntry(name, values) => construct_message(&["CFG", name], &values),
- Kicked => msg!["KICKED"],
- RunGame => msg!["RUN_GAME"],
- ForwardEngineMessage(em) => construct_message(&["EM"], &em),
- RoundFinished => msg!["ROUND_FINISHED"],
- ChatMsg { nick, msg } => msg!["CHAT", nick, msg],
- Info(info) => construct_message(&["INFO"], &info),
- ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
- ServerVars(vars) => construct_message(&["SERVER_VARS"], &vars),
- Notice(msg) => msg!["NOTICE", msg],
- Warning(msg) => msg!["WARNING", msg],
- Error(msg) => msg!["ERROR", msg],
- ReplayStart => msg!["REPLAY_START"],
-
- LegacyReady(is_ready, nicks) => {
- construct_message(&[if *is_ready { "READY" } else { "NOT_READY" }], &nicks)
- }
-
- _ => msg!["ERROR", "UNIMPLEMENTED"],
- }
- }
-}
--- a/rust/hedgewars-server/src/protocol/parser.rs Wed Jun 23 15:32:48 2021 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,576 +0,0 @@
-/** The parsers for the chat and multiplayer protocol. The main parser is `message`.
- * # Protocol
- * All messages consist of `\n`-separated strings. The end of a message is
- * indicated by a double newline - `\n\n`.
- *
- * For example, a nullary command like PING will be actually sent as `PING\n\n`.
- * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
- */
-use nom::{
- branch::alt,
- bytes::complete::{tag, tag_no_case, take_until, take_while},
- character::complete::{newline, not_line_ending},
- combinator::{map, peek},
- error::{ErrorKind, ParseError},
- multi::separated_list0,
- sequence::{delimited, pair, preceded, terminated, tuple},
- Err, IResult,
-};
-
-use std::{
- num::ParseIntError,
- ops::Range,
- str,
- str::{FromStr, Utf8Error},
-};
-
-use super::messages::{HwProtocolMessage, HwProtocolMessage::*};
-use crate::core::types::{
- GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType, MAX_HEDGEHOGS_PER_TEAM,
-};
-
-#[derive(Debug, PartialEq)]
-pub struct HwProtocolError {}
-
-impl HwProtocolError {
- fn new() -> Self {
- HwProtocolError {}
- }
-}
-
-impl<I> ParseError<I> for HwProtocolError {
- fn from_error_kind(input: I, kind: ErrorKind) -> Self {
- HwProtocolError::new()
- }
-
- fn append(input: I, kind: ErrorKind, other: Self) -> Self {
- HwProtocolError::new()
- }
-}
-
-impl From<Utf8Error> for HwProtocolError {
- fn from(_: Utf8Error) -> Self {
- HwProtocolError::new()
- }
-}
-
-impl From<ParseIntError> for HwProtocolError {
- fn from(_: ParseIntError) -> Self {
- HwProtocolError::new()
- }
-}
-
-pub type HwResult<'a, O> = IResult<&'a [u8], O, HwProtocolError>;
-
-fn end_of_message(input: &[u8]) -> HwResult<&[u8]> {
- tag("\n\n")(input)
-}
-
-fn convert_utf8(input: &[u8]) -> HwResult<&str> {
- match str::from_utf8(input) {
- Ok(str) => Ok((b"", str)),
- Err(utf_err) => Result::Err(Err::Failure(utf_err.into())),
- }
-}
-
-fn convert_from_str<T>(str: &str) -> HwResult<T>
-where
- T: FromStr<Err = ParseIntError>,
-{
- match T::from_str(str) {
- Ok(x) => Ok((b"", x)),
- Err(format_err) => Result::Err(Err::Failure(format_err.into())),
- }
-}
-
-fn str_line(input: &[u8]) -> HwResult<&str> {
- let (i, text) = not_line_ending(input.clone())?;
- if i != input {
- Ok((i, convert_utf8(text)?.1))
- } else {
- Err(Err::Error(HwProtocolError::new()))
- }
-}
-
-fn a_line(input: &[u8]) -> HwResult<String> {
- map(str_line, String::from)(input)
-}
-
-fn cmd_arg(input: &[u8]) -> HwResult<String> {
- let delimiters = b" \n";
- let (i, str) = take_while(move |c| !delimiters.contains(&c))(input.clone())?;
- if i != input {
- Ok((i, convert_utf8(str)?.1.to_string()))
- } else {
- Err(Err::Error(HwProtocolError::new()))
- }
-}
-
-fn u8_line(input: &[u8]) -> HwResult<u8> {
- let (i, str) = str_line(input)?;
- Ok((i, convert_from_str(str)?.1))
-}
-
-fn u16_line(input: &[u8]) -> HwResult<u16> {
- let (i, str) = str_line(input)?;
- Ok((i, convert_from_str(str)?.1))
-}
-
-fn u32_line(input: &[u8]) -> HwResult<u32> {
- let (i, str) = str_line(input)?;
- Ok((i, convert_from_str(str)?.1))
-}
-
-fn yes_no_line(input: &[u8]) -> HwResult<bool> {
- alt((
- map(tag_no_case(b"YES"), |_| true),
- map(tag_no_case(b"NO"), |_| false),
- ))(input)
-}
-
-fn opt_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
- alt((
- map(peek(end_of_message), |_| None),
- map(preceded(tag("\n"), a_line), Some),
- ))(input)
-}
-
-fn spaces(input: &[u8]) -> HwResult<&[u8]> {
- preceded(tag(" "), take_while(|c| c == b' '))(input)
-}
-
-fn opt_space_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
- alt((
- map(peek(end_of_message), |_| None),
- map(preceded(spaces, a_line), Some),
- ))(input)
-}
-
-fn hedgehog_array(input: &[u8]) -> HwResult<[HedgehogInfo; 8]> {
- fn hedgehog_line(input: &[u8]) -> HwResult<HedgehogInfo> {
- map(
- tuple((terminated(a_line, newline), a_line)),
- |(name, hat)| HedgehogInfo { name, hat },
- )(input)
- }
-
- let (i, (h1, h2, h3, h4, h5, h6, h7, h8)) = tuple((
- terminated(hedgehog_line, newline),
- terminated(hedgehog_line, newline),
- terminated(hedgehog_line, newline),
- terminated(hedgehog_line, newline),
- terminated(hedgehog_line, newline),
- terminated(hedgehog_line, newline),
- terminated(hedgehog_line, newline),
- hedgehog_line,
- ))(input)?;
-
- Ok((i, [h1, h2, h3, h4, h5, h6, h7, h8]))
-}
-
-fn voting(input: &[u8]) -> HwResult<VoteType> {
- alt((
- map(tag_no_case("PAUSE"), |_| VoteType::Pause),
- map(tag_no_case("NEWSEED"), |_| VoteType::NewSeed),
- map(
- preceded(pair(tag_no_case("KICK"), spaces), a_line),
- VoteType::Kick,
- ),
- map(
- preceded(pair(tag_no_case("HEDGEHOGS"), spaces), u8_line),
- VoteType::HedgehogsPerTeam,
- ),
- map(preceded(tag_no_case("MAP"), opt_space_arg), VoteType::Map),
- ))(input)
-}
-
-fn no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- fn message<'a>(
- name: &'a str,
- msg: HwProtocolMessage,
- ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
- move |i| map(tag(name), |_| msg.clone())(i)
- }
-
- alt((
- message("PING", Ping),
- message("PONG", Pong),
- message("LIST", List),
- message("BANLIST", BanList),
- message("GET_SERVER_VAR", GetServerVar),
- message("TOGGLE_READY", ToggleReady),
- message("START_GAME", StartGame),
- message("TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
- message("TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
- message("TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
- ))(input)
-}
-
-fn single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- fn message<'a, T, F, G>(
- name: &'a str,
- parser: F,
- constructor: G,
- ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
- where
- F: Fn(&[u8]) -> HwResult<T>,
- G: Fn(T) -> HwProtocolMessage,
- {
- map(preceded(tag(name), parser), constructor)
- }
-
- alt((
- message("NICK\n", a_line, Nick),
- message("INFO\n", a_line, Info),
- message("CHAT\n", a_line, Chat),
- message("PART", opt_arg, Part),
- message("FOLLOW\n", a_line, Follow),
- message("KICK\n", a_line, Kick),
- message("UNBAN\n", a_line, Unban),
- message("EM\n", a_line, EngineMessage),
- message("TEAMCHAT\n", a_line, TeamChat),
- message("ROOM_NAME\n", a_line, RoomName),
- message("REMOVE_TEAM\n", a_line, RemoveTeam),
- message("ROUNDFINISHED", opt_arg, |_| RoundFinished),
- message("PROTO\n", u16_line, Proto),
- message("QUIT", opt_arg, Quit),
- ))(input)
-}
-
-fn cmd_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
- fn cmd_no_arg<'a>(
- name: &'a str,
- msg: HwProtocolMessage,
- ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
- move |i| map(tag_no_case(name), |_| msg.clone())(i)
- }
-
- fn cmd_single_arg<'a, T, F, G>(
- name: &'a str,
- parser: F,
- constructor: G,
- ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
- where
- F: Fn(&'a [u8]) -> HwResult<'a, T>,
- G: Fn(T) -> HwProtocolMessage,
- {
- map(
- preceded(pair(tag_no_case(name), spaces), parser),
- constructor,
- )
- }
-
- fn cmd_no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- alt((
- cmd_no_arg("STATS", Stats),
- cmd_no_arg("FIX", Fix),
- cmd_no_arg("UNFIX", Unfix),
- cmd_no_arg("REGISTERED_ONLY", ToggleServerRegisteredOnly),
- cmd_no_arg("SUPER_POWER", SuperPower),
- ))(input)
- }
-
- fn cmd_single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- alt((
- cmd_single_arg("RESTART_SERVER", |i| tag("YES")(i), |_| RestartServer),
- cmd_single_arg("DELEGATE", a_line, Delegate),
- cmd_single_arg("DELETE", a_line, Delete),
- cmd_single_arg("SAVEROOM", a_line, SaveRoom),
- cmd_single_arg("LOADROOM", a_line, LoadRoom),
- cmd_single_arg("GLOBAL", a_line, Global),
- cmd_single_arg("WATCH", u32_line, Watch),
- cmd_single_arg("VOTE", yes_no_line, Vote),
- cmd_single_arg("FORCE", yes_no_line, ForceVote),
- cmd_single_arg("INFO", a_line, Info),
- cmd_single_arg("MAXTEAMS", u8_line, MaxTeams),
- cmd_single_arg("CALLVOTE", voting, |v| CallVote(Some(v))),
- ))(input)
- }
-
- preceded(
- tag("CMD\n"),
- alt((
- cmd_no_arg_message,
- cmd_single_arg_message,
- map(tag_no_case("CALLVOTE"), |_| CallVote(None)),
- map(preceded(tag_no_case("GREETING"), opt_space_arg), Greeting),
- map(preceded(tag_no_case("PART"), opt_space_arg), Part),
- map(preceded(tag_no_case("QUIT"), opt_space_arg), Quit),
- map(
- preceded(
- tag_no_case("SAVE"),
- pair(preceded(spaces, cmd_arg), preceded(spaces, cmd_arg)),
- ),
- |(n, l)| Save(n, l),
- ),
- map(
- preceded(
- tag_no_case("RND"),
- alt((
- map(peek(end_of_message), |_| vec![]),
- preceded(spaces, separated_list0(spaces, cmd_arg)),
- )),
- ),
- Rnd,
- ),
- )),
- )(input)
-}
-
-fn config_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
- fn cfg_single_arg<'a, T, F, G>(
- name: &'a str,
- parser: F,
- constructor: G,
- ) -> impl FnMut(&'a [u8]) -> HwResult<'a, GameCfg>
- where
- F: Fn(&[u8]) -> HwResult<T>,
- G: Fn(T) -> GameCfg,
- {
- map(preceded(pair(tag(name), newline), parser), constructor)
- }
-
- let (i, cfg) = preceded(
- tag("CFG\n"),
- alt((
- cfg_single_arg("THEME", a_line, GameCfg::Theme),
- cfg_single_arg("SCRIPT", a_line, GameCfg::Script),
- cfg_single_arg("MAP", a_line, GameCfg::MapType),
- cfg_single_arg("MAPGEN", u32_line, GameCfg::MapGenerator),
- cfg_single_arg("MAZE_SIZE", u32_line, GameCfg::MazeSize),
- cfg_single_arg("TEMPLATE", u32_line, GameCfg::Template),
- cfg_single_arg("FEATURE_SIZE", u32_line, GameCfg::FeatureSize),
- cfg_single_arg("SEED", a_line, GameCfg::Seed),
- cfg_single_arg("DRAWNMAP", a_line, GameCfg::DrawnMap),
- preceded(pair(tag("AMMO"), newline), |i| {
- let (i, name) = a_line(i)?;
- let (i, value) = opt_arg(i)?;
- Ok((i, GameCfg::Ammo(name, value)))
- }),
- preceded(
- pair(tag("SCHEME"), newline),
- map(
- pair(
- a_line,
- alt((
- map(peek(end_of_message), |_| None),
- map(preceded(newline, separated_list0(newline, a_line)), Some),
- )),
- ),
- |(name, values)| GameCfg::Scheme(name, values.unwrap_or_default()),
- ),
- ),
- )),
- )(input)?;
- Ok((i, Cfg(cfg)))
-}
-
-fn server_var_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- map(
- preceded(
- tag("SET_SERVER_VAR\n"),
- alt((
- map(preceded(tag("MOTD_NEW\n"), a_line), ServerVar::MOTDNew),
- map(preceded(tag("MOTD_OLD\n"), a_line), ServerVar::MOTDOld),
- map(
- preceded(tag("LATEST_PROTO\n"), u16_line),
- ServerVar::LatestProto,
- ),
- )),
- ),
- SetServerVar,
- )(input)
-}
-
-fn complex_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- alt((
- preceded(
- pair(tag("PASSWORD"), newline),
- map(pair(terminated(a_line, newline), a_line), |(pass, salt)| {
- Password(pass, salt)
- }),
- ),
- preceded(
- pair(tag("CHECKER"), newline),
- map(
- tuple((
- terminated(u16_line, newline),
- terminated(a_line, newline),
- a_line,
- )),
- |(protocol, name, pass)| Checker(protocol, name, pass),
- ),
- ),
- preceded(
- pair(tag("CREATE_ROOM"), newline),
- map(pair(a_line, opt_arg), |(name, pass)| CreateRoom(name, pass)),
- ),
- preceded(
- pair(tag("JOIN_ROOM"), newline),
- map(pair(a_line, opt_arg), |(name, pass)| JoinRoom(name, pass)),
- ),
- preceded(
- pair(tag("ADD_TEAM"), newline),
- map(
- tuple((
- terminated(a_line, newline),
- terminated(u8_line, newline),
- terminated(a_line, newline),
- terminated(a_line, newline),
- terminated(a_line, newline),
- terminated(a_line, newline),
- terminated(u8_line, newline),
- hedgehog_array,
- )),
- |(name, color, grave, fort, voice_pack, flag, difficulty, hedgehogs)| {
- AddTeam(Box::new(TeamInfo {
- owner: String::new(),
- name,
- color,
- grave,
- fort,
- voice_pack,
- flag,
- difficulty,
- hedgehogs,
- hedgehogs_number: 0,
- }))
- },
- ),
- ),
- preceded(
- pair(tag("HH_NUM"), newline),
- map(
- pair(terminated(a_line, newline), u8_line),
- |(name, count)| SetHedgehogsNumber(name, count),
- ),
- ),
- preceded(
- pair(tag("TEAM_COLOR"), newline),
- map(
- pair(terminated(a_line, newline), u8_line),
- |(name, color)| SetTeamColor(name, color),
- ),
- ),
- preceded(
- pair(tag("BAN"), newline),
- map(
- tuple((
- terminated(a_line, newline),
- terminated(a_line, newline),
- u32_line,
- )),
- |(name, reason, time)| Ban(name, reason, time),
- ),
- ),
- preceded(
- pair(tag("BAN_IP"), newline),
- map(
- tuple((
- terminated(a_line, newline),
- terminated(a_line, newline),
- u32_line,
- )),
- |(ip, reason, time)| BanIp(ip, reason, time),
- ),
- ),
- preceded(
- pair(tag("BAN_NICK"), newline),
- map(
- tuple((
- terminated(a_line, newline),
- terminated(a_line, newline),
- u32_line,
- )),
- |(nick, reason, time)| BanNick(nick, reason, time),
- ),
- ),
- ))(input)
-}
-
-pub fn malformed_message(input: &[u8]) -> HwResult<()> {
- map(terminated(take_until(&b"\n\n"[..]), end_of_message), |_| ())(input)
-}
-
-pub fn message(input: &[u8]) -> HwResult<HwProtocolMessage> {
- delimited(
- take_while(|c| c == b'\n'),
- alt((
- no_arg_message,
- single_arg_message,
- cmd_message,
- config_message,
- server_var_message,
- complex_message,
- )),
- end_of_message,
- )(input)
-}
-
-#[cfg(test)]
-mod test {
- use super::message;
- use crate::{
- core::types::GameCfg,
- protocol::{messages::HwProtocolMessage::*, parser::HwProtocolError, test::gen_proto_msg},
- };
- use proptest::{proptest, proptest_helper};
-
- #[cfg(test)]
- proptest! {
- #[test]
- fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
- println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
- assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
- }
- }
-
- #[test]
- fn parse_test() {
- assert_eq!(message(b"PING\n\n"), Ok((&b""[..], Ping)));
- assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame)));
- assert_eq!(
- message(b"NICK\nit's me\n\n"),
- Ok((&b""[..], Nick("it's me".to_string())))
- );
- assert_eq!(message(b"PROTO\n51\n\n"), Ok((&b""[..], Proto(51))));
- assert_eq!(
- message(b"QUIT\nbye-bye\n\n"),
- Ok((&b""[..], Quit(Some("bye-bye".to_string()))))
- );
- assert_eq!(message(b"QUIT\n\n"), Ok((&b""[..], Quit(None))));
- assert_eq!(
- message(b"CMD\nwatch 49471\n\n"),
- Ok((&b""[..], Watch(49471)))
- );
- assert_eq!(
- message(b"BAN\nme\nbad\n77\n\n"),
- Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77)))
- );
-
- assert_eq!(message(b"CMD\nPART\n\n"), Ok((&b""[..], Part(None))));
- assert_eq!(
- message(b"CMD\nPART _msg_\n\n"),
- Ok((&b""[..], Part(Some("_msg_".to_string()))))
- );
-
- assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![]))));
- assert_eq!(
- message(b"CMD\nRND A B\n\n"),
- Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")])))
- );
-
- assert_eq!(
- message(b"CFG\nSCHEME\na\nA\n\n"),
- Ok((
- &b""[..],
- Cfg(GameCfg::Scheme("a".to_string(), vec!["A".to_string()]))
- ))
- );
-
- assert_eq!(
- message(b"QUIT\n1\n2\n\n"),
- Err(nom::Err::Error(HwProtocolError::new()))
- );
- }
-}
--- a/rust/hedgewars-server/src/protocol/test.rs Wed Jun 23 15:32:48 2021 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-use proptest::{
- arbitrary::{any, any_with, Arbitrary, StrategyFor},
- strategy::{BoxedStrategy, Just, Map, Strategy},
- test_runner::{Reason, TestRunner},
-};
-
-use crate::core::types::{GameCfg, HedgehogInfo, ServerVar, ServerVar::*, TeamInfo, VoteType};
-
-use super::messages::{HwProtocolMessage, HwProtocolMessage::*};
-
-// Due to inability to define From between Options
-trait Into2<T>: Sized {
- fn into2(self) -> T;
-}
-impl<T> Into2<T> for T {
- fn into2(self) -> T {
- self
- }
-}
-impl Into2<Vec<String>> for Vec<Ascii> {
- fn into2(self) -> Vec<String> {
- self.into_iter().map(|x| x.0).collect()
- }
-}
-impl Into2<String> for Ascii {
- fn into2(self) -> String {
- self.0
- }
-}
-impl Into2<Option<String>> for Option<Ascii> {
- fn into2(self) -> Option<String> {
- self.map(|x| x.0)
- }
-}
-
-macro_rules! proto_msg_case {
- ($val: ident()) => {
- Just($val)
- };
- ($val: ident($arg: ty)) => {
- any::<$arg>().prop_map(|v| $val(v.into2()))
- };
- ($val: ident($arg1: ty, $arg2: ty)) => {
- any::<($arg1, $arg2)>().prop_map(|v| $val(v.0.into2(), v.1.into2()))
- };
- ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => {
- any::<($arg1, $arg2, $arg3)>().prop_map(|v| $val(v.0.into2(), v.1.into2(), v.2.into2()))
- };
-}
-
-macro_rules! proto_msg_match {
- ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => (
- match $var {
- $($num => (proto_msg_case!($constr $res)).boxed()),*,
- _ => Just($default).boxed()
- }
- )
-}
-
-/// Wrapper type for generating non-empty strings
-#[derive(Debug)]
-struct Ascii(String);
-
-impl Arbitrary for Ascii {
- type Parameters = <String as Arbitrary>::Parameters;
-
- fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
- "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
- }
-
- type Strategy = BoxedStrategy<Ascii>;
-}
-
-impl Arbitrary for GameCfg {
- type Parameters = ();
-
- fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
- use crate::core::types::GameCfg::*;
- (0..10)
- .no_shrink()
- .prop_flat_map(|i| {
- proto_msg_match!(i, def = FeatureSize(0),
- 0 => FeatureSize(u32),
- 1 => MapType(Ascii),
- 2 => MapGenerator(u32),
- 3 => MazeSize(u32),
- 4 => Seed(Ascii),
- 5 => Template(u32),
- 6 => Ammo(Ascii, Option<Ascii>),
- 7 => Scheme(Ascii, Vec<Ascii>),
- 8 => Script(Ascii),
- 9 => Theme(Ascii),
- 10 => DrawnMap(Ascii))
- })
- .boxed()
- }
-
- type Strategy = BoxedStrategy<GameCfg>;
-}
-
-impl Arbitrary for TeamInfo {
- type Parameters = ();
-
- fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
- (
- "[a-z]+",
- 0u8..127u8,
- "[a-z]+",
- "[a-z]+",
- "[a-z]+",
- "[a-z]+",
- 0u8..127u8,
- )
- .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| {
- fn hog(n: u8) -> HedgehogInfo {
- HedgehogInfo {
- name: format!("hog{}", n),
- hat: format!("hat{}", n),
- }
- }
- let hedgehogs = [
- hog(1),
- hog(2),
- hog(3),
- hog(4),
- hog(5),
- hog(6),
- hog(7),
- hog(8),
- ];
- TeamInfo {
- owner: String::new(),
- name,
- color,
- grave,
- fort,
- voice_pack,
- flag,
- difficulty,
- hedgehogs,
- hedgehogs_number: 0,
- }
- })
- .boxed()
- }
-
- type Strategy = BoxedStrategy<TeamInfo>;
-}
-
-impl Arbitrary for ServerVar {
- type Parameters = ();
-
- fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
- (0..=2)
- .no_shrink()
- .prop_flat_map(|i| {
- proto_msg_match!(i, def = ServerVar::LatestProto(0),
- 0 => MOTDNew(Ascii),
- 1 => MOTDOld(Ascii),
- 2 => LatestProto(u16)
- )
- })
- .boxed()
- }
-
- type Strategy = BoxedStrategy<ServerVar>;
-}
-
-impl Arbitrary for VoteType {
- type Parameters = ();
-
- fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
- use VoteType::*;
- (0..=4)
- .no_shrink()
- .prop_flat_map(|i| {
- proto_msg_match!(i, def = VoteType::Pause,
- 0 => Kick(Ascii),
- 1 => Map(Option<Ascii>),
- 2 => Pause(),
- 3 => NewSeed(),
- 4 => HedgehogsPerTeam(u8)
- )
- })
- .boxed()
- }
-
- type Strategy = BoxedStrategy<VoteType>;
-}
-
-pub fn gen_proto_msg() -> BoxedStrategy<HwProtocolMessage> where {
- let res = (0..=55).no_shrink().prop_flat_map(|i| {
- proto_msg_match!(i, def = Ping,
- 0 => Ping(),
- 1 => Pong(),
- 2 => Quit(Option<Ascii>),
- 4 => Global(Ascii),
- 5 => Watch(u32),
- 6 => ToggleServerRegisteredOnly(),
- 7 => SuperPower(),
- 8 => Info(Ascii),
- 9 => Nick(Ascii),
- 10 => Proto(u16),
- 11 => Password(Ascii, Ascii),
- 12 => Checker(u16, Ascii, Ascii),
- 13 => List(),
- 14 => Chat(Ascii),
- 15 => CreateRoom(Ascii, Option<Ascii>),
- 16 => JoinRoom(Ascii, Option<Ascii>),
- 17 => Follow(Ascii),
- 18 => Rnd(Vec<Ascii>),
- 19 => Kick(Ascii),
- 20 => Ban(Ascii, Ascii, u32),
- 21 => BanIp(Ascii, Ascii, u32),
- 22 => BanNick(Ascii, Ascii, u32),
- 23 => BanList(),
- 24 => Unban(Ascii),
- 25 => SetServerVar(ServerVar),
- 26 => GetServerVar(),
- 27 => RestartServer(),
- 28 => Stats(),
- 29 => Part(Option<Ascii>),
- 30 => Cfg(GameCfg),
- 31 => AddTeam(Box<TeamInfo>),
- 32 => RemoveTeam(Ascii),
- 33 => SetHedgehogsNumber(Ascii, u8),
- 34 => SetTeamColor(Ascii, u8),
- 35 => ToggleReady(),
- 36 => StartGame(),
- 37 => EngineMessage(Ascii),
- 38 => RoundFinished(),
- 39 => ToggleRestrictJoin(),
- 40 => ToggleRestrictTeams(),
- 41 => ToggleRegisteredOnly(),
- 42 => RoomName(Ascii),
- 43 => Delegate(Ascii),
- 44 => TeamChat(Ascii),
- 45 => MaxTeams(u8),
- 46 => Fix(),
- 47 => Unfix(),
- 48 => Greeting(Option<Ascii>),
- 49 => CallVote(Option<VoteType>),
- 50 => Vote(bool),
- 51 => ForceVote(bool),
- 52 => Save(Ascii, Ascii),
- 53 => Delete(Ascii),
- 54 => SaveRoom(Ascii),
- 55 => LoadRoom(Ascii)
- )
- });
- res.boxed()
-}
--- a/rust/hedgewars-server/src/server/demo.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/server/demo.rs Wed Jun 23 23:41:51 2021 +0200
@@ -1,6 +1,6 @@
-use crate::{
- core::types::{Ammo, GameCfg, HedgehogInfo, Replay, RoomConfig, Scheme, TeamInfo},
- server::haskell::HaskellValue,
+use crate::{core::types::Replay, server::haskell::HaskellValue};
+use hedgewars_network_protocol::types::{
+ Ammo, GameCfg, HedgehogInfo, RoomConfig, Scheme, TeamInfo,
};
use std::{
collections::HashMap,
--- a/rust/hedgewars-server/src/server/network.rs Wed Jun 23 15:32:48 2021 -0400
+++ b/rust/hedgewars-server/src/server/network.rs Wed Jun 23 23:41:51 2021 +0200
@@ -27,9 +27,10 @@
},
handlers,
handlers::{IoResult, IoTask, ServerState},
- protocol::{messages::HwServerMessage::Redirect, messages::*, ProtocolDecoder},
+ protocol::ProtocolDecoder,
utils,
};
+use hedgewars_network_protocol::{messages::HwServerMessage::Redirect, messages::*};
#[cfg(feature = "official-server")]
use super::io::{IoThread, RequestId};