# HG changeset patch # User unC0Rr # Date 1696624664 -7200 # Node ID cf580d9ff7ef6bdebd8597b17a1c3788b11f11a8 # Parent 5ba4d3a0c3ebc54f65c6776b41c6f1362dbbf227 Modernize engine protocol parser diff -r 5ba4d3a0c3eb -r cf580d9ff7ef rust/hedgewars-engine-messages/Cargo.toml --- 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" diff -r 5ba4d3a0c3eb -r cf580d9ff7ef rust/hedgewars-engine-messages/src/messages.rs --- 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), 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::(*y).unwrap(); v - }, + } CursorMove(x, y) => { let mut v = vec![b'P']; v.write_i24::(*x).unwrap(); v.write_i24::(*y).unwrap(); v - }, + } HighJump => em![b'J'], LongJump => em![b'j'], Skip => em![b','], @@ -242,7 +242,7 @@ fn to_unwrapped(&self) -> Vec { 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) => { diff -r 5ba4d3a0c3eb -r cf580d9ff7ef rust/hedgewars-engine-messages/src/parser.rs --- 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::))) - } +fn eof_slice(i: I) -> IResult +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::().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 >, 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> { + 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))) diff -r 5ba4d3a0c3eb -r cf580d9ff7ef rust/hedgewars-network-protocol/src/parser.rs --- 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> { +fn opt_arg(input: &[u8]) -> HwResult> { 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> { +fn opt_space_arg(input: &[u8]) -> HwResult> { alt(( map(peek(end_of_message), |_| None), map(preceded(spaces, a_line), Some), @@ -184,10 +184,10 @@ } fn no_arg_message(input: &[u8]) -> HwResult { - fn message<'a>( - name: &'a str, + fn message( + name: &str, msg: HwProtocolMessage, - ) -> impl Fn(&'a [u8]) -> HwResult<'a, HwProtocolMessage> { + ) -> impl Fn(&[u8]) -> HwResult { move |i| map(tag(name), |_| msg.clone())(i) } @@ -207,11 +207,11 @@ } fn single_arg_message(input: &[u8]) -> HwResult { - fn message<'a, T, F, G>( - name: &'a str, + fn message( + name: &str, parser: F, constructor: G, - ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwProtocolMessage> + ) -> impl FnMut(&[u8]) -> HwResult where F: Fn(&[u8]) -> HwResult, 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 { 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( + name: &str, parser: F, constructor: G, - ) -> impl FnMut(&'a [u8]) -> HwResult<'a, GameCfg> + ) -> impl FnMut(&[u8]) -> HwResult where F: Fn(&[u8]) -> HwResult, G: Fn(T) -> GameCfg, @@ -521,11 +521,11 @@ pub fn server_message(input: &[u8]) -> HwResult { use HwServerMessage::*; - fn single_arg_message<'a, T, F, G>( - name: &'a str, + fn single_arg_message( + name: &str, parser: F, constructor: G, - ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage> + ) -> impl FnMut(&[u8]) -> HwResult where F: Fn(&[u8]) -> HwResult, G: Fn(T) -> HwServerMessage, @@ -536,10 +536,10 @@ ) } - fn list_message<'a, G>( - name: &'a str, + fn list_message( + name: &str, constructor: G, - ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage> + ) -> impl FnMut(&[u8]) -> HwResult where G: Fn(Vec) -> HwServerMessage, { @@ -555,10 +555,10 @@ ) } - fn string_and_list_message<'a, G>( - name: &'a str, + fn string_and_list_message( + name: &str, constructor: G, - ) -> impl FnMut(&'a [u8]) -> HwResult<'a, HwServerMessage> + ) -> impl FnMut(&[u8]) -> HwResult where G: Fn(String, Vec) -> 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 { move |i| map(tag(name), |_| msg.clone())(i) }