ya-smoltcp 0.1.0

Golem Network fork of smoltcp
Documentation
#![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 (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<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,
        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<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,
            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<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(false)) {
            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(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<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(false))
    }
}

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(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<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
            })
        );
    }
}