- Update hedgewars-network-protocol library with messages needed for checker
authorunc0rr
Wed, 30 Jun 2021 23:06:54 +0200
changeset 15811 a855f32ab3ca
parent 15810 ee84e417d8d0
child 15812 8c39a11f7756
- Update hedgewars-network-protocol library with messages needed for checker - Use the library in hedgewars-checker
rust/hedgewars-checker/Cargo.toml
rust/hedgewars-checker/src/main.rs
rust/hedgewars-network-protocol/src/messages.rs
rust/hedgewars-network-protocol/src/parser.rs
rust/hedgewars-network-protocol/tests/test.rs
--- a/rust/hedgewars-checker/Cargo.toml	Wed Jun 30 00:18:53 2021 +0200
+++ b/rust/hedgewars-checker/Cargo.toml	Wed Jun 30 23:06:54 2021 +0200
@@ -5,11 +5,13 @@
 edition = "2018"
 
 [dependencies]
-rust-ini = "0.13"
-dirs = "1.0"
+rust-ini = "0.17"
+dirs = "3.0"
 argparse = "0.2.2"
 log = "0.4"
-stderrlog = "0.4"
+stderrlog = "0.5"
 netbuf = "0.4"
 tempfile = "3.0"
-base64 = "0.9.3"
+base64 = "0.13"
+hedgewars-network-protocol = { path = "../hedgewars-network-protocol" }
+anyhow = "1.0"
--- a/rust/hedgewars-checker/src/main.rs	Wed Jun 30 00:18:53 2021 +0200
+++ b/rust/hedgewars-checker/src/main.rs	Wed Jun 30 23:06:54 2021 +0200
@@ -1,32 +1,17 @@
+use anyhow::{bail, Result};
 use argparse::{ArgumentParser, Store};
+use hedgewars_network_protocol::{
+    messages::HwProtocolMessage as ClientMessage, messages::HwServerMessage::*, parser,
+};
 use ini::Ini;
+use log::{debug, info, warn};
 use netbuf::Buf;
-use log::{debug, warn, info};
-use std::{
-    io::Write,
-    net::TcpStream,
-    process::Command,
-    str::FromStr
-};
-
-type CheckError = Box<std::error::Error>;
+use std::{io::Write, net::TcpStream, process::Command, str::FromStr};
 
