blob: d7e81dc3af595c85304e09129d1c3716a902f656 [file] [log] [blame]
#[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;