#![cfg_attr( not(all(feature = "proto-ipv6", feature = "proto-ipv4")), allow(dead_code) )] use core::result; use core::str::FromStr; #[cfg(feature = "medium-ethernet")] use crate::wire::EthernetAddress; use crate::wire::{IpAddress, IpCidr, IpEndpoint}; #[cfg(feature = "proto-ipv4")] use crate::wire::{Ipv4Address, Ipv4Cidr}; #[cfg(feature = "proto-ipv6")] use crate::wire::{Ipv6Address, Ipv6Cidr}; type Result = result::Result; struct Parser<'a> { data: &'a [u8], pos: usize, } impl<'a> Parser<'a> { fn new(data: &'a str) -> Parser<'a> { Parser { data: data.as_bytes(), pos: 0, } } fn lookahead_char(&self, ch: u8) -> bool { if self.pos < self.data.len() { self.data[self.pos] == ch } else { false } } fn advance(&mut self) -> Result { match self.data.get(self.pos) { Some(&chr) => { self.pos += 1; Ok(chr) } None => Err(()), } } fn try_do(&mut self, f: F) -> Option where F: FnOnce(&mut Parser<'a>) -> Result, { let pos = self.pos; match f(self) { Ok(res) => Some(res), Err(()) => { self.pos = pos; None } } } fn accept_eof(&mut self) -> Result<()> { if self.data.len() == self.pos { Ok(()) } else { Err(()) } } fn until_eof(&mut self, f: F) -> Result where F: FnOnce(&mut Parser<'a>) -> Result, { let res = f(self)?; self.accept_eof()?; Ok(res) } fn accept_char(&mut self, chr: u8) -> Result<()> { if self.advance()? == chr { Ok(()) } else { Err(()) } } fn accept_str(&mut self, string: &[u8]) -> Result<()> { for byte in string.iter() { self.accept_char(*byte)?; } Ok(()) } fn accept_digit(&mut self, hex: bool) -> Result { let digit = self.advance()?; if (b'0'..=b'9').contains(&digit) { Ok(digit - b'0') } else if hex && (b'a'..=b'f').contains(&digit) { Ok(digit - b'a' + 10) } else if hex && (b'A'..=b'F').contains(&digit) { Ok(digit - b'A' + 10) } else { Err(()) } } fn accept_number(&mut self, max_digits: usize, max_value: u32, hex: bool) -> Result { let mut value = self.accept_digit(hex)? as u32; for _ in 1..max_digits { match self.try_do(|p| p.accept_digit(hex)) { Some(digit) => { value *= if hex { 16 } else { 10 }; value += digit as u32; } None => break, } } if value < max_value { Ok(value) } else { Err(()) } } #[cfg(feature = "medium-ethernet")] fn accept_mac_joined_with(&mut self, separator: u8) -> Result { let mut octets = [0u8; 6]; for (n, octet) in octets.iter_mut().enumerate() { *octet = self.accept_number(2, 0x100, true)? as u8; if n != 5 { self.accept_char(separator)?; } } Ok(EthernetAddress(octets)) } #[cfg(feature = "medium-ethernet")] fn accept_mac(&mut self) -> Result { if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b'-')) { return Ok(mac); } if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b':')) { return Ok(mac); } Err(()) } #[cfg(feature = "proto-ipv6")] fn accept_ipv4_mapped_ipv6_part(&mut self, parts: &mut [u16], idx: &mut usize) -> Result<()> { let octets = self.accept_ipv4_octets()?; parts[*idx] = ((octets[0] as u16) << 8) | (octets[1] as u16); *idx += 1; parts[*idx] = ((octets[2] as u16) << 8) | (octets[3] as u16); *idx += 1; Ok(()) } #[cfg(feature = "proto-ipv6")] fn accept_ipv6_part( &mut self, (head, tail): (&mut [u16; 8], &mut [u16; 6]), (head_idx, tail_idx): (&mut usize, &mut usize), mut use_tail: bool, is_cidr: bool, ) -> Result<()> { let double_colon = match self.try_do(|p| p.accept_str(b"::")) { Some(_) if !use_tail && *head_idx < 7 => { // Found a double colon. Start filling out the // tail and set the double colon flag in case // this is the last character we can parse. use_tail = true; true } Some(_) => { // This is a bad address. Only one double colon is // allowed and an address is only 128 bits. return Err(()); } None => { if *head_idx != 0 || use_tail && *tail_idx != 0 { // If this is not the first number or the position following // a double colon, we expect there to be a single colon. self.accept_char(b':')?; } false } }; match self.try_do(|p| p.accept_number(4, 0x10000, true)) { Some(part) if !use_tail && *head_idx < 8 => { // Valid u16 to be added to the address head[*head_idx] = part as u16; *head_idx += 1; if *head_idx == 6 && head[0..*head_idx] == [0, 0, 0, 0, 0, 0xffff] { self.try_do(|p| { p.accept_char(b':')?; p.accept_ipv4_mapped_ipv6_part(head, head_idx) }); } Ok(()) } Some(part) if *tail_idx < 6 => { // Valid u16 to be added to the address tail[*tail_idx] = part as u16; *tail_idx += 1; if *tail_idx == 1 && tail[0] == 0xffff && head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] { self.try_do(|p| { p.accept_char(b':')?; p.accept_ipv4_mapped_ipv6_part(tail, tail_idx) }); } Ok(()) } Some(_) => { // Tail or head section is too long Err(()) } None if double_colon && (is_cidr || self.pos == self.data.len()) => { // The address ends with "::". E.g. 1234:: or :: Ok(()) } None => { // Invalid address Err(()) } }?; if *head_idx + *tail_idx > 8 { // The head and tail indexes add up to a bad address length. Err(()) } else if !self.lookahead_char(b':') { if *head_idx < 8 && !use_tail { // There was no double colon found, and the head is too short return Err(()); } Ok(()) } else { // Continue recursing self.accept_ipv6_part((head, tail), (head_idx, tail_idx), use_tail, is_cidr) } } #[cfg(feature = "proto-ipv6")] fn accept_ipv6(&mut self, is_cidr: bool) -> Result { // IPv6 addresses may contain a "::" to indicate a series of // 16 bit sections that evaluate to 0. E.g. // // fe80:0000:0000:0000:0000:0000:0000:0001 // // May be written as // // fe80::1 // // As a result, we need to find the first section of colon // delimited u16's before a possible "::", then the // possible second section after the "::", and finally // combine the second optional section to the end of the // final address. // // See https://tools.ietf.org/html/rfc4291#section-2.2 // for details. let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]); let (mut head_idx, mut tail_idx) = (0, 0); self.accept_ipv6_part( (&mut addr, &mut tail), (&mut head_idx, &mut tail_idx), false, is_cidr, )?; // We need to copy the tail portion (the portion following the "::") to the // end of the address. addr[8 - tail_idx..].copy_from_slice(&tail[..tail_idx]); Ok(Ipv6Address::from_parts(&addr)) } fn accept_ipv4_octets(&mut self) -> Result<[u8; 4]> { let mut octets = [0u8; 4]; for (n, octet) in octets.iter_mut().enumerate() { *octet = self.accept_number(3, 0x100, false)? as u8; if n != 3 { self.accept_char(b'.')?; } } Ok(octets) } #[cfg(feature = "proto-ipv4")] fn accept_ipv4(&mut self) -> Result { let octets = self.accept_ipv4_octets()?; Ok(Ipv4Address(octets)) } fn accept_ip(&mut self) -> Result { #[cfg(feature = "proto-ipv4")] #[allow(clippy::single_match)] match self.try_do(|p| p.accept_ipv4()) { Some(ipv4) => return Ok(IpAddress::Ipv4(ipv4)), None => (), } #[cfg(feature = "proto-ipv6")] #[allow(clippy::single_match)] match self.try_do(|p| p.accept_ipv6(false)) { Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)), None => (), } Err(()) } #[cfg(feature = "proto-ipv4")] fn accept_ipv4_endpoint(&mut self) -> Result { let ip = self.accept_ipv4()?; let port = if self.accept_eof().is_ok() { 0 } else { self.accept_char(b':')?; self.accept_number(5, 65535, false)? }; Ok(IpEndpoint { addr: IpAddress::Ipv4(ip), port: port as u16, }) } #[cfg(feature = "proto-ipv6")] fn accept_ipv6_endpoint(&mut self) -> Result { if self.lookahead_char(b'[') { self.accept_char(b'[')?; let ip = self.accept_ipv6(false)?; self.accept_char(b']')?; self.accept_char(b':')?; let port = self.accept_number(5, 65535, false)?; Ok(IpEndpoint { addr: IpAddress::Ipv6(ip), port: port as u16, }) } else { let ip = self.accept_ipv6(false)?; Ok(IpEndpoint { addr: IpAddress::Ipv6(ip), port: 0, }) } } fn accept_ip_endpoint(&mut self) -> Result { #[cfg(feature = "proto-ipv4")] #[allow(clippy::single_match)] match self.try_do(|p| p.accept_ipv4_endpoint()) { Some(ipv4) => return Ok(ipv4), None => (), } #[cfg(feature = "proto-ipv6")] #[allow(clippy::single_match)] match self.try_do(|p| p.accept_ipv6_endpoint()) { Some(ipv6) => return Ok(ipv6), None => (), } Err(()) } } #[cfg(feature = "medium-ethernet")] impl FromStr for EthernetAddress { type Err = (); /// Parse a string representation of an Ethernet address. fn from_str(s: &str) -> Result { Parser::new(s).until_eof(|p| p.accept_mac()) } } #[cfg(feature = "proto-ipv4")] impl FromStr for Ipv4Address { type Err = (); /// Parse a string representation of an IPv4 address. fn from_str(s: &str) -> Result { Parser::new(s).until_eof(|p| p.accept_ipv4()) } } #[cfg(feature = "proto-ipv6")] impl FromStr for Ipv6Address { type Err = (); /// Parse a string representation of an IPv6 address. fn from_str(s: &str) -> Result { Parser::new(s).until_eof(|p| p.accept_ipv6(false)) } } impl FromStr for IpAddress { type Err = (); /// Parse a string representation of an IP address. fn from_str(s: &str) -> Result { Parser::new(s).until_eof(|p| p.accept_ip()) } } #[cfg(feature = "proto-ipv4")] impl FromStr for Ipv4Cidr { type Err = (); /// Parse a string representation of an IPv4 CIDR. fn from_str(s: &str) -> Result { Parser::new(s).until_eof(|p| { let ip = p.accept_ipv4()?; p.accept_char(b'/')?; let prefix_len = p.accept_number(2, 33, false)? as u8; Ok(Ipv4Cidr::new(ip, prefix_len)) }) } } #[cfg(feature = "proto-ipv6")] impl FromStr for Ipv6Cidr { type Err = (); /// Parse a string representation of an IPv6 CIDR. fn from_str(s: &str) -> Result { // https://tools.ietf.org/html/rfc4291#section-2.3 Parser::new(s).until_eof(|p| { let ip = p.accept_ipv6(true)?; p.accept_char(b'/')?; let prefix_len = p.accept_number(3, 129, false)? as u8; Ok(Ipv6Cidr::new(ip, prefix_len)) }) } } impl FromStr for IpCidr { type Err = (); /// Parse a string representation of an IP CIDR. fn from_str(s: &str) -> Result { #[cfg(feature = "proto-ipv4")] #[allow(clippy::single_match)] match Ipv4Cidr::from_str(s) { Ok(cidr) => return Ok(IpCidr::Ipv4(cidr)), Err(_) => (), } #[cfg(feature = "proto-ipv6")] #[allow(clippy::single_match)] match Ipv6Cidr::from_str(s) { Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)), Err(_) => (), } Err(()) } } impl FromStr for IpEndpoint { type Err = (); fn from_str(s: &str) -> Result { Parser::new(s).until_eof(|p| p.accept_ip_endpoint()) } } #[cfg(test)] mod test { use super::*; macro_rules! check_cidr_test_array { ($tests:expr, $from_str:path, $variant:path) => { for &(s, cidr) in &$tests { assert_eq!($from_str(s), cidr); assert_eq!(IpCidr::from_str(s), cidr.map($variant)); if let Ok(cidr) = cidr { assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr)); assert_eq!(IpCidr::from_str(&format!("{}", cidr)), Ok($variant(cidr))); } } }; } #[test] #[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))] fn test_mac() { assert_eq!(EthernetAddress::from_str(""), Err(())); assert_eq!( EthernetAddress::from_str("02:00:00:00:00:00"), Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00])) ); assert_eq!( EthernetAddress::from_str("01:23:45:67:89:ab"), Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab])) ); assert_eq!( EthernetAddress::from_str("cd:ef:10:00:00:00"), Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00])) ); assert_eq!( EthernetAddress::from_str("00:00:00:ab:cd:ef"), Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef])) ); assert_eq!( EthernetAddress::from_str("00-00-00-ab-cd-ef"), Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef])) ); assert_eq!( EthernetAddress::from_str("AB-CD-EF-00-00-00"), Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00])) ); assert_eq!(EthernetAddress::from_str("100:00:00:00:00:00"), Err(())); assert_eq!(EthernetAddress::from_str("002:00:00:00:00:00"), Err(())); assert_eq!(EthernetAddress::from_str("02:00:00:00:00:000"), Err(())); assert_eq!(EthernetAddress::from_str("02:00:00:00:00:0x"), Err(())); } #[test] #[cfg(feature = "proto-ipv4")] fn test_ipv4() { assert_eq!(Ipv4Address::from_str(""), Err(())); assert_eq!( Ipv4Address::from_str("1.2.3.4"), Ok(Ipv4Address([1, 2, 3, 4])) ); assert_eq!( Ipv4Address::from_str("001.2.3.4"), Ok(Ipv4Address([1, 2, 3, 4])) ); assert_eq!(Ipv4Address::from_str("0001.2.3.4"), Err(())); assert_eq!(Ipv4Address::from_str("999.2.3.4"), Err(())); assert_eq!(Ipv4Address::from_str("1.2.3.4.5"), Err(())); assert_eq!(Ipv4Address::from_str("1.2.3"), Err(())); assert_eq!(Ipv4Address::from_str("1.2.3."), Err(())); assert_eq!(Ipv4Address::from_str("1.2.3.4."), Err(())); } #[test] #[cfg(feature = "proto-ipv6")] fn test_ipv6() { // Obviously not valid assert_eq!(Ipv6Address::from_str(""), Err(())); assert_eq!( Ipv6Address::from_str("fe80:0:0:0:0:0:0:1"), Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)) ); assert_eq!(Ipv6Address::from_str("::1"), Ok(Ipv6Address::LOOPBACK)); assert_eq!(Ipv6Address::from_str("::"), Ok(Ipv6Address::UNSPECIFIED)); assert_eq!( Ipv6Address::from_str("fe80::1"), Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)) ); assert_eq!( Ipv6Address::from_str("1234:5678::"), Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0, 0)) ); assert_eq!( Ipv6Address::from_str("1234:5678::8765:4321"), Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0x8765, 0x4321)) ); // Two double colons in address assert_eq!(Ipv6Address::from_str("1234:5678::1::1"), Err(())); assert_eq!( Ipv6Address::from_str("4444:333:22:1::4"), Ok(Ipv6Address::new(0x4444, 0x0333, 0x0022, 0x0001, 0, 0, 0, 4)) ); assert_eq!( Ipv6Address::from_str("1:1:1:1:1:1::"), Ok(Ipv6Address::new(1, 1, 1, 1, 1, 1, 0, 0)) ); assert_eq!( Ipv6Address::from_str("::1:1:1:1:1:1"), Ok(Ipv6Address::new(0, 0, 1, 1, 1, 1, 1, 1)) ); assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(())); // Double colon appears too late indicating an address that is too long assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1::"), Err(())); // Section after double colon is too long for a valid address assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(())); // Obviously too long assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1:1:1"), Err(())); // Address is too short assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1"), Err(())); // Long number assert_eq!(Ipv6Address::from_str("::000001"), Err(())); // IPv4-Mapped address assert_eq!( Ipv6Address::from_str("::ffff:192.168.1.1"), Ok(Ipv6Address([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1 ])) ); assert_eq!( Ipv6Address::from_str("0:0:0:0:0:ffff:192.168.1.1"), Ok(Ipv6Address([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1 ])) ); assert_eq!( Ipv6Address::from_str("0::ffff:192.168.1.1"), Ok(Ipv6Address([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1 ])) ); // Only ffff is allowed in position 6 when IPv4 mapped assert_eq!(Ipv6Address::from_str("0:0:0:0:0:eeee:192.168.1.1"), Err(())); // Positions 1-5 must be 0 when IPv4 mapped assert_eq!(Ipv6Address::from_str("0:0:0:0:1:ffff:192.168.1.1"), Err(())); assert_eq!(Ipv6Address::from_str("1::ffff:192.168.1.1"), Err(())); // Out of range ipv4 octet assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:256.168.1.1"), Err(())); // Invalid hex in ipv4 octet assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:c0.168.1.1"), Err(())); } #[test] #[cfg(feature = "proto-ipv4")] fn test_ip_ipv4() { assert_eq!(IpAddress::from_str(""), Err(())); assert_eq!( IpAddress::from_str("1.2.3.4"), Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4]))) ); assert_eq!(IpAddress::from_str("x"), Err(())); } #[test] #[cfg(feature = "proto-ipv6")] fn test_ip_ipv6() { assert_eq!(IpAddress::from_str(""), Err(())); assert_eq!( IpAddress::from_str("fe80::1"), Ok(IpAddress::Ipv6(Ipv6Address::new( 0xfe80, 0, 0, 0, 0, 0, 0, 1 ))) ); assert_eq!(IpAddress::from_str("x"), Err(())); } #[test] #[cfg(feature = "proto-ipv4")] fn test_cidr_ipv4() { let tests = [ ( "127.0.0.1/8", Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8)), ), ( "192.168.1.1/24", Ok(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 1]), 24u8)), ), ( "8.8.8.8/32", Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 32u8)), ), ( "8.8.8.8/0", Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 0u8)), ), ("", Err(())), ("1", Err(())), ("127.0.0.1", Err(())), ("127.0.0.1/", Err(())), ("127.0.0.1/33", Err(())), ("127.0.0.1/111", Err(())), ("/32", Err(())), ]; check_cidr_test_array!(tests, Ipv4Cidr::from_str, IpCidr::Ipv4); } #[test] #[cfg(feature = "proto-ipv6")] fn test_cidr_ipv6() { let tests = [ ( "fe80::1/64", Ok(Ipv6Cidr::new( Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64u8, )), ), ( "fe80::/64", Ok(Ipv6Cidr::new( Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 64u8, )), ), ("::1/128", Ok(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128u8))), ("::/128", Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))), ( "fe80:0:0:0:0:0:0:1/64", Ok(Ipv6Cidr::new( Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64u8, )), ), ("fe80:0:0:0:0:0:0:1|64", Err(())), ("fe80::|64", Err(())), ("fe80::1::/64", Err(())), ]; check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6); } #[test] #[cfg(feature = "proto-ipv4")] fn test_endpoint_ipv4() { assert_eq!(IpEndpoint::from_str(""), Err(())); assert_eq!(IpEndpoint::from_str("x"), Err(())); assert_eq!( IpEndpoint::from_str("127.0.0.1"), Ok(IpEndpoint { addr: IpAddress::v4(127, 0, 0, 1), port: 0 }) ); assert_eq!( IpEndpoint::from_str("127.0.0.1:12345"), Ok(IpEndpoint { addr: IpAddress::v4(127, 0, 0, 1), port: 12345 }) ); } #[test] #[cfg(feature = "proto-ipv6")] fn test_endpoint_ipv6() { assert_eq!(IpEndpoint::from_str(""), Err(())); assert_eq!(IpEndpoint::from_str("x"), Err(())); assert_eq!( IpEndpoint::from_str("fe80::1"), Ok(IpEndpoint { addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), port: 0 }) ); assert_eq!( IpEndpoint::from_str("[fe80::1]:12345"), Ok(IpEndpoint { addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), port: 12345 }) ); } }