From eec1c5360b410b5474c9645b07bc273bb3034625 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Tue, 28 Apr 2020 13:17:13 +0200 Subject: restructure how agents are constructed The old function turned a bit into a mess, so the functionality is now split up. --- src/lib.rs | 123 +++++++++++++++++++++++++++++++-------------------------- src/raw/mod.rs | 42 ++++++++++++++++++++ 2 files changed, 109 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 72b35ef..05f9f2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ pub enum EvtcError { #[error("invalid elite specialization id: {0}")] InvalidEliteSpec(u32), #[error("utf8 decoding error: {0}")] - Utf8Error(#[from] std::string::FromUtf8Error), + Utf8Error(#[from] std::str::Utf8Error), } /// Player-specific agent data. @@ -113,6 +113,71 @@ pub enum AgentKind { } impl AgentKind { + fn from_raw_character(raw_agent: &raw::Agent) -> Result { + assert!(raw_agent.is_character()); + let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; + Ok(Character { + id: raw_agent.prof as u16, + name: name.to_str()?.to_owned(), + }) + } + + fn from_raw_gadget(raw_agent: &raw::Agent) -> Result { + assert!(raw_agent.is_gadget()); + let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; + Ok(Gadget { + id: raw_agent.prof as u16, + name: name.to_str()?.to_owned(), + }) + } + + fn from_raw_player(raw_agent: &raw::Agent) -> Result { + assert!(raw_agent.is_player()); + let character_name = raw::cstr_up_to_nul(&raw_agent.name) + .ok_or(EvtcError::InvalidData)? + .to_str()?; + let account_name = raw::cstr_up_to_nul(&raw_agent.name[character_name.len() + 1..]) + .ok_or(EvtcError::InvalidData)? + .to_str()?; + let subgroup = raw_agent.name[character_name.len() + account_name.len() + 2] - b'0'; + let elite = if raw_agent.is_elite == 0 { + None + } else { + Some( + EliteSpec::from_u32(raw_agent.is_elite) + .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?, + ) + }; + Ok(Player { + profession: Profession::from_u32(raw_agent.prof) + .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?, + elite, + character_name: character_name.to_owned(), + account_name: account_name.to_owned(), + subgroup, + }) + } + + /// Extract the correct `AgentKind` from the given [raw agent][raw::Agent]. + /// + /// This automatically discerns between player, gadget and characters. + /// + /// Note that in most cases, you probably want to use [`Agent::from_raw`][Agent::from_raw] or + /// even [`process`][process] instead of this function. + pub fn from_raw(raw_agent: &raw::Agent) -> Result { + if raw_agent.is_character() { + Ok(AgentKind::Character(AgentKind::from_raw_character( + raw_agent, + )?)) + } else if raw_agent.is_gadget() { + Ok(AgentKind::Gadget(AgentKind::from_raw_gadget(raw_agent)?)) + } else if raw_agent.is_player() { + Ok(AgentKind::Player(AgentKind::from_raw_player(raw_agent)?)) + } else { + Err(EvtcError::InvalidData) + } + } + /// Accesses the inner [`Player`][Player] struct, if available. pub fn as_player(&self) -> Option<&Player> { if let AgentKind::Player(ref player) = *self { @@ -268,61 +333,7 @@ pub struct Agent { impl Agent { /// Parse a raw agent. pub fn from_raw(raw_agent: &raw::Agent) -> Result { - let kind = if raw_agent.is_character() || raw_agent.is_gadget() { - let name = String::from_utf8( - raw_agent - .name - .iter() - .cloned() - .take_while(|c| *c != 0) - .collect::>(), - )?; - if raw_agent.is_character() { - AgentKind::Character(Character { - id: raw_agent.prof as u16, - name, - }) - } else { - AgentKind::Gadget(Gadget { - id: raw_agent.prof as u16, - name, - }) - } - } else if raw_agent.is_player() { - let first = raw_agent - .name - .iter() - .cloned() - .take_while(|c| *c != 0) - .collect::>(); - let second = raw_agent - .name - .iter() - .cloned() - .skip(first.len() + 1) - .take_while(|c| *c != 0) - .collect::>(); - let third = raw_agent.name[first.len() + second.len() + 2] - b'0'; - let elite = if raw_agent.is_elite == 0 { - None - } else { - Some( - EliteSpec::from_u32(raw_agent.is_elite) - .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?, - ) - }; - AgentKind::Player(Player { - profession: Profession::from_u32(raw_agent.prof) - .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?, - elite, - character_name: String::from_utf8(first)?, - account_name: String::from_utf8(second)?, - subgroup: third, - }) - } else { - return Err(EvtcError::InvalidData); - }; - + let kind = AgentKind::from_raw(raw_agent)?; Ok(Agent { addr: raw_agent.addr, kind, diff --git a/src/raw/mod.rs b/src/raw/mod.rs index 7451b5a..50ad9b4 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -18,6 +18,7 @@ pub mod parser; pub use self::parser::{parse_file, Evtc, ParseError, ParseResult}; use std::io::{Read, Seek, BufReader}; +use std::ffi::CStr; /// Parse a complete log that was compressed as a zip file. pub fn parse_zip(input: &mut T) -> ParseResult { @@ -25,3 +26,44 @@ pub fn parse_zip(input: &mut T) -> ParseResult { let mut file = BufReader::new(archive.by_index(0)?); parse_file(&mut file) } + +/// Return a [`CStr`][CStr] up to the first nul byte. +/// +/// This is different to [`CStr::from_bytes_with_nul`][CStr::from_bytes_with_nul] in that it stops +/// at the first nul byte instead of raising an error. +/// +/// If the slice does not end with a nul byte, this function returns `None`. +pub fn cstr_up_to_nul(bytes: &[u8]) -> Option<&CStr> { + let index = bytes.iter().position(|c| *c == 0)?; + CStr::from_bytes_with_nul(&bytes[..index + 1]).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cstr_up_to_nul_terminated() { + let bytes = b"foo\0"; + let cstr = cstr_up_to_nul(bytes).unwrap(); + assert_eq!(cstr.to_bytes(), b"foo"); + } + + #[test] + fn test_cstr_up_to_nul_multiple() { + let bytes = b"foo\0\0\0"; + let cstr = cstr_up_to_nul(bytes).unwrap(); + assert_eq!(cstr.to_bytes(), b"foo"); + + let bytes = b"foo\0bar\0\0"; + let cstr = cstr_up_to_nul(bytes).unwrap(); + assert_eq!(cstr.to_bytes(), b"foo"); + } + + #[test] + fn test_cstr_up_to_nul_unterminated() { + let bytes = b"foo"; + let cstr = cstr_up_to_nul(bytes); + assert!(cstr.is_none()); + } +} -- cgit v1.2.3