Add parser and idempotention tests for server messages
authorunc0rr
Wed, 30 Jun 2021 00:18:53 +0200
changeset 15810 ee84e417d8d0
parent 15809 c3971b38bbfa
child 15811 a855f32ab3ca
Add parser and idempotention tests for server messages
rust/hedgewars-network-protocol/src/messages.rs
rust/hedgewars-network-protocol/src/parser.rs
rust/hedgewars-network-protocol/tests/parser.rs
rust/hedgewars-network-protocol/tests/test.rs
rust/hedgewars-server/src/handlers.rs
rust/hedgewars-server/src/utils.rs
--- a/rust/hedgewars-network-protocol/src/messages.rs	Sat Jun 26 00:13:28 2021 +0200
+++ b/rust/hedgewars-network-protocol/src/messages.rs	Wed Jun 30 00:18:53 2021 +0200
@@ -107,9 +107,9 @@
     ProtocolFlags::format('-', flags)
 }
 
-#[derive(Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
 pub enum HwServerMessage {
-    Connected(u32),
+    Connected(String, u32),
     Redirect(u16),
 
     Ping,
@@ -151,7 +151,6 @@
     Notice(String),
     Warning(String),
     Error(String),
-    Unreachable,
 
     //Deprecated messages
     LegacyReady(bool, Vec<String>),
@@ -370,11 +369,7 @@
         match self {
             Ping => msg!["PING"],
             Pong => msg!["PONG"],
-            Connected(protocol_version) => msg![
-                "CONNECTED",
-                "Hedgewars server https://www.hedgewars.org/",
-                protocol_version
-            ],
+            Connected(message, protocol_version) => msg!["CONNECTED", message, protocol_version],
             Redirect(port) => msg!["REDIRECT", port],
             Bye(msg) => msg!["BYE", msg],
             Nick(nick) => msg!["NICK", nick],
@@ -414,8 +409,6 @@
             LegacyReady(is_ready, nicks) => {
                 construct_message(&[if *is_ready { "READY" } else { "NOT_READY" }], &nicks)
             }
-
-            _ => msg!["ERROR", "UNIMPLEMENTED"],
         }
     }
 }
--- a/rust/hedgewars-network-protocol/src/parser.rs	Sat Jun 26 00:13:28 2021 +0200
+++ b/rust/hedgewars-network-protocol/src/parser.rs	Wed Jun 30 00:18:53 2021 +0200
@@ -23,7 +23,7 @@
     str::{FromStr, Utf8Error},
 };
 
-use crate::messages::{HwProtocolMessage, HwProtocolMessage::*};
+use crate::messages::{HwProtocolMessage, HwProtocolMessage::*, HwServerMessage};
 use crate::types::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType};
 
 #[derive(Debug, PartialEq)]
@@ -503,3 +503,158 @@
         end_of_message,
     )(input)
 }
