summaryrefslogtreecommitdiff
path: root/src/parsers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parsers.rs')
-rw-r--r--src/parsers.rs765
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
+ })
+ );
+ }
+}