Modernize engine protocol parser
authorunC0Rr
Fri, 06 Oct 2023 22:37:44 +0200
changeset 16011 cf580d9ff7ef
parent 16010 5ba4d3a0c3eb
child 16012 2c92499daa67
Modernize engine protocol parser
rust/hedgewars-engine-messages/Cargo.toml
rust/hedgewars-engine-messages/src/messages.rs
rust/hedgewars-engine-messages/src/parser.rs
rust/hedgewars-network-protocol/src/parser.rs
--- a/rust/hedgewars-engine-messages/Cargo.toml	Fri Oct 06 20:34:51 2023 +0200
+++ b/rust/hedgewars-engine-messages/Cargo.toml	Fri Oct 06 22:37:44 2023 +0200
@@ -5,6 +5,6 @@
 edition = "2018"
 
 [dependencies]
-nom = "4.1"
+nom = "7.1"
 byteorder = "1.2"
 queues = "1.1"
--- a/rust/hedgewars-engine-messages/src/messages.rs	Fri Oct 06 20:34:51 2023 +0200
+++ b/rust/hedgewars-engine-messages/src/messages.rs	Fri Oct 06 22:37:44 2023 +0200
@@ -128,7 +128,7 @@
 
 #[derive(Debug, PartialEq, Clone)]
 pub enum EngineMessage {
-    Unknown,
+    Unknown(Vec<u8>),
     Empty,
     Synced(SyncedEngineMessage, u32),
     Unsynced(UnsyncedEngineMessage),
@@ -172,7 +172,7 @@
             NextTurn => em![b'N'],
             Switch => em![b'S'],
             Timer(t) => vec![b'0' + t],
-            Slot(s) => vec![b'~' , *s],
+            Slot(s) => vec![b'~', *s],
             SetWeapon(s) => vec![b'~', *s],
             Put(x, y) => {
                 let mut v = vec![b'p'];
@@ -180,14 +180,14 @@
                 v.write_i24::<BigEndian>(*y).unwrap();
 
                 v
-            },
+            }
             CursorMove(x, y) => {
                 let mut v = vec![b'P'];
                 v.write_i24::<BigEndian>(*x).unwrap();
                 v.write_i24::<BigEndian>(*y).unwrap();
 
                 v
-            },
+            }
             HighJump => em![b'J'],
             LongJump => em![b'j'],
             Skip => em![b','],
