diff options
Diffstat (limited to 'src/parsers.rs')
-rw-r--r-- | src/parsers.rs | 765 |
1 files changed, 765 insertions, 0 deletions
diff --git a/src/parsers.rs b/src/parsers.rs new file mode 100644 index 0000000..16419ab --- /dev/null +++ b/src/parsers.rs @@ -0,0 +1,765 @@ +#![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<T> = result::Result<T, ()>; + +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<u8> { + match self.data.get(self.pos) { + Some(&chr) => { + self.pos += 1; + Ok(chr) + } + None => Err(()), + } + } + + fn try_do<F, T>(&mut self, f: F) -> Option<T> + where + F: FnOnce(&mut Parser<'a>) -> Result<T>, + { + 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<F, T>(&mut self, f: F) -> Result<T> + where + F: FnOnce(&mut Parser<'a>) -> Result<T>, + { + 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<u8> { + let digit = self.advance()?; + if digit.is_ascii_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<u32> { + 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<EthernetAddress> { + 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<EthernetAddress> { + 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, + ) -> 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 => { + // 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) + } + } + + #[cfg(feature = "proto-ipv6")] + fn accept_ipv6(&mut self) -> Result<Ipv6Address> { + // 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, + )?; + + // 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<Ipv4Address> { + let octets = self.accept_ipv4_octets()?; + Ok(Ipv4Address(octets)) + } + + fn accept_ip(&mut self) -> Result<IpAddress> { + #[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()) { + Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)), + None => (), + } + + Err(()) + } + + #[cfg(feature = "proto-ipv4")] + fn accept_ipv4_endpoint(&mut self) -> Result<IpEndpoint> { + 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<IpEndpoint> { + if self.lookahead_char(b'[') { + self.accept_char(b'[')?; + let ip = self.accept_ipv6()?; + 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()?; + Ok(IpEndpoint { + addr: IpAddress::Ipv6(ip), + port: 0, + }) + } + } + + fn accept_ip_endpoint(&mut self) -> Result<IpEndpoint> { + #[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<EthernetAddress> { + 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<Ipv4Address> { + 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<Ipv6Address> { + Parser::new(s).until_eof(|p| p.accept_ipv6()) + } +} + +impl FromStr for IpAddress { + type Err = (); + + /// Parse a string representation of an IP address. + fn from_str(s: &str) -> Result<IpAddress> { + 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<Ipv4Cidr> { + 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<Ipv6Cidr> { + // https://tools.ietf.org/html/rfc4291#section-2.3 + Parser::new(s).until_eof(|p| { + let ip = p.accept_ipv6()?; + 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<IpCidr> { + #[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<IpEndpoint> { + 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 + }) + ); + assert_eq!( + IpEndpoint::from_str("[::]:12345"), + Ok(IpEndpoint { + addr: IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), + port: 12345 + }) + ); + } +} |