-fn extract_packet(buf: &mut Buf) -> Option<netbuf::Buf> {
-    let packet_end = (&buf[..]).windows(2).position(|window| window == b"\n\n")?;
-
-    let mut tail = buf.split_off(packet_end);
-
-    std::mem::swap(&mut tail, buf);
-
-    buf.consume(2);
-
-    Some(tail)
-}
-
-fn check(executable: &str, data_prefix: &str, buffer: &[u8]) -> Result<Vec<Vec<u8>>, CheckError> {
+fn check(executable: &str, data_prefix: &str, buffer: &[String]) -> Result<Vec<String>> {
     let mut replay = tempfile::NamedTempFile::new()?;
 
-    for line in buffer.split(|b| *b == '\n' as u8) {
+    for line in buffer.into_iter() {
         replay.write(&base64::decode(line)?)?;
     }
 
@@ -57,31 +42,31 @@
 
     loop {
         match engine_lines.next() {
-            Some(b"DRAW") => result.push(b"DRAW".to_vec()),
+            Some(b"DRAW") => result.push("DRAW".to_owned()),
             Some(b"WINNERS") => {
-                result.push(b"WINNERS".to_vec());
+                result.push("WINNERS".to_owned());
                 let winners = engine_lines.next().unwrap();
                 let winners_num = u32::from_str(&String::from_utf8(winners.to_vec())?)?;
-                result.push(winners.to_vec());
+                result.push(String::from_utf8(winners.to_vec())?);
 
                 for _i in 0..winners_num {
-                    result.push(engine_lines.next().unwrap().to_vec());
+                    result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?);
                 }
             }
             Some(b"GHOST_POINTS") => {
-                result.push(b"GHOST_POINTS".to_vec());
+                result.push("GHOST_POINTS".to_owned());
                 let points = engine_lines.next().unwrap();
                 let points_num = u32::from_str(&String::from_utf8(points.to_vec())?)? * 2;
-                result.push(points.to_vec());
+                result.push(String::from_utf8(points.to_vec())?);
 
                 for _i in 0..points_num {
-                    result.push(engine_lines.next().unwrap().to_vec());
+                    result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?);
                 }
             }
             Some(b"ACHIEVEMENT") => {
-                result.push(b"ACHIEVEMENT".to_vec());
+                result.push("ACHIEVEMENT".to_owned());
                 for _i in 0..4 {
-                    result.push(engine_lines.next().unwrap().to_vec());
+                    result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?);
                 }
             }
             _ => break,
@@ -91,17 +76,17 @@
     if result.len() > 0 {
         Ok(result)
     } else {
-        Err("no data from engine".into())
+        bail!("no data from engine")
     }
 }
 
 fn connect_and_run(
     username: &str,
     password: &str,
-    protocol_number: u32,
+    protocol_number: u16,
     executable: &str,
     data_prefix: &str,
-) -> Result<(), CheckError> {
+) -> Result<()> {
     info!("Connecting...");
 
     let mut stream = TcpStream::connect("hedgewars.org:46631")?;
@@ -112,70 +97,93 @@
     loop {
         buf.read_from(&mut stream)?;
 
-        while let Some(msg) = extract_packet(&mut buf) {
-            if msg[..].starts_with(b"CONNECTED") {
-                info!("Connected");
-                let p = format!(
-                    "CHECKER\n{}\n{}\n{}\n\n",
-                    protocol_number, username, password
-                );
-                stream.write(p.as_bytes())?;
-            } else if msg[..].starts_with(b"PING") {
-                stream.write(b"PONG\n\n")?;
-            } else if msg[..].starts_with(b"LOGONPASSED") {
-                info!("Logged in");
-                stream.write(b"READY\n\n")?;
-            } else if msg[..].starts_with(b"REPLAY") {
-                info!("Got a replay");
-                match check(executable, data_prefix, &msg[7..]) {
-                    Ok(result) => {
-                        info!("Checked");
-                        debug!(
-                            "Check result: [{}]",
-                            String::from_utf8_lossy(&result.join(&(',' as u8)))
-                        );
+        while let Ok((tail, msg)) = parser::server_message(buf.as_ref()) {
+            buf.consume(buf.len() - tail.len());
 
-                        stream.write(b"CHECKED\nOK\n")?;
-                        stream.write(&result.join(&('\n' as u8)))?;
-                        stream.write(b"\n\nREADY\n\n")?;
-                    }
-                    Err(e) => {
-                        info!("Check failed: {:?}", e);
-                        stream.write(b"CHECKED\nFAIL\nerror\n\nREADY\n\n")?;
+            match msg {
+                Connected(_, _) => {
+                    info!("Connected");
+                    stream.write(
+                        ClientMessage::Checker(
+                            protocol_number,
+                            username.to_owned(),
+                            password.to_owned(),
+                        )
+                        .to_raw_protocol()
+                        .as_bytes(),
+                    )?;
+                }
+                Ping => {
+                    stream.write(ClientMessage::Pong.to_raw_protocol().as_bytes())?;
+                }
+                LogonPassed => {
+                    stream.write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?;
+                }
+                Replay(lines) => {
+                    info!("Got a replay");
+                    match check(executable, data_prefix, &lines) {
+                        Ok(result) => {
+                            info!("Checked");
+                            debug!("Check result: [{:?}]", result);
+
+                            stream.write(
+                                ClientMessage::CheckedOk(result)
+                                    .to_raw_protocol()
+                                    .as_bytes(),
+                            )?;
+                            stream
+                                .write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?;
+                        }
+                        Err(e) => {
+                            info!("Check failed: {:?}", e);
+                            stream.write(
+                                ClientMessage::CheckedFail("error".to_owned())
+                                    .to_raw_protocol()
+                                    .as_bytes(),
+                            )?;
+                            stream
+                                .write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?;
+                        }
                     }
                 }
-            } else if msg[..].starts_with(b"BYE") {
-                warn!("Received BYE: {}", String::from_utf8_lossy(&msg[..]));
-                return Ok(());
-            } else if msg[..].starts_with(b"CHAT") {
-                let body = String::from_utf8_lossy(&msg[5..]);
-                let mut l = body.lines();
-                info!("Chat [{}]: {}", l.next().unwrap(), l.next().unwrap());
-            } else if msg[..].starts_with(b"ROOM") {
-                let body = String::from_utf8_lossy(&msg[5..]);
-                let mut l = body.lines();
-                if let Some(action) = l.next() {
-                    if action == "ADD" {
-                        info!("Room added: {}", l.skip(1).next().unwrap());
+                Bye(message) => {
+                    warn!("Received BYE: {}", message);
+                    return Ok(());
+                }
+                ChatMsg { nick, msg } => {
+                    info!("Chat [{}]: {}", nick, msg);
+                }
+                RoomAdd(fields) => {
+                    let mut l = fields.into_iter();
+                    info!("Room added: {}", l.skip(1).next().unwrap());
+                }
+                RoomUpdated(name, fields) => {
+                    let mut l = fields.into_iter();
+                    let new_name = l.skip(1).next().unwrap();
+
+                    if (name != new_name) {
+                        info!("Room renamed: {}", new_name);
                     }
                 }
-            } else if msg[..].starts_with(b"ERROR") {
-                warn!("Received ERROR: {}", String::from_utf8_lossy(&msg[..]));
-                return Ok(());
-            } else {
-                warn!(
-                    "Unknown protocol command: {}",
-                    String::from_utf8_lossy(&msg[..])
-                )
+                RoomRemove(_) => {
+                    // ignore
+                }
+                Error(message) => {
+                    warn!("Received ERROR: {}", message);
+                    return Ok(());
+                }
+                something => {
+                    warn!("Unexpected protocol command: {:?}", something)
+                }
             }
         }
     }
 }
 
-fn get_protocol_number(executable: &str) -> std::io::Result<u32> {
+fn get_protocol_number(executable: &str) -> std::io::Result<u16> {
     let output = Command::new(executable).arg("--protocol").output()?;
 
-    Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
+    Ok(u16::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
 }
 
 fn main() {
@@ -214,23 +222,3 @@
 
     connect_and_run(&username, &password, protocol_number, &exe, &prefix).unwrap();
 }
-
-#[cfg(test)]
-#[test]
-fn test() {
-    let mut buf = Buf::new();
-    buf.extend(b"Hell");
-    if let Some(_) = extract_packet(&mut buf) {
-        assert!(false)
-    }
-
-    buf.extend(b"o\n\nWorld");
-
-    let packet2 = extract_packet(&mut buf).unwrap();
-    assert_eq!(&buf[..], b"World");
-    assert_eq!(&packet2[..], b"Hello");
-
-    if let Some(_) = extract_packet(&mut buf) {
-        assert!(false)
-    }
-}
--- a/rust/hedgewars-network-protocol/src/messages.rs	Wed Jun 30 00:18:53 2021 +0200
+++ b/rust/hedgewars-network-protocol/src/messages.rs	Wed Jun 30 23:06:54 2021 +0200
@@ -62,6 +62,9 @@
     Delete(String),
     SaveRoom(String),
     LoadRoom(String),
+    CheckerReady,
+    CheckedOk(Vec<String>),
+    CheckedFail(String),
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -152,6 +155,8 @@
     Warning(String),
     Error(String),
 
+    Replay(Vec<String>),
+
     //Deprecated messages
     LegacyReady(bool, Vec<String>),
 }
@@ -352,6 +357,9 @@
             Delete(name) => msg!["CMD", format!("DELETE {}", name)],
             SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)],
             LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)],
+            CheckerReady => msg!["READY"],
+            CheckedOk(args) => msg!["CHECKED", "OK", args.join("\n")],
+            CheckedFail(message) => msg!["CHECKED", "FAIL", message],
         }
     }
 }
@@ -405,6 +413,7 @@
             Warning(msg) => msg!["WARNING", msg],
             Error(msg) => msg!["ERROR", msg],
             ReplayStart => msg!["REPLAY_START"],
+            Replay(em) => construct_message(&["REPLAY"], &em),
 
             LegacyReady(is_ready, nicks) => {
                 construct_message(&[if *is_ready { "READY" } else { "NOT_READY" }], &nicks)
--- a/rust/hedgewars-network-protocol/src/parser.rs	Wed Jun 30 00:18:53 2021 +0200
+++ b/rust/hedgewars-network-protocol/src/parser.rs	Wed Jun 30 23:06:54 2021 +0200
@@ -200,6 +200,7 @@
         message("TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
         message("TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
         message("TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
+        message("READY", CheckerReady),
     ))(input)
 }
 
@@ -231,6 +232,7 @@
         message("ROUNDFINISHED", opt_arg, |_| RoundFinished),
         message("PROTO\n", u16_line, Proto),
         message("QUIT", opt_arg, Quit),
+        message("CHECKED\nFAIL\n", a_line, CheckedFail),
     ))(input)
 }
 
@@ -482,7 +484,17 @@
                 |(nick, reason, time)| BanNick(nick, reason, time),
             ),
         ),
-    ))(input)
+        map(
+            preceded(
+                tag("CHECKED\nOK"),
+                alt((
+                    map(peek(end_of_message), |_| None),
+                    map(preceded(newline, separated_list0(newline, a_line)), Some),
+                )),
+            ),
+            |values| CheckedOk(values.unwrap_or_default()),
+        )
+))(input)
 }
 
 pub fn malformed_message(input: &[u8]) -> HwResult<()> {
@@ -653,6 +665,7 @@
                 list_message("EM", ForwardEngineMessage),
                 list_message("INFO", Info),
                 list_message("SERVER_VARS", ServerVars),
+                list_message("REPLAY", Replay),
             )),
         )),
         end_of_message,
--- a/rust/hedgewars-network-protocol/tests/test.rs	Wed Jun 30 00:18:53 2021 +0200
+++ b/rust/hedgewars-network-protocol/tests/test.rs	Wed Jun 30 23:06:54 2021 +0200
@@ -14,7 +14,7 @@
 pub fn gen_proto_msg() -> BoxedStrategy<HwProtocolMessage> where {
     use hedgewars_network_protocol::messages::HwProtocolMessage::*;
 
-    let res = (0..=55).no_shrink().prop_flat_map(|i| {
+    let res = (0..=58).no_shrink().prop_flat_map(|i| {
         proto_msg_match!(i, def = Ping,
             0 => Ping(),
             1 => Pong(),
@@ -70,7 +70,10 @@
             52 => Save(Ascii, Ascii),
             53 => Delete(Ascii),
             54 => SaveRoom(Ascii),
-            55 => LoadRoom(Ascii)
+            55 => LoadRoom(Ascii),
+            56 => CheckerReady(),
+            57 => CheckedOk(Vec<Ascii>),
+            58 => CheckedFail(Ascii)
         )
     });
     res.boxed()
@@ -79,7 +82,7 @@
 pub fn gen_server_msg() -> BoxedStrategy<HwServerMessage> where {
     use hedgewars_network_protocol::messages::HwServerMessage::*;
 
-    let res = (0..=55).no_shrink().prop_flat_map(|i| {
+    let res = (0..=38).no_shrink().prop_flat_map(|i| {
         proto_msg_match!(i, def = Ping,
                     0 => Connected(Ascii, u32),
                     1 => Redirect(u16),
@@ -118,7 +121,8 @@
                     34 => ServerVars(Vec<Ascii>),
                     35 => Notice(Ascii),
                     36 => Warning(Ascii),
-                    37 => Error(Ascii)
+                    37 => Error(Ascii),
+                    38 => Replay(Vec<Ascii>)
                 )
     });
     res.boxed()