aboutsummaryrefslogtreecommitdiff
path: root/src/parser/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser/mod.rs')
-rw-r--r--src/parser/mod.rs201
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;