+
+pub fn server_message(input: &[u8]) -> HwResult<HwServerMessage> {
+    use HwServerMessage::*;
+
+    fn single_arg_message<'a, T, F, G>(
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage>
+    where
+        F: Fn(&[u8]) -> HwResult<T>,
+        G: Fn(T) -> HwServerMessage,
+    {
+        map(
+            preceded(terminated(tag(name), newline), parser),
+            constructor,
+        )
+    }
+
+    fn list_message<'a, G>(
+        name: &'a str,
+        constructor: G,
+    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage>
+    where
+        G: Fn(Vec<String>) -> HwServerMessage,
+    {
+        map(
+            preceded(
+                tag(name),
+                alt((
+                    map(peek(end_of_message), |_| None),
+                    map(preceded(newline, separated_list0(newline, a_line)), Some),
+                )),
+            ),
+            move |values| constructor(values.unwrap_or_default()),
+        )
+    }
+
+    fn string_and_list_message<'a, G>(
+        name: &'a str,
+        constructor: G,
+    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage>
+    where
+        G: Fn(String, Vec<String>) -> HwServerMessage,
+    {
+        preceded(
+            pair(tag(name), newline),
+            map(
+                pair(
+                    a_line,
+                    alt((
+                        map(peek(end_of_message), |_| None),
+                        map(preceded(newline, separated_list0(newline, a_line)), Some),
+                    )),
+                ),
+                move |(name, values)| constructor(name, values.unwrap_or_default()),
+            ),
+        )
+    }
+
+    fn message<'a>(
+        name: &'a str,
+        msg: HwServerMessage,
+    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwServerMessage> {
+        move |i| map(tag(name), |_| msg.clone())(i)
+    }
+
+    delimited(
+        take_while(|c| c == b'\n'),
+        alt((
+            alt((
+                message("PING", Ping),
+                message("PONG", Pong),
+                message("LOGONPASSED", LogonPassed),
+                message("KICKED", Kicked),
+                message("RUN_GAME", RunGame),
+                message("ROUND_FINISHED", RoundFinished),
+                message("REPLAY_START", ReplayStart),
+            )),
+            alt((
+                single_arg_message("REDIRECT", u16_line, Redirect),
+                single_arg_message("BYE", a_line, Bye),
+                single_arg_message("NICK", a_line, Nick),
+                single_arg_message("PROTO", u16_line, Proto),
+                single_arg_message("ASKPASSWORD", a_line, AskPassword),
+                single_arg_message("SERVER_AUTH", a_line, ServerAuth),
+                single_arg_message("ROOM\nDEL", a_line, RoomRemove),
+                single_arg_message("JOINING", a_line, Joining),
+                single_arg_message("REMOVE_TEAM", a_line, TeamRemove),
+                single_arg_message("TEAM_ACCEPTED", a_line, TeamAccepted),
+                single_arg_message("SERVER_MESSAGE", a_line, ServerMessage),
+                single_arg_message("NOTICE", a_line, Notice),
+                single_arg_message("WARNING", a_line, Warning),
+                single_arg_message("ERROR", a_line, Error),
+            )),
+            alt((
+                preceded(
+                    pair(tag("LOBBY:LEFT"), newline),
+                    map(pair(terminated(a_line, newline), a_line), |(nick, msg)| {
+                        LobbyLeft(nick, msg)
+                    }),
+                ),
+                preceded(
+                    pair(tag("CHAT"), newline),
+                    map(pair(terminated(a_line, newline), a_line), |(nick, msg)| {
+                        ChatMsg { nick, msg }
+                    }),
+                ),
+                preceded(
+                    pair(tag("TEAM_COLOR"), newline),
+                    map(
+                        pair(terminated(a_line, newline), u8_line),
+                        |(name, color)| TeamColor(name, color),
+                    ),
+                ),
+                preceded(
+                    pair(tag("HH_NUM"), newline),
+                    map(
+                        pair(terminated(a_line, newline), u8_line),
+                        |(name, count)| HedgehogsNumber(name, count),
+                    ),
+                ),
+                preceded(
+                    pair(tag("CONNECTED"), newline),
+                    map(
+                        pair(terminated(a_line, newline), u32_line),
+                        |(msg, server_protocol_version)| Connected(msg, server_protocol_version),
+                    ),
+                ),
+                preceded(
+                    pair(tag("LEFT"), newline),
+                    map(pair(terminated(a_line, newline), a_line), |(nick, msg)| {
+                        RoomLeft(nick, msg)
+                    }),
+                ),
+            )),
+            alt((
+                string_and_list_message("CLIENT_FLAGS", ClientFlags),
+                string_and_list_message("ROOM\nUPD", RoomUpdated),
+                string_and_list_message("CFG", ConfigEntry),
+            )),
+            alt((
+                list_message("LOBBY:JOINED", LobbyJoined),
+                list_message("ROOMS", Rooms),
+                list_message("ROOM\nADD", RoomAdd),
+                list_message("JOINED", RoomJoined),
+                list_message("ADD_TEAM", TeamAdd),
+                list_message("EM", ForwardEngineMessage),
+                list_message("INFO", Info),
+                list_message("SERVER_VARS", ServerVars),
+            )),
+        )),
+        end_of_message,
+    )(input)
+}
--- a/rust/hedgewars-network-protocol/tests/parser.rs	Sat Jun 26 00:13:28 2021 +0200
+++ b/rust/hedgewars-network-protocol/tests/parser.rs	Wed Jun 30 00:18:53 2021 +0200
@@ -1,11 +1,13 @@
 use hedgewars_network_protocol::{
-    parser::message,
+    parser::HwProtocolError,
+    parser::{message, server_message},
     types::GameCfg,
-    {messages::HwProtocolMessage::*, parser::HwProtocolError},
 };
 
 #[test]
 fn parse_test() {
+    use hedgewars_network_protocol::messages::HwProtocolMessage::*;
+
     assert_eq!(message(b"PING\n\n"), Ok((&b""[..], Ping)));
     assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame)));
     assert_eq!(
@@ -52,3 +54,23 @@
         Err(nom::Err::Error(HwProtocolError::new()))
     );
 }
+
+#[test]
+fn parse_server_messages_test() {
+    use hedgewars_network_protocol::messages::HwServerMessage::*;
+
+    assert_eq!(server_message(b"PING\n\n"), Ok((&b""[..], Ping)));
+
+    assert_eq!(
+        server_message(b"JOINING\nnoone\n\n"),
+        Ok((&b""[..], Joining("noone".to_string())))
+    );
+
+    assert_eq!(
+        server_message(b"CLIENT_FLAGS\naaa\nA\nB\n\n"),
+        Ok((
+            &b""[..],
+            ClientFlags("aaa".to_string(), vec!["A".to_string(), "B".to_string()])
+        ))
+    )
+}
--- a/rust/hedgewars-network-protocol/tests/test.rs	Sat Jun 26 00:13:28 2021 +0200
+++ b/rust/hedgewars-network-protocol/tests/test.rs	Wed Jun 30 00:18:53 2021 +0200
@@ -4,14 +4,16 @@
     strategy::{BoxedStrategy, Just, Strategy},
 };
 
