- Update hedgewars-network-protocol library with messages needed for checker
- Use the library in hedgewars-checker
--- 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()