@@ -242,7 +242,7 @@
     fn to_unwrapped(&self) -> Vec<u8> {
         use self::EngineMessage::*;
         match self {
-            Unknown => unreachable!("you're not supposed to construct such messages"),
+            Unknown(_) => unreachable!("you're not supposed to construct such messages"),
             Empty => unreachable!("you're not supposed to construct such messages"),
             Synced(SyncedEngineMessage::TimeWrap, _) => vec![b'#', 0xff, 0xff],
             Synced(msg, timestamp) => {
--- a/rust/hedgewars-engine-messages/src/parser.rs	Fri Oct 06 20:34:51 2023 +0200
+++ b/rust/hedgewars-engine-messages/src/parser.rs	Fri Oct 06 22:37:44 2023 +0200
@@ -1,126 +1,169 @@
+use std::str;
+
+use nom::branch::alt;
+use nom::bytes::streaming::*;
+use nom::combinator::*;
+use nom::error::{ErrorKind, ParseError};
+use nom::multi::*;
+use nom::number::streaming::*;
+use nom::sequence::{pair, preceded, terminated, tuple};
+use nom::{Err, IResult, Parser};
+
 use crate::messages::{
     ConfigEngineMessage::*, EngineMessage::*, KeystrokeAction::*, SyncedEngineMessage::*,
     UnorderedEngineMessage::*, *,
 };
-use nom::{Err::Error, *};
-use std::str;
 
-macro_rules! eof_slice (
-  ($i:expr,) => (
-    {
-      if ($i).input_len() == 0 {
-        Ok(($i, $i))
-      } else {
-        Err(Error(error_position!($i, ErrorKind::Eof::<u32>)))
-      }
+fn eof_slice<I>(i: I) -> IResult<I, I>
+where
+    I: nom::InputLength + Clone,
+{
+    if i.input_len() == 0 {
+        Ok((i.clone(), i))
+    } else {
+        Err(Err::Error(nom::error::Error::new(i, ErrorKind::Eof)))
     }
-  );
-);
+}
+fn unrecognized_message(input: &[u8]) -> IResult<&[u8], EngineMessage> {
+    map(rest, |i: &[u8]| Unknown(i.to_owned()))(input)
+}
 
-named!(unrecognized_message<&[u8], EngineMessage>,
-    do_parse!(rest >> (Unknown))
-);
+fn string_tail(input: &[u8]) -> IResult<&[u8], String> {
+    map_res(rest, str::from_utf8)(input).map(|(i, s)| (i, s.to_owned()))
+}
 
-named!(string_tail<&[u8], String>, map!(map_res!(rest, str::from_utf8), String::from));
-
-named!(length_without_timestamp<&[u8], usize>,
-    map_opt!(rest_len, |l| if l > 2 { Some(l - 2) } else { None } )
-);
+fn length_without_timestamp(input: &[u8]) -> IResult<&[u8], usize> {
+    map_opt(rest_len, |l| if l > 2 { Some(l - 2) } else { None })(input)
+}
 
-named!(synced_message<&[u8], SyncedEngineMessage>, alt!(
-        do_parse!(tag!("L") >> (Left(Press)))
-      | do_parse!(tag!("l") >> ( Left(Release) ))
-      | do_parse!(tag!("R") >> ( Right(Press) ))
-      | do_parse!(tag!("r") >> ( Right(Release) ))
-      | do_parse!(tag!("U") >> ( Up(Press) ))
-      | do_parse!(tag!("u") >> ( Up(Release) ))
-      | do_parse!(tag!("D") >> ( Down(Press) ))
-      | do_parse!(tag!("d") >> ( Down(Release) ))
-      | do_parse!(tag!("Z") >> ( Precise(Press) ))
-      | do_parse!(tag!("z") >> ( Precise(Release) ))
-      | do_parse!(tag!("A") >> ( Attack(Press) ))
-      | do_parse!(tag!("a") >> ( Attack(Release) ))
-      | do_parse!(tag!("N") >> ( NextTurn ))
-      | do_parse!(tag!("j") >> ( LongJump ))
-      | do_parse!(tag!("J") >> ( HighJump ))
-      | do_parse!(tag!("S") >> ( Switch ))
-      | do_parse!(tag!(",") >> ( Skip ))
-      | do_parse!(tag!("1") >> ( Timer(1) ))
-      | do_parse!(tag!("2") >> ( Timer(2) ))
-      | do_parse!(tag!("3") >> ( Timer(3) ))
-      | do_parse!(tag!("4") >> ( Timer(4) ))
-      | do_parse!(tag!("5") >> ( Timer(5) ))
-      | do_parse!(tag!("p") >> x: be_i24 >> y: be_i24 >> ( Put(x, y) ))
-      | do_parse!(tag!("P") >> x: be_i24 >> y: be_i24 >> ( CursorMove(x, y) ))
-      | do_parse!(tag!("f") >> s: string_tail >> ( SyncedEngineMessage::TeamControlLost(s) ))
-      | do_parse!(tag!("g") >> s: string_tail >> ( SyncedEngineMessage::TeamControlGained(s) ))
-      | do_parse!(tag!("t") >> t: be_u8 >> ( Taunt(t) ))
-      | do_parse!(tag!("w") >> w: be_u8 >> ( SetWeapon(w) ))
-      | do_parse!(tag!("~") >> s: be_u8 >> ( Slot(s) ))
-      | do_parse!(tag!("+") >> ( Heartbeat ))
-));
+fn synced_message(input: &[u8]) -> IResult<&[u8], SyncedEngineMessage> {
+    alt((
+        alt((
+            map(tag(b"L"), |_| Left(Press)),
+            map(tag(b"l"), |_| Left(Release)),
+            map(tag(b"R"), |_| Right(Press)),
+            map(tag(b"r"), |_| Right(Release)),
+            map(tag(b"U"), |_| Up(Press)),
+            map(tag(b"u"), |_| Up(Release)),
+            map(tag(b"D"), |_| Down(Press)),
+            map(tag(b"d"), |_| Down(Release)),
+            map(tag(b"Z"), |_| Precise(Press)),
+            map(tag(b"z"), |_| Precise(Release)),
+            map(tag(b"A"), |_| Attack(Press)),
+            map(tag(b"a"), |_| Attack(Release)),
+            map(tag(b"N"), |_| NextTurn),
+            map(tag(b"j"), |_| LongJump),
+            map(tag(b"J"), |_| HighJump),
+            map(tag(b"S"), |_| Switch),
+        )),
+        alt((
+            map(tag(b","), |_| Skip),
+            map(tag(b"1"), |_| Timer(1)),
+            map(tag(b"2"), |_| Timer(2)),
+            map(tag(b"3"), |_| Timer(3)),
+            map(tag(b"4"), |_| Timer(4)),
+            map(tag(b"5"), |_| Timer(5)),
+            map(tuple((tag(b"p"), be_i24, be_i24)), |(_, x, y)| Put(x, y)),
+            map(tuple((tag(b"P"), be_i24, be_i24)), |(_, x, y)| {
+                CursorMove(x, y)
+            }),
+            map(preceded(tag(b"f"), string_tail), TeamControlLost),
+            map(preceded(tag(b"g"), string_tail), TeamControlGained),
+            map(preceded(tag(b"t"), be_u8), Taunt),
+            map(preceded(tag(b"w"), be_u8), SetWeapon),
+            map(preceded(tag(b"~"), be_u8), Slot),
+            map(tag(b"+"), |_| Heartbeat),
+        )),
+    ))(input)
+}
 
-named!(unsynced_message<&[u8], UnsyncedEngineMessage>, alt!(
-        do_parse!(tag!("F") >> s: string_tail >> ( UnsyncedEngineMessage::TeamControlLost(s) ))
-      | do_parse!(tag!("G") >> s: string_tail >> ( UnsyncedEngineMessage::TeamControlGained(s) ))
-      | do_parse!(tag!("h") >> s: string_tail >> ( UnsyncedEngineMessage::HogSay(s) ))
-      | do_parse!(tag!("s") >> s: string_tail >> ( UnsyncedEngineMessage::ChatMessage(s)) )
-      | do_parse!(tag!("b") >> s: string_tail >> ( UnsyncedEngineMessage::TeamMessage(s)) ) // TODO: wtf is the format
-));
+fn unsynced_message(input: &[u8]) -> IResult<&[u8], UnsyncedEngineMessage> {
+    alt((
+        map(
+            preceded(tag(b"F"), string_tail),
+            UnsyncedEngineMessage::TeamControlLost,
+        ),
+        map(
+            preceded(tag(b"G"), string_tail),
+            UnsyncedEngineMessage::TeamControlGained,
+        ),
+        map(
+            preceded(tag(b"h"), string_tail),
+            UnsyncedEngineMessage::HogSay,
+        ),
+        map(
+            preceded(tag(b"s"), string_tail),
+            UnsyncedEngineMessage::ChatMessage,
+        ),
+        map(
+            preceded(tag(b"b"), string_tail),
+            UnsyncedEngineMessage::TeamMessage,
+        ),
+    ))(input)
+}
 
-named!(unordered_message<&[u8], UnorderedEngineMessage>, alt!(
-      do_parse!(tag!("?") >> ( Ping ))
-    | do_parse!(tag!("!") >> ( Pong ))
-    | do_parse!(tag!("E") >> s: string_tail >> ( UnorderedEngineMessage::Error(s)) )
-    | do_parse!(tag!("W") >> s: string_tail >> ( Warning(s)) )
-    | do_parse!(tag!("M") >> s: string_tail >> ( GameSetupChecksum(s)) )
-    | do_parse!(tag!("o") >> ( StopSyncing ))
-    | do_parse!(tag!("I") >> ( PauseToggled ))
-));
-
-named!(config_message<&[u8], ConfigEngineMessage>, alt!(
-    do_parse!(tag!("C") >> (ConfigRequest))
-    | do_parse!(tag!("eseed ") >> s: string_tail >> ( SetSeed(s)) )
-    | do_parse!(tag!("e$feature_size ") >> s: string_tail >> ( SetFeatureSize(s.parse::<u8>().unwrap())) )
-));
-
-named!(timestamped_message<&[u8], (SyncedEngineMessage, u16)>,
-    do_parse!(msg: length_value!(length_without_timestamp, terminated!(synced_message, eof_slice!()))
-        >> timestamp: be_u16
-        >> ((msg, timestamp))
-    )
-);
+fn unordered_message(input: &[u8]) -> IResult<&[u8], UnorderedEngineMessage> {
+    alt((
+        map(tag(b"?"), |_| Ping),
+        map(tag(b"!"), |_| Pong),
+        map(preceded(tag(b"E"), string_tail), Error),
+        map(preceded(tag(b"W"), string_tail), Warning),
+        map(preceded(tag(b"M"), string_tail), GameSetupChecksum),
+        map(tag(b"o"), |_| StopSyncing),
+        map(tag(b"I"), |_| PauseToggled),
+    ))(input)
+}
 
-named!(unwrapped_message<&[u8], EngineMessage>,
-    alt!(
-        map!(timestamped_message, |(m, t)| Synced(m, t as u32))
-        | do_parse!(tag!("#") >> (Synced(TimeWrap, 65535)))
-        | map!(unordered_message, |m| Unordered(m))
-        | map!(unsynced_message, |m| Unsynced(m))
-        | map!(config_message, |m| Config(m))
-        | unrecognized_message
-));
+fn config_message(input: &[u8]) -> IResult<&[u8], ConfigEngineMessage> {
+    alt((
+        map(tag(b"C"), |_| ConfigRequest),
+        map(preceded(tag(b"eseed "), string_tail), SetSeed),
+        map(preceded(tag(b"e$feature_size "), string_tail), |s| {
+            SetFeatureSize(s.parse().unwrap_or_default())
+        }),
+    ))(input)
+}
 
-named!(length_specifier<&[u8], u16>, alt!(
-    verify!(map!(take!(1), |a : &[u8]| a[0] as u16), |l| l < 64)
-    | map!(take!(2), |a| (a[0] as u16 - 64) * 256 + a[1] as u16 + 64)
-    )
-);
-
-named!(empty_message<&[u8], EngineMessage>,
-    do_parse!(tag!("\0") >> (Empty))
-);
+fn timestamped_message(input: &[u8]) -> IResult<&[u8], (SyncedEngineMessage, u16)> {
+    terminated(pair(synced_message, be_u16), eof_slice)(input)
+}
+fn unwrapped_message(input: &[u8]) -> IResult<&[u8], EngineMessage> {
+    alt((
+        map(timestamped_message, |(m, t)| {
+            EngineMessage::Synced(m, t as u32)
+        }),
+        map(tag(b"#"), |_| Synced(TimeWrap, 65535u32)),
+        map(unordered_message, Unordered),
+        map(unsynced_message, Unsynced),
+        map(config_message, Config),
+        unrecognized_message,
+    ))(input)
+}
 
-named!(non_empty_message<&[u8], EngineMessage>,
-    length_value!(length_specifier, terminated!(unwrapped_message, eof_slice!())));
+fn length_specifier(input: &[u8]) -> IResult<&[u8], u16> {
+    alt((
+        verify(map(take(1usize), |a: &[u8]| a[0] as u16), |&l| l < 64),
+        map(take(2usize), |a: &[u8]| {
+            (a[0] as u16 - 64) * 256 + a[1] as u16 + 64
+        }),
+    ))(input)
+}
 
-named!(message<&[u8], EngineMessage>, alt!(
-      empty_message
-    | non_empty_message
-    )
-);
+fn empty_message(input: &[u8]) -> IResult<&[u8], EngineMessage> {
+    map(tag(b"\0"), |_| Empty)(input)
+}
+
+fn non_empty_message(input: &[u8]) -> IResult<&[u8], EngineMessage> {
+    map_parser(length_data(length_specifier), unwrapped_message)(input)
+}
 
-named!(pub extract_messages<&[u8], Vec<EngineMessage> >, many0!(complete!(message)));
+fn message(input: &[u8]) -> IResult<&[u8], EngineMessage> {
+    alt((empty_message, non_empty_message))(input)
+}
+
+pub fn extract_messages(input: &[u8]) -> IResult<&[u8], Vec<EngineMessage>> {
+    many0(complete(message))(input)
+}
 
 pub fn extract_message(buf: &[u8]) -> Option<(usize, EngineMessage)> {
     let parse_result = message(buf);
@@ -178,10 +221,13 @@
     #[test]
     fn parse_incorrect_messages() {
         assert_eq!(message(b"\x00"), Ok((&b""[..], Empty)));
-        assert_eq!(message(b"\x01\x00"), Ok((&b""[..], Unknown)));
+        assert_eq!(message(b"\x01\x00"), Ok((&b""[..], Unknown(vec![0]))));
 
         // garbage after correct message
-        assert_eq!(message(b"\x04La\x01\x02"), Ok((&b""[..], Unknown)));
+        assert_eq!(
+            message(b"\x04La\x01\x02"),
+            Ok((&b""[..], Unknown(vec![76, 97, 1, 2])))
+        );
     }
 
     #[test]
@@ -194,6 +240,9 @@
         assert_eq!(string_tail(b"abc"), Ok((&b""[..], String::from("abc"))));
 
         assert_eq!(extract_message(b"\x02#"), None);
+
+        assert_eq!(synced_message(b"L"), Ok((&b""[..], Left(Press))));
+
         assert_eq!(
             extract_message(b"\x01#"),
             Some((2, Synced(TimeWrap, 65535)))
--- a/rust/hedgewars-network-protocol/src/parser.rs	Fri Oct 06 20:34:51 2023 +0200
+++ b/rust/hedgewars-network-protocol/src/parser.rs	Fri Oct 06 22:37:44 2023 +0200
@@ -127,7 +127,7 @@
     ))(input)
 }
 
-fn opt_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
+fn opt_arg(input: &[u8]) -> HwResult<Option<String>> {
     alt((
         map(peek(end_of_message), |_| None),
         map(preceded(tag("\n"), a_line), Some),
@@ -138,7 +138,7 @@
     preceded(tag(" "), take_while(|c| c == b' '))(input)
 }
 
-fn opt_space_arg<'a>(input: &'a [u8]) -> HwResult<'a, Option<String>> {
+fn opt_space_arg(input: &[u8]) -> HwResult<Option<String>> {
     alt((
         map(peek(end_of_message), |_| None),
         map(preceded(spaces, a_line), Some),
@@ -184,10 +184,10 @@
 }
 
 fn no_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    fn message<'a>(
-        name: &'a str,
+    fn message(
+        name: &str,
         msg: HwProtocolMessage,
-    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+    ) -> impl Fn(&[u8]) -> HwResult<HwProtocolMessage> {
         move |i| map(tag(name), |_| msg.clone())(i)
     }
 
@@ -207,11 +207,11 @@
 }
 
 fn single_arg_message(input: &[u8]) -> HwResult<HwProtocolMessage> {
-    fn message<'a, T, F, G>(
-        name: &'a str,
+    fn message<T, F, G>(
+        name: &str,
         parser: F,
         constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage>
+    ) -> impl FnMut(&[u8]) -> HwResult<HwProtocolMessage>
     where
         F: Fn(&[u8]) -> HwResult<T>,
         G: Fn(T) -> HwProtocolMessage,
@@ -239,10 +239,10 @@
 }
 
 fn cmd_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
-    fn cmd_no_arg<'a>(
-        name: &'a str,
+    fn cmd_no_arg(
+        name: &str,
         msg: HwProtocolMessage,
-    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> {
+    ) -> impl Fn(&[u8]) -> HwResult<HwProtocolMessage> {
         move |i| map(tag_no_case(name), |_| msg.clone())(i)
     }
 
@@ -319,11 +319,11 @@
 }
 
 fn config_message<'a>(input: &'a [u8]) -> HwResult<'a, HwProtocolMessage> {
-    fn cfg_single_arg<'a, T, F, G>(
-        name: &'a str,
+    fn cfg_single_arg<T, F, G>(
+        name: &str,
         parser: F,
         constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, GameCfg>
+    ) -> impl FnMut(&[u8]) -> HwResult<GameCfg>
     where
         F: Fn(&[u8]) -> HwResult<T>,
         G: Fn(T) -> GameCfg,
@@ -521,11 +521,11 @@
 pub fn server_message(input: &[u8]) -> HwResult<HwServerMessage> {
     use HwServerMessage::*;
 
-    fn single_arg_message<'a, T, F, G>(
-        name: &'a str,
+    fn single_arg_message<T, F, G>(
+        name: &str,
         parser: F,
         constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage>
+    ) -> impl FnMut(&[u8]) -> HwResult<HwServerMessage>
     where
         F: Fn(&[u8]) -> HwResult<T>,
         G: Fn(T) -> HwServerMessage,
@@ -536,10 +536,10 @@
         )
     }
 
-    fn list_message<'a, G>(
-        name: &'a str,
+    fn list_message<G>(
+        name: &str,
         constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage>
+    ) -> impl FnMut(&[u8]) -> HwResult<HwServerMessage>
     where
         G: Fn(Vec<String>) -> HwServerMessage,
     {
@@ -555,10 +555,10 @@
         )
     }
 
-    fn string_and_list_message<'a, G>(
-        name: &'a str,
+    fn string_and_list_message<G>(
+        name: &str,
         constructor: G,
-    ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage>
+    ) -> impl FnMut(&[u8]) -> HwResult<HwServerMessage>
     where
         G: Fn(String, Vec<String>) -> HwServerMessage,
     {
@@ -577,10 +577,10 @@
         )
     }
 
-    fn message<'a>(
-        name: &'a str,
+    fn message(
+        name: &str,
         msg: HwServerMessage,
-    ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwServerMessage> {
+    ) -> impl Fn(&[u8]) -> HwResult<HwServerMessage> {
         move |i| map(tag(name), |_| msg.clone())(i)
     }