-use hedgewars_network_protocol::messages::{HwProtocolMessage, HwProtocolMessage::*};
-use hedgewars_network_protocol::parser::message;
+use hedgewars_network_protocol::messages::{HwProtocolMessage, HwServerMessage};
+use hedgewars_network_protocol::parser::{message, server_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 {
+    use hedgewars_network_protocol::messages::HwProtocolMessage::*;
+
     let res = (0..=55).no_shrink().prop_flat_map(|i| {
         proto_msg_match!(i, def = Ping,
             0 => Ping(),
@@ -74,10 +76,64 @@
     res.boxed()
 }
 
+pub fn gen_server_msg() -> BoxedStrategy<HwServerMessage> where {
+    use hedgewars_network_protocol::messages::HwServerMessage::*;
+
+    let res = (0..=55).no_shrink().prop_flat_map(|i| {
+        proto_msg_match!(i, def = Ping,
+                    0 => Connected(Ascii, u32),
+                    1 => Redirect(u16),
+                    2 => Ping(),
+                    3 => Pong(),
+                    4 => Bye(Ascii),
+                    5 => Nick(Ascii),
+                    6 => Proto(u16),
+                    7 => AskPassword(Ascii),
+                    8 => ServerAuth(Ascii),
+                    9 => LogonPassed(),
+                    10 => LobbyLeft(Ascii, Ascii),
+                    11 => LobbyJoined(Vec<Ascii>),
+        //            12 => ChatMsg { Ascii, Ascii },
+                    13 => ClientFlags(Ascii, Vec<Ascii>),
+                    14 => Rooms(Vec<Ascii>),
+                    15 => RoomAdd(Vec<Ascii>),
+                    16=> RoomJoined(Vec<Ascii>),
+                    17 => RoomLeft(Ascii, Ascii),
+                    18 => RoomRemove(Ascii),
+                    19 => RoomUpdated(Ascii, Vec<Ascii>),
+                    20 => Joining(Ascii),
+                    21 => TeamAdd(Vec<Ascii>),
+                    22 => TeamRemove(Ascii),
+                    23 => TeamAccepted(Ascii),
+                    24 => TeamColor(Ascii, u8),
+                    25 => HedgehogsNumber(Ascii, u8),
+                    26 => ConfigEntry(Ascii, Vec<Ascii>),
+                    27 => Kicked(),
+                    28 => RunGame(),
+                    29 => ForwardEngineMessage(Vec<Ascii>),
+                    30 => RoundFinished(),
+                    31 => ReplayStart(),
+                    32 => Info(Vec<Ascii>),
+                    33 => ServerMessage(Ascii),
+                    34 => ServerVars(Vec<Ascii>),
+                    35 => Notice(Ascii),
+                    36 => Warning(Ascii),
+                    37 => Error(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())))
     }
+
+    #[test]
+    fn is_server_message_parser_composition_idempotent(ref msg in gen_server_msg()) {
+        println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
+        assert_eq!(server_message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
+    }
 }
--- a/rust/hedgewars-server/src/handlers.rs	Sat Jun 26 00:13:28 2021 +0200
+++ b/rust/hedgewars-server/src/handlers.rs	Wed Jun 30 00:18:53 2021 +0200
@@ -401,7 +401,7 @@
             .anteroom
             .add_client(client_id, encode(&salt), is_local);
 
-        response.add(HwServerMessage::Connected(utils::SERVER_VERSION).send_self());
+        response.add(HwServerMessage::Connected(utils::SERVER_MESSAGE.to_owned(), utils::SERVER_VERSION).send_self());
     }
 }
 
--- a/rust/hedgewars-server/src/utils.rs	Sat Jun 26 00:13:28 2021 +0200
+++ b/rust/hedgewars-server/src/utils.rs	Wed Jun 30 00:18:53 2021 +0200
@@ -3,6 +3,7 @@
 use std::iter::Iterator;
 
 pub const SERVER_VERSION: u32 = 3;
+pub const SERVER_MESSAGE: &str = &"Hedgewars server https://www.hedgewars.org/";
 pub const SERVER_TOKEN: mio::Token = mio::Token(1_000_000_000);
 pub const SECURE_SERVER_TOKEN: mio::Token = mio::Token(1_000_000_001);
 pub const IO_TOKEN: mio::Token = mio::Token(1_000_000_003);