diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2020-04-28 13:17:13 +0200 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2020-04-28 13:17:13 +0200 | 
| commit | eec1c5360b410b5474c9645b07bc273bb3034625 (patch) | |
| tree | 8b831eda6aa1be1ec4538326aa942c817109ea49 /src | |
| parent | 84cba68af3d58573aba22b132ed4c3a1c5ccfaec (diff) | |
| download | evtclib-eec1c5360b410b5474c9645b07bc273bb3034625.tar.gz evtclib-eec1c5360b410b5474c9645b07bc273bb3034625.tar.bz2 evtclib-eec1c5360b410b5474c9645b07bc273bb3034625.zip | |
restructure how agents are constructed
The old function turned a bit into a mess, so the functionality is now
split up.
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 123 | ||||
| -rw-r--r-- | src/raw/mod.rs | 42 | 
2 files changed, 109 insertions, 56 deletions
| @@ -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<Character, EvtcError> { +        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<Gadget, EvtcError> { +        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<Player, EvtcError> { +        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<AgentKind, EvtcError> { +        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<Kind = ()> {  impl Agent {      /// Parse a raw agent.      pub fn from_raw(raw_agent: &raw::Agent) -> Result<Agent, EvtcError> { -        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::<Vec<_>>(), -            )?; -            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::<Vec<_>>(); -            let second = raw_agent -                .name -                .iter() -                .cloned() -                .skip(first.len() + 1) -                .take_while(|c| *c != 0) -                .collect::<Vec<_>>(); -            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<T: Read + Seek>(input: &mut T) -> ParseResult<Evtc> { @@ -25,3 +26,44 @@ pub fn parse_zip<T: Read + Seek>(input: &mut T) -> ParseResult<Evtc> {      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()); +    } +} | 
