rust/hedgewars-checker/src/main.rs
changeset 15811 a855f32ab3ca
parent 14396 694c96fb3a06
child 15812 8c39a11f7756
equal deleted inserted replaced
15810:ee84e417d8d0 15811:a855f32ab3ca
       
     1 use anyhow::{bail, Result};
     1 use argparse::{ArgumentParser, Store};
     2 use argparse::{ArgumentParser, Store};
       
     3 use hedgewars_network_protocol::{
       
     4     messages::HwProtocolMessage as ClientMessage, messages::HwServerMessage::*, parser,
       
     5 };
     2 use ini::Ini;
     6 use ini::Ini;
       
     7 use log::{debug, info, warn};
     3 use netbuf::Buf;
     8 use netbuf::Buf;
     4 use log::{debug, warn, info};
     9 use std::{io::Write, net::TcpStream, process::Command, str::FromStr};
     5 use std::{
    10 
     6     io::Write,
    11 fn check(executable: &str, data_prefix: &str, buffer: &[String]) -> Result<Vec<String>> {
     7     net::TcpStream,
       
     8     process::Command,
       
     9     str::FromStr
       
    10 };
       
    11 
       
    12 type CheckError = Box<std::error::Error>;
       
    13 
       
    14 fn extract_packet(buf: &mut Buf) -> Option<netbuf::Buf> {
       
    15     let packet_end = (&buf[..]).windows(2).position(|window| window == b"\n\n")?;
       
    16 
       
    17     let mut tail = buf.split_off(packet_end);
       
    18 
       
    19     std::mem::swap(&mut tail, buf);
       
    20 
       
    21     buf.consume(2);
       
    22 
       
    23     Some(tail)
       
    24 }
       
    25 
       
    26 fn check(executable: &str, data_prefix: &str, buffer: &[u8]) -> Result<Vec<Vec<u8>>, CheckError> {
       
    27     let mut replay = tempfile::NamedTempFile::new()?;
    12     let mut replay = tempfile::NamedTempFile::new()?;
    28 
    13 
    29     for line in buffer.split(|b| *b == '\n' as u8) {
    14     for line in buffer.into_iter() {
    30         replay.write(&base64::decode(line)?)?;
    15         replay.write(&base64::decode(line)?)?;
    31     }
    16     }
    32 
    17 
    33     let temp_file_path = replay.path();
    18     let temp_file_path = replay.path();
    34 
    19 
    55         .split(|b| *b == '\n' as u8)
    40         .split(|b| *b == '\n' as u8)
    56         .skip_while(|l| *l != b"WINNERS" && *l != b"DRAW");
    41         .skip_while(|l| *l != b"WINNERS" && *l != b"DRAW");
    57 
    42 
    58     loop {
    43     loop {
    59         match engine_lines.next() {
    44         match engine_lines.next() {
    60             Some(b"DRAW") => result.push(b"DRAW".to_vec()),
    45             Some(b"DRAW") => result.push("DRAW".to_owned()),
    61             Some(b"WINNERS") => {
    46             Some(b"WINNERS") => {
    62                 result.push(b"WINNERS".to_vec());
    47                 result.push("WINNERS".to_owned());
    63                 let winners = engine_lines.next().unwrap();
    48                 let winners = engine_lines.next().unwrap();
    64                 let winners_num = u32::from_str(&String::from_utf8(winners.to_vec())?)?;
    49                 let winners_num = u32::from_str(&String::from_utf8(winners.to_vec())?)?;
    65                 result.push(winners.to_vec());
    50                 result.push(String::from_utf8(winners.to_vec())?);
    66 
    51 
    67                 for _i in 0..winners_num {
    52                 for _i in 0..winners_num {
    68                     result.push(engine_lines.next().unwrap().to_vec());
    53                     result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?);
    69                 }
    54                 }
    70             }
    55             }
    71             Some(b"GHOST_POINTS") => {
    56             Some(b"GHOST_POINTS") => {
    72                 result.push(b"GHOST_POINTS".to_vec());
    57                 result.push("GHOST_POINTS".to_owned());
    73                 let points = engine_lines.next().unwrap();
    58                 let points = engine_lines.next().unwrap();
    74                 let points_num = u32::from_str(&String::from_utf8(points.to_vec())?)? * 2;
    59                 let points_num = u32::from_str(&String::from_utf8(points.to_vec())?)? * 2;
    75                 result.push(points.to_vec());
    60                 result.push(String::from_utf8(points.to_vec())?);
    76 
    61 
    77                 for _i in 0..points_num {
    62                 for _i in 0..points_num {
    78                     result.push(engine_lines.next().unwrap().to_vec());
    63                     result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?);
    79                 }
    64                 }
    80             }
    65             }
    81             Some(b"ACHIEVEMENT") => {
    66             Some(b"ACHIEVEMENT") => {
    82                 result.push(b"ACHIEVEMENT".to_vec());
    67                 result.push("ACHIEVEMENT".to_owned());
    83                 for _i in 0..4 {
    68                 for _i in 0..4 {
    84                     result.push(engine_lines.next().unwrap().to_vec());
    69                     result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?);
    85                 }
    70                 }
    86             }
    71             }
    87             _ => break,
    72             _ => break,
    88         }
    73         }
    89     }
    74     }
    90 
    75 
    91     if result.len() > 0 {
    76     if result.len() > 0 {
    92         Ok(result)
    77         Ok(result)
    93     } else {
    78     } else {
    94         Err("no data from engine".into())
    79         bail!("no data from engine")
    95     }
    80     }
    96 }
    81 }
    97 
    82 
    98 fn connect_and_run(
    83 fn connect_and_run(
    99     username: &str,
    84     username: &str,
   100     password: &str,
    85     password: &str,
   101     protocol_number: u32,
    86     protocol_number: u16,
   102     executable: &str,
    87     executable: &str,
   103     data_prefix: &str,
    88     data_prefix: &str,
   104 ) -> Result<(), CheckError> {
    89 ) -> Result<()> {
   105     info!("Connecting...");
    90     info!("Connecting...");
   106 
    91 
   107     let mut stream = TcpStream::connect("hedgewars.org:46631")?;
    92     let mut stream = TcpStream::connect("hedgewars.org:46631")?;
   108     stream.set_nonblocking(false)?;
    93     stream.set_nonblocking(false)?;
   109 
    94 
   110     let mut buf = Buf::new();
    95     let mut buf = Buf::new();
   111 
    96 
   112     loop {
    97     loop {
   113         buf.read_from(&mut stream)?;
    98         buf.read_from(&mut stream)?;
   114 
    99 
   115         while let Some(msg) = extract_packet(&mut buf) {
   100         while let Ok((tail, msg)) = parser::server_message(buf.as_ref()) {
   116             if msg[..].starts_with(b"CONNECTED") {
   101             buf.consume(buf.len() - tail.len());
   117                 info!("Connected");
   102 
   118                 let p = format!(
   103             match msg {
   119                     "CHECKER\n{}\n{}\n{}\n\n",
   104                 Connected(_, _) => {
   120                     protocol_number, username, password
   105                     info!("Connected");
   121                 );
   106                     stream.write(
   122                 stream.write(p.as_bytes())?;
   107                         ClientMessage::Checker(
   123             } else if msg[..].starts_with(b"PING") {
   108                             protocol_number,
   124                 stream.write(b"PONG\n\n")?;
   109                             username.to_owned(),
   125             } else if msg[..].starts_with(b"LOGONPASSED") {
   110                             password.to_owned(),
   126                 info!("Logged in");
   111                         )
   127                 stream.write(b"READY\n\n")?;
   112                         .to_raw_protocol()
   128             } else if msg[..].starts_with(b"REPLAY") {
   113                         .as_bytes(),
   129                 info!("Got a replay");
   114                     )?;
   130                 match check(executable, data_prefix, &msg[7..]) {
   115                 }
   131                     Ok(result) => {
   116                 Ping => {
   132                         info!("Checked");
   117                     stream.write(ClientMessage::Pong.to_raw_protocol().as_bytes())?;
   133                         debug!(
   118                 }
   134                             "Check result: [{}]",
   119                 LogonPassed => {
   135                             String::from_utf8_lossy(&result.join(&(',' as u8)))
   120                     stream.write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?;
   136                         );
   121                 }
   137 
   122                 Replay(lines) => {
   138                         stream.write(b"CHECKED\nOK\n")?;
   123                     info!("Got a replay");
   139                         stream.write(&result.join(&('\n' as u8)))?;
   124                     match check(executable, data_prefix, &lines) {
   140                         stream.write(b"\n\nREADY\n\n")?;
   125                         Ok(result) => {
       
   126                             info!("Checked");
       
   127                             debug!("Check result: [{:?}]", result);
       
   128 
       
   129                             stream.write(
       
   130                                 ClientMessage::CheckedOk(result)
       
   131                                     .to_raw_protocol()
       
   132                                     .as_bytes(),
       
   133                             )?;
       
   134                             stream
       
   135                                 .write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?;
       
   136                         }
       
   137                         Err(e) => {
       
   138                             info!("Check failed: {:?}", e);
       
   139                             stream.write(
       
   140                                 ClientMessage::CheckedFail("error".to_owned())
       
   141                                     .to_raw_protocol()
       
   142                                     .as_bytes(),
       
   143                             )?;
       
   144                             stream
       
   145                                 .write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?;
       
   146                         }
   141                     }
   147                     }
   142                     Err(e) => {
   148                 }
   143                         info!("Check failed: {:?}", e);
   149                 Bye(message) => {
   144                         stream.write(b"CHECKED\nFAIL\nerror\n\nREADY\n\n")?;
   150                     warn!("Received BYE: {}", message);
       
   151                     return Ok(());
       
   152                 }
       
   153                 ChatMsg { nick, msg } => {
       
   154                     info!("Chat [{}]: {}", nick, msg);
       
   155                 }
       
   156                 RoomAdd(fields) => {
       
   157                     let mut l = fields.into_iter();
       
   158                     info!("Room added: {}", l.skip(1).next().unwrap());
       
   159                 }
       
   160                 RoomUpdated(name, fields) => {
       
   161                     let mut l = fields.into_iter();
       
   162                     let new_name = l.skip(1).next().unwrap();
       
   163 
       
   164                     if (name != new_name) {
       
   165                         info!("Room renamed: {}", new_name);
   145                     }
   166                     }
   146                 }
   167                 }
   147             } else if msg[..].starts_with(b"BYE") {
   168                 RoomRemove(_) => {
   148                 warn!("Received BYE: {}", String::from_utf8_lossy(&msg[..]));
   169                     // ignore
   149                 return Ok(());
   170                 }
   150             } else if msg[..].starts_with(b"CHAT") {
   171                 Error(message) => {
   151                 let body = String::from_utf8_lossy(&msg[5..]);
   172                     warn!("Received ERROR: {}", message);
   152                 let mut l = body.lines();
   173                     return Ok(());
   153                 info!("Chat [{}]: {}", l.next().unwrap(), l.next().unwrap());
   174                 }
   154             } else if msg[..].starts_with(b"ROOM") {
   175                 something => {
   155                 let body = String::from_utf8_lossy(&msg[5..]);
   176                     warn!("Unexpected protocol command: {:?}", something)
   156                 let mut l = body.lines();
   177                 }
   157                 if let Some(action) = l.next() {
       
   158                     if action == "ADD" {
       
   159                         info!("Room added: {}", l.skip(1).next().unwrap());
       
   160                     }
       
   161                 }
       
   162             } else if msg[..].starts_with(b"ERROR") {
       
   163                 warn!("Received ERROR: {}", String::from_utf8_lossy(&msg[..]));
       
   164                 return Ok(());
       
   165             } else {
       
   166                 warn!(
       
   167                     "Unknown protocol command: {}",
       
   168                     String::from_utf8_lossy(&msg[..])
       
   169                 )
       
   170             }
   178             }
   171         }
   179         }
   172     }
   180     }
   173 }
   181 }
   174 
   182 
   175 fn get_protocol_number(executable: &str) -> std::io::Result<u32> {
   183 fn get_protocol_number(executable: &str) -> std::io::Result<u16> {
   176     let output = Command::new(executable).arg("--protocol").output()?;
   184     let output = Command::new(executable).arg("--protocol").output()?;
   177 
   185 
   178     Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
   186     Ok(u16::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
   179 }
   187 }
   180 
   188 
   181 fn main() {
   189 fn main() {
   182     stderrlog::new()
   190     stderrlog::new()
   183         .verbosity(3)
   191         .verbosity(3)
   212 
   220 
   213     info!("Using protocol number {}", protocol_number);
   221     info!("Using protocol number {}", protocol_number);
   214 
   222 
   215     connect_and_run(&username, &password, protocol_number, &exe, &prefix).unwrap();
   223     connect_and_run(&username, &password, protocol_number, &exe, &prefix).unwrap();
   216 }
   224 }
   217 
       
   218 #[cfg(test)]
       
   219 #[test]
       
   220 fn test() {
       
   221     let mut buf = Buf::new();
       
   222     buf.extend(b"Hell");
       
   223     if let Some(_) = extract_packet(&mut buf) {
       
   224         assert!(false)
       
   225     }
       
   226 
       
   227     buf.extend(b"o\n\nWorld");
       
   228 
       
   229     let packet2 = extract_packet(&mut buf).unwrap();
       
   230     assert_eq!(&buf[..], b"World");
       
   231     assert_eq!(&packet2[..], b"Hello");
       
   232 
       
   233     if let Some(_) = extract_packet(&mut buf) {
       
   234         assert!(false)
       
   235     }
       
   236 }