diff options
Diffstat (limited to 'src/parser/mod.rs')
-rw-r--r-- | src/parser/mod.rs | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..d7e81dc --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,201 @@ +#[cfg(feature = "std")] +use std::{error::Error, fmt}; + +#[cfg(not(feature = "std"))] +use core::fmt; + +use crate::{MacAddr, MacAddr6, MacAddr8}; + +/// An error which can be returned when parsing MAC address. +/// +/// This error is used as the error type for the `FromStr` implementation +/// for [MacAddr6] and [MacAddr8]. +/// +/// [MacAddr6]: ./struct.MacAddr6.html +/// [MacAddr8]: ./struct.MacAddr8.html +#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +pub enum ParseError { + /// Provided string can't be parsed into the given type, + /// because it is either too short or too long. + /// + /// For example, any trailing symbols will result in the error, + /// as in `"12-34-56-78-9A-BC\n"`. + /// + /// This enum member will contain the provided string length when returned. + InvalidLength(usize), + + /// Invalid character occurred in the provided string. + /// + /// Allowed characters are `0123456789abcdefABCDEF-:.`. + /// + /// This enum member will contain the wrong char and it's position when returned. + InvalidCharacter(char, usize), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParseError::InvalidLength(len) => f.write_fmt(format_args!("Invalid length of {} characters", len,)), + ParseError::InvalidCharacter(chr, pos) => { + f.write_fmt(format_args!("Unexpected character '{}' at position {}", chr, pos,)) + } + } + } +} + +#[cfg(feature = "std")] +impl Error for ParseError {} + +#[derive(Debug, Eq, PartialEq)] +enum Delimiter { + Hyphen, + Colon, + Dot, +} + +// Heavily based on the Rust' `std/net/parser.rs` sources. +#[derive(Debug)] +pub struct Parser<'a> { + source: &'a [u8], + pos: usize, + delimiter: Option<Delimiter>, +} + +impl<'a> Parser<'a> { + pub fn new(s: &'a str) -> Parser<'a> { + Parser { + source: s.as_bytes(), + pos: 0, + delimiter: None, + } + } + + fn is_eof(&self) -> bool { + self.pos == self.source.len() + } + + fn move_next(&mut self) { + if !self.is_eof() { + self.pos += 1; + } + } + + fn peek_char(&mut self) -> Option<char> { + if self.is_eof() { + None + } else { + Some(self.source[self.pos] as char) + } + } + + fn read_char(&mut self) -> Result<char, ParseError> { + if self.is_eof() { + Err(ParseError::InvalidLength(self.pos)) + } else { + let r = self.source[self.pos] as char; + self.pos += 1; + Ok(r) + } + } + + fn read_digit(&mut self) -> Result<u8, ParseError> { + let chr = self.read_char()?; + + match chr as u8 { + byte @ b'0'..=b'9' => Ok(byte - b'0'), + byte @ b'a'..=b'f' => Ok(byte - b'a' + 10), + byte @ b'A'..=b'F' => Ok(byte - b'A' + 10), + _ => Err(ParseError::InvalidCharacter(chr, self.pos)), + } + } + + fn probe_delimiter(&mut self) -> Result<Option<()>, ParseError> { + match self.peek_char() { + Some('-') if self.delimiter.is_none() => { + self.delimiter = Some(Delimiter::Hyphen); + Ok(Some(())) + } + Some('-') if self.delimiter != Some(Delimiter::Hyphen) => Err(ParseError::InvalidCharacter('-', self.pos)), + Some('-') => Ok(Some(())), + + Some(':') if self.delimiter.is_none() => { + self.delimiter = Some(Delimiter::Colon); + Ok(Some(())) + } + Some(':') if self.delimiter != Some(Delimiter::Colon) => Err(ParseError::InvalidCharacter(':', self.pos)), + Some(':') => Ok(Some(())), + + Some('.') if self.delimiter.is_none() => { + self.delimiter = Some(Delimiter::Dot); + Ok(Some(())) + } + Some('.') if self.delimiter != Some(Delimiter::Dot) => Err(ParseError::InvalidCharacter('.', self.pos)), + Some('.') => Ok(Some(())), + _ => Ok(None), + } + } + + pub fn read_v6_addr(&mut self) -> Result<MacAddr6, ParseError> { + let mut bytes = [0; 6]; + let mut i = 0; + + while i < 6 { + if self.probe_delimiter()?.is_some() { + self.move_next(); + } + + let mut digit = self.read_digit()? * 16; + digit += self.read_digit()?; + + bytes[i] = digit; + + i += 1; + } + + if self.is_eof() { + Ok(MacAddr6::from(bytes)) + } else { + Err(ParseError::InvalidLength(self.source.len())) + } + } + + pub fn read_v8_addr(&mut self) -> Result<MacAddr8, ParseError> { + let mut bytes = [0; 8]; + let mut i = 0; + + while i < 8 { + if self.probe_delimiter()?.is_some() { + self.move_next(); + } + + let mut digit = self.read_digit()? * 16; + digit += self.read_digit()?; + + bytes[i] = digit; + + i += 1; + } + + if self.is_eof() { + Ok(MacAddr8::from(bytes)) + } else { + Err(ParseError::InvalidLength(self.source.len())) + } + } + + pub fn read_addr(&mut self) -> Result<MacAddr, ParseError> { + match self.read_v6_addr() { + Ok(addr) => return Ok(addr.into()), + Err(err @ ParseError::InvalidCharacter(..)) => return Err(err), + Err(ParseError::InvalidLength(..)) => {} + } + + // Rolling back to the start. + self.pos = 0; + + self.read_v8_addr().map(Into::into) + } +} + +#[cfg(test)] +mod tests; |