#[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, } 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 { if self.is_eof() { None } else { Some(self.source[self.pos] as char) } } fn read_char(&mut self) -> Result { 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 { 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, 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 { 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 { 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 { 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;