# HG changeset patch # User unc0rr # Date 1625087214 -7200 # Node ID a855f32ab3ca76896b33aacc32eab792e7f8a3a9 # Parent ee84e417d8d0258e9e8d609505554297426d78b9 - Update hedgewars-network-protocol library with messages needed for checker - Use the library in hedgewars-checker diff -r ee84e417d8d0 -r a855f32ab3ca rust/hedgewars-checker/Cargo.toml --- 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" diff -r ee84e417d8d0 -r a855f32ab3ca rust/hedgewars-checker/src/main.rs --- 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; +use std::{io::Write, net::TcpStream, process::Command, str::FromStr}; -fn extract_packet(buf: &mut Buf) -> Option { - 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>, CheckError> { +fn check(executable: &str, data_prefix: &str, buffer: &[String]) -> Result> { 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 { +fn get_protocol_number(executable: &str) -> std::io::Result { 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) - } -} diff -r ee84e417d8d0 -r a855f32ab3ca rust/hedgewars-network-protocol/src/messages.rs --- 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), + CheckedFail(String), } #[derive(Debug, Clone, Copy)] @@ -152,6 +155,8 @@ Warning(String), Error(String), + Replay(Vec), + //Deprecated messages LegacyReady(bool, Vec), } @@ -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) diff -r ee84e417d8d0 -r a855f32ab3ca rust/hedgewars-network-protocol/src/parser.rs --- 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, diff -r ee84e417d8d0 -r a855f32ab3ca rust/hedgewars-network-protocol/tests/test.rs --- 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 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), + 58 => CheckedFail(Ascii) ) }); res.boxed() @@ -79,7 +82,7 @@ pub fn gen_server_msg() -> BoxedStrategy 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), 35 => Notice(Ascii), 36 => Warning(Ascii), - 37 => Error(Ascii) + 37 => Error(Ascii), + 38 => Replay(Vec) ) }); res.boxed()