smoltcp/
parsers.rs

1#![cfg_attr(
2    not(all(feature = "proto-ipv6", feature = "proto-ipv4")),
3    allow(dead_code)
4)]
5
6use core::result;
7use core::str::FromStr;
8
9#[cfg(feature = "medium-ethernet")]
10use crate::wire::EthernetAddress;
11use crate::wire::{IpAddress, IpCidr, IpEndpoint};
12#[cfg(feature = "proto-ipv4")]
13use crate::wire::{Ipv4Address, Ipv4Cidr};
14#[cfg(feature = "proto-ipv6")]
15use crate::wire::{Ipv6Address, Ipv6Cidr};
16
17type Result<T> = result::Result<T, ()>;
18
19struct Parser<'a> {
20    data: &'a [u8],
21    pos: usize,
22}
23
24impl<'a> Parser<'a> {
25    fn new(data: &'a str) -> Parser<'a> {
26        Parser {
27            data: data.as_bytes(),
28            pos: 0,
29        }
30    }
31
32    fn lookahead_char(&self, ch: u8) -> bool {
33        if self.pos < self.data.len() {
34            self.data[self.pos] == ch
35        } else {
36            false
37        }
38    }
39
40    fn advance(&mut self) -> Result<u8> {
41        match self.data.get(self.pos) {
42            Some(&chr) => {
43                self.pos += 1;
44                Ok(chr)
45            }
46            None => Err(()),
47        }
48    }
49
50    fn try_do<F, T>(&mut self, f: F) -> Option<T>
51    where
52        F: FnOnce(&mut Parser<'a>) -> Result<T>,
53    {
54        let pos = self.pos;
55        match f(self) {
56            Ok(res) => Some(res),
57            Err(()) => {
58                self.pos = pos;
59                None
60            }
61        }
62    }
63
64    fn accept_eof(&mut self) -> Result<()> {
65        if self.data.len() == self.pos {
66            Ok(())
67        } else {
68            Err(())
69        }
70    }
71
72    fn until_eof<F, T>(&mut self, f: F) -> Result<T>
73    where
74        F: FnOnce(&mut Parser<'a>) -> Result<T>,
75    {
76        let res = f(self)?;
77        self.accept_eof()?;
78        Ok(res)
79    }
80
81    fn accept_char(&mut self, chr: u8) -> Result<()> {
82        if self.advance()? == chr {
83            Ok(())
84        } else {
85            Err(())
86        }
87    }
88
89    fn accept_str(&mut self, string: &[u8]) -> Result<()> {
90        for byte in string.iter() {
91            self.accept_char(*byte)?;
92        }
93        Ok(())
94    }
95
96    fn accept_digit(&mut self, hex: bool) -> Result<u8> {
97        let digit = self.advance()?;
98        if digit.is_ascii_digit() {
99            Ok(digit - b'0')
100        } else if hex && (b'a'..=b'f').contains(&digit) {
101            Ok(digit - b'a' + 10)
102        } else if hex && (b'A'..=b'F').contains(&digit) {
103            Ok(digit - b'A' + 10)
104        } else {
105            Err(())
106        }
107    }
108
109    fn accept_number(&mut self, max_digits: usize, max_value: u32, hex: bool) -> Result<u32> {
110        let mut value = self.accept_digit(hex)? as u32;
111        for _ in 1..max_digits {
112            match self.try_do(|p| p.accept_digit(hex)) {
113                Some(digit) => {
114                    value *= if hex { 16 } else { 10 };
115                    value += digit as u32;
116                }
117                None => break,
118            }
119        }
120        if value < max_value {
121            Ok(value)
122        } else {
123            Err(())
124        }
125    }
126
127    #[cfg(feature = "medium-ethernet")]
128    fn accept_mac_joined_with(&mut self, separator: u8) -> Result<EthernetAddress> {
129        let mut octets = [0u8; 6];
130        for (n, octet) in octets.iter_mut().enumerate() {
131            *octet = self.accept_number(2, 0x100, true)? as u8;
132            if n != 5 {
133                self.accept_char(separator)?;
134            }
135        }
136        Ok(EthernetAddress(octets))
137    }
138
139    #[cfg(feature = "medium-ethernet")]
140    fn accept_mac(&mut self) -> Result<EthernetAddress> {
141        if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b'-')) {
142            return Ok(mac);
143        }
144        if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b':')) {
145            return Ok(mac);
146        }
147        Err(())
148    }
149
150    #[cfg(feature = "proto-ipv6")]
151    fn accept_ipv4_mapped_ipv6_part(&mut self, parts: &mut [u16], idx: &mut usize) -> Result<()> {
152        let octets = self.accept_ipv4_octets()?;
153
154        parts[*idx] = ((octets[0] as u16) << 8) | (octets[1] as u16);
155        *idx += 1;
156        parts[*idx] = ((octets[2] as u16) << 8) | (octets[3] as u16);
157        *idx += 1;
158
159        Ok(())
160    }
161
162    #[cfg(feature = "proto-ipv6")]
163    fn accept_ipv6_part(
164        &mut self,
165        (head, tail): (&mut [u16; 8], &mut [u16; 6]),
166        (head_idx, tail_idx): (&mut usize, &mut usize),
167        mut use_tail: bool,
168    ) -> Result<()> {
169        let double_colon = match self.try_do(|p| p.accept_str(b"::")) {
170            Some(_) if !use_tail && *head_idx < 7 => {
171                // Found a double colon. Start filling out the
172                // tail and set the double colon flag in case
173                // this is the last character we can parse.
174                use_tail = true;
175                true
176            }
177            Some(_) => {
178                // This is a bad address. Only one double colon is
179                // allowed and an address is only 128 bits.
180                return Err(());
181            }
182            None => {
183                if *head_idx != 0 || use_tail && *tail_idx != 0 {
184                    // If this is not the first number or the position following
185                    // a double colon, we expect there to be a single colon.
186                    self.accept_char(b':')?;
187                }
188                false
189            }
190        };
191
192        match self.try_do(|p| p.accept_number(4, 0x10000, true)) {
193            Some(part) if !use_tail && *head_idx < 8 => {
194                // Valid u16 to be added to the address
195                head[*head_idx] = part as u16;
196                *head_idx += 1;
197
198                if *head_idx == 6 && head[0..*head_idx] == [0, 0, 0, 0, 0, 0xffff] {
199                    self.try_do(|p| {
200                        p.accept_char(b':')?;
201                        p.accept_ipv4_mapped_ipv6_part(head, head_idx)
202                    });
203                }
204                Ok(())
205            }
206            Some(part) if *tail_idx < 6 => {
207                // Valid u16 to be added to the address
208                tail[*tail_idx] = part as u16;
209                *tail_idx += 1;
210
211                if *tail_idx == 1 && tail[0] == 0xffff && head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] {
212                    self.try_do(|p| {
213                        p.accept_char(b':')?;
214                        p.accept_ipv4_mapped_ipv6_part(tail, tail_idx)
215                    });
216                }
217                Ok(())
218            }
219            Some(_) => {
220                // Tail or head section is too long
221                Err(())
222            }
223            None if double_colon => {
224                // The address ends with "::". E.g. 1234:: or ::
225                Ok(())
226            }
227            None => {
228                // Invalid address
229                Err(())
230            }
231        }?;
232
233        if *head_idx + *tail_idx > 8 {
234            // The head and tail indexes add up to a bad address length.
235            Err(())
236        } else if !self.lookahead_char(b':') {
237            if *head_idx < 8 && !use_tail {
238                // There was no double colon found, and the head is too short
239                return Err(());
240            }
241            Ok(())
242        } else {
243            // Continue recursing
244            self.accept_ipv6_part((head, tail), (head_idx, tail_idx), use_tail)
245        }
246    }
247
248    #[cfg(feature = "proto-ipv6")]
249    fn accept_ipv6(&mut self) -> Result<Ipv6Address> {
250        // IPv6 addresses may contain a "::" to indicate a series of
251        // 16 bit sections that evaluate to 0. E.g.
252        //
253        // fe80:0000:0000:0000:0000:0000:0000:0001
254        //
255        // May be written as
256        //
257        // fe80::1
258        //
259        // As a result, we need to find the first section of colon
260        // delimited u16's before a possible "::", then the
261        // possible second section after the "::", and finally
262        // combine the second optional section to the end of the
263        // final address.
264        //
265        // See https://tools.ietf.org/html/rfc4291#section-2.2
266        // for details.
267        let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]);
268        let (mut head_idx, mut tail_idx) = (0, 0);
269
270        self.accept_ipv6_part(
271            (&mut addr, &mut tail),
272            (&mut head_idx, &mut tail_idx),
273            false,
274        )?;
275
276        // We need to copy the tail portion (the portion following the "::") to the
277        // end of the address.
278        addr[8 - tail_idx..].copy_from_slice(&tail[..tail_idx]);
279
280        Ok(Ipv6Address::from_parts(&addr))
281    }
282
283    fn accept_ipv4_octets(&mut self) -> Result<[u8; 4]> {
284        let mut octets = [0u8; 4];
285        for (n, octet) in octets.iter_mut().enumerate() {
286            *octet = self.accept_number(3, 0x100, false)? as u8;
287            if n != 3 {
288                self.accept_char(b'.')?;
289            }
290        }
291        Ok(octets)
292    }
293
294    #[cfg(feature = "proto-ipv4")]
295    fn accept_ipv4(&mut self) -> Result<Ipv4Address> {
296        let octets = self.accept_ipv4_octets()?;
297        Ok(Ipv4Address(octets))
298    }
299
300    fn accept_ip(&mut self) -> Result<IpAddress> {
301        #[cfg(feature = "proto-ipv4")]
302        #[allow(clippy::single_match)]
303        match self.try_do(|p| p.accept_ipv4()) {
304            Some(ipv4) => return Ok(IpAddress::Ipv4(ipv4)),
305            None => (),
306        }
307
308        #[cfg(feature = "proto-ipv6")]
309        #[allow(clippy::single_match)]
310        match self.try_do(|p| p.accept_ipv6()) {
311            Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)),
312            None => (),
313        }
314
315        Err(())
316    }
317
318    #[cfg(feature = "proto-ipv4")]
319    fn accept_ipv4_endpoint(&mut self) -> Result<IpEndpoint> {
320        let ip = self.accept_ipv4()?;
321
322        let port = if self.accept_eof().is_ok() {
323            0
324        } else {
325            self.accept_char(b':')?;
326            self.accept_number(5, 65535, false)?
327        };
328
329        Ok(IpEndpoint {
330            addr: IpAddress::Ipv4(ip),
331            port: port as u16,
332        })
333    }
334
335    #[cfg(feature = "proto-ipv6")]
336    fn accept_ipv6_endpoint(&mut self) -> Result<IpEndpoint> {
337        if self.lookahead_char(b'[') {
338            self.accept_char(b'[')?;
339            let ip = self.accept_ipv6()?;
340            self.accept_char(b']')?;
341            self.accept_char(b':')?;
342            let port = self.accept_number(5, 65535, false)?;
343
344            Ok(IpEndpoint {
345                addr: IpAddress::Ipv6(ip),
346                port: port as u16,
347            })
348        } else {
349            let ip = self.accept_ipv6()?;
350            Ok(IpEndpoint {
351                addr: IpAddress::Ipv6(ip),
352                port: 0,
353            })
354        }
355    }
356
357    fn accept_ip_endpoint(&mut self) -> Result<IpEndpoint> {
358        #[cfg(feature = "proto-ipv4")]
359        #[allow(clippy::single_match)]
360        match self.try_do(|p| p.accept_ipv4_endpoint()) {
361            Some(ipv4) => return Ok(ipv4),
362            None => (),
363        }
364
365        #[cfg(feature = "proto-ipv6")]
366        #[allow(clippy::single_match)]
367        match self.try_do(|p| p.accept_ipv6_endpoint()) {
368            Some(ipv6) => return Ok(ipv6),
369            None => (),
370        }
371
372        Err(())
373    }
374}
375
376#[cfg(feature = "medium-ethernet")]
377impl FromStr for EthernetAddress {
378    type Err = ();
379
380    /// Parse a string representation of an Ethernet address.
381    fn from_str(s: &str) -> Result<EthernetAddress> {
382        Parser::new(s).until_eof(|p| p.accept_mac())
383    }
384}
385
386#[cfg(feature = "proto-ipv4")]
387impl FromStr for Ipv4Address {
388    type Err = ();
389
390    /// Parse a string representation of an IPv4 address.
391    fn from_str(s: &str) -> Result<Ipv4Address> {
392        Parser::new(s).until_eof(|p| p.accept_ipv4())
393    }
394}
395
396#[cfg(feature = "proto-ipv6")]
397impl FromStr for Ipv6Address {
398    type Err = ();
399
400    /// Parse a string representation of an IPv6 address.
401    fn from_str(s: &str) -> Result<Ipv6Address> {
402        Parser::new(s).until_eof(|p| p.accept_ipv6())
403    }
404}
405
406impl FromStr for IpAddress {
407    type Err = ();
408
409    /// Parse a string representation of an IP address.
410    fn from_str(s: &str) -> Result<IpAddress> {
411        Parser::new(s).until_eof(|p| p.accept_ip())
412    }
413}
414
415#[cfg(feature = "proto-ipv4")]
416impl FromStr for Ipv4Cidr {
417    type Err = ();
418
419    /// Parse a string representation of an IPv4 CIDR.
420    fn from_str(s: &str) -> Result<Ipv4Cidr> {
421        Parser::new(s).until_eof(|p| {
422            let ip = p.accept_ipv4()?;
423            p.accept_char(b'/')?;
424            let prefix_len = p.accept_number(2, 33, false)? as u8;
425            Ok(Ipv4Cidr::new(ip, prefix_len))
426        })
427    }
428}
429
430#[cfg(feature = "proto-ipv6")]
431impl FromStr for Ipv6Cidr {
432    type Err = ();
433
434    /// Parse a string representation of an IPv6 CIDR.
435    fn from_str(s: &str) -> Result<Ipv6Cidr> {
436        // https://tools.ietf.org/html/rfc4291#section-2.3
437        Parser::new(s).until_eof(|p| {
438            let ip = p.accept_ipv6()?;
439            p.accept_char(b'/')?;
440            let prefix_len = p.accept_number(3, 129, false)? as u8;
441            Ok(Ipv6Cidr::new(ip, prefix_len))
442        })
443    }
444}
445
446impl FromStr for IpCidr {
447    type Err = ();
448
449    /// Parse a string representation of an IP CIDR.
450    fn from_str(s: &str) -> Result<IpCidr> {
451        #[cfg(feature = "proto-ipv4")]
452        #[allow(clippy::single_match)]
453        match Ipv4Cidr::from_str(s) {
454            Ok(cidr) => return Ok(IpCidr::Ipv4(cidr)),
455            Err(_) => (),
456        }
457
458        #[cfg(feature = "proto-ipv6")]
459        #[allow(clippy::single_match)]
460        match Ipv6Cidr::from_str(s) {
461            Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)),
462            Err(_) => (),
463        }
464
465        Err(())
466    }
467}
468
469impl FromStr for IpEndpoint {
470    type Err = ();
471
472    fn from_str(s: &str) -> Result<IpEndpoint> {
473        Parser::new(s).until_eof(|p| p.accept_ip_endpoint())
474    }
475}
476
477#[cfg(test)]
478mod test {
479    use super::*;
480
481    macro_rules! check_cidr_test_array {
482        ($tests:expr, $from_str:path, $variant:path) => {
483            for &(s, cidr) in &$tests {
484                assert_eq!($from_str(s), cidr);
485                assert_eq!(IpCidr::from_str(s), cidr.map($variant));
486
487                if let Ok(cidr) = cidr {
488                    assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr));
489                    assert_eq!(IpCidr::from_str(&format!("{}", cidr)), Ok($variant(cidr)));
490                }
491            }
492        };
493    }
494
495    #[test]
496    #[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
497    fn test_mac() {
498        assert_eq!(EthernetAddress::from_str(""), Err(()));
499        assert_eq!(
500            EthernetAddress::from_str("02:00:00:00:00:00"),
501            Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00]))
502        );
503        assert_eq!(
504            EthernetAddress::from_str("01:23:45:67:89:ab"),
505            Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab]))
506        );
507        assert_eq!(
508            EthernetAddress::from_str("cd:ef:10:00:00:00"),
509            Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00]))
510        );
511        assert_eq!(
512            EthernetAddress::from_str("00:00:00:ab:cd:ef"),
513            Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
514        );
515        assert_eq!(
516            EthernetAddress::from_str("00-00-00-ab-cd-ef"),
517            Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
518        );
519        assert_eq!(
520            EthernetAddress::from_str("AB-CD-EF-00-00-00"),
521            Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00]))
522        );
523        assert_eq!(EthernetAddress::from_str("100:00:00:00:00:00"), Err(()));
524        assert_eq!(EthernetAddress::from_str("002:00:00:00:00:00"), Err(()));
525        assert_eq!(EthernetAddress::from_str("02:00:00:00:00:000"), Err(()));
526        assert_eq!(EthernetAddress::from_str("02:00:00:00:00:0x"), Err(()));
527    }
528
529    #[test]
530    #[cfg(feature = "proto-ipv4")]
531    fn test_ipv4() {
532        assert_eq!(Ipv4Address::from_str(""), Err(()));
533        assert_eq!(
534            Ipv4Address::from_str("1.2.3.4"),
535            Ok(Ipv4Address([1, 2, 3, 4]))
536        );
537        assert_eq!(
538            Ipv4Address::from_str("001.2.3.4"),
539            Ok(Ipv4Address([1, 2, 3, 4]))
540        );
541        assert_eq!(Ipv4Address::from_str("0001.2.3.4"), Err(()));
542        assert_eq!(Ipv4Address::from_str("999.2.3.4"), Err(()));
543        assert_eq!(Ipv4Address::from_str("1.2.3.4.5"), Err(()));
544        assert_eq!(Ipv4Address::from_str("1.2.3"), Err(()));
545        assert_eq!(Ipv4Address::from_str("1.2.3."), Err(()));
546        assert_eq!(Ipv4Address::from_str("1.2.3.4."), Err(()));
547    }
548
549    #[test]
550    #[cfg(feature = "proto-ipv6")]
551    fn test_ipv6() {
552        // Obviously not valid
553        assert_eq!(Ipv6Address::from_str(""), Err(()));
554        assert_eq!(
555            Ipv6Address::from_str("fe80:0:0:0:0:0:0:1"),
556            Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))
557        );
558        assert_eq!(Ipv6Address::from_str("::1"), Ok(Ipv6Address::LOOPBACK));
559        assert_eq!(Ipv6Address::from_str("::"), Ok(Ipv6Address::UNSPECIFIED));
560        assert_eq!(
561            Ipv6Address::from_str("fe80::1"),
562            Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))
563        );
564        assert_eq!(
565            Ipv6Address::from_str("1234:5678::"),
566            Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0, 0))
567        );
568        assert_eq!(
569            Ipv6Address::from_str("1234:5678::8765:4321"),
570            Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0x8765, 0x4321))
571        );
572        // Two double colons in address
573        assert_eq!(Ipv6Address::from_str("1234:5678::1::1"), Err(()));
574        assert_eq!(
575            Ipv6Address::from_str("4444:333:22:1::4"),
576            Ok(Ipv6Address::new(0x4444, 0x0333, 0x0022, 0x0001, 0, 0, 0, 4))
577        );
578        assert_eq!(
579            Ipv6Address::from_str("1:1:1:1:1:1::"),
580            Ok(Ipv6Address::new(1, 1, 1, 1, 1, 1, 0, 0))
581        );
582        assert_eq!(
583            Ipv6Address::from_str("::1:1:1:1:1:1"),
584            Ok(Ipv6Address::new(0, 0, 1, 1, 1, 1, 1, 1))
585        );
586        assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(()));
587        // Double colon appears too late indicating an address that is too long
588        assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1::"), Err(()));
589        // Section after double colon is too long for a valid address
590        assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(()));
591        // Obviously too long
592        assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1:1:1"), Err(()));
593        // Address is too short
594        assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1"), Err(()));
595        // Long number
596        assert_eq!(Ipv6Address::from_str("::000001"), Err(()));
597        // IPv4-Mapped address
598        assert_eq!(
599            Ipv6Address::from_str("::ffff:192.168.1.1"),
600            Ok(Ipv6Address([
601                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
602            ]))
603        );
604        assert_eq!(
605            Ipv6Address::from_str("0:0:0:0:0:ffff:192.168.1.1"),
606            Ok(Ipv6Address([
607                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
608            ]))
609        );
610        assert_eq!(
611            Ipv6Address::from_str("0::ffff:192.168.1.1"),
612            Ok(Ipv6Address([
613                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
614            ]))
615        );
616        // Only ffff is allowed in position 6 when IPv4 mapped
617        assert_eq!(Ipv6Address::from_str("0:0:0:0:0:eeee:192.168.1.1"), Err(()));
618        // Positions 1-5 must be 0 when IPv4 mapped
619        assert_eq!(Ipv6Address::from_str("0:0:0:0:1:ffff:192.168.1.1"), Err(()));
620        assert_eq!(Ipv6Address::from_str("1::ffff:192.168.1.1"), Err(()));
621        // Out of range ipv4 octet
622        assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:256.168.1.1"), Err(()));
623        // Invalid hex in ipv4 octet
624        assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:c0.168.1.1"), Err(()));
625    }
626
627    #[test]
628    #[cfg(feature = "proto-ipv4")]
629    fn test_ip_ipv4() {
630        assert_eq!(IpAddress::from_str(""), Err(()));
631        assert_eq!(
632            IpAddress::from_str("1.2.3.4"),
633            Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4])))
634        );
635        assert_eq!(IpAddress::from_str("x"), Err(()));
636    }
637
638    #[test]
639    #[cfg(feature = "proto-ipv6")]
640    fn test_ip_ipv6() {
641        assert_eq!(IpAddress::from_str(""), Err(()));
642        assert_eq!(
643            IpAddress::from_str("fe80::1"),
644            Ok(IpAddress::Ipv6(Ipv6Address::new(
645                0xfe80, 0, 0, 0, 0, 0, 0, 1
646            )))
647        );
648        assert_eq!(IpAddress::from_str("x"), Err(()));
649    }
650
651    #[test]
652    #[cfg(feature = "proto-ipv4")]
653    fn test_cidr_ipv4() {
654        let tests = [
655            (
656                "127.0.0.1/8",
657                Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8)),
658            ),
659            (
660                "192.168.1.1/24",
661                Ok(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 1]), 24u8)),
662            ),
663            (
664                "8.8.8.8/32",
665                Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 32u8)),
666            ),
667            (
668                "8.8.8.8/0",
669                Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 0u8)),
670            ),
671            ("", Err(())),
672            ("1", Err(())),
673            ("127.0.0.1", Err(())),
674            ("127.0.0.1/", Err(())),
675            ("127.0.0.1/33", Err(())),
676            ("127.0.0.1/111", Err(())),
677            ("/32", Err(())),
678        ];
679
680        check_cidr_test_array!(tests, Ipv4Cidr::from_str, IpCidr::Ipv4);
681    }
682
683    #[test]
684    #[cfg(feature = "proto-ipv6")]
685    fn test_cidr_ipv6() {
686        let tests = [
687            (
688                "fe80::1/64",
689                Ok(Ipv6Cidr::new(
690                    Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
691                    64u8,
692                )),
693            ),
694            (
695                "fe80::/64",
696                Ok(Ipv6Cidr::new(
697                    Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
698                    64u8,
699                )),
700            ),
701            ("::1/128", Ok(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128u8))),
702            ("::/128", Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))),
703            (
704                "fe80:0:0:0:0:0:0:1/64",
705                Ok(Ipv6Cidr::new(
706                    Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
707                    64u8,
708                )),
709            ),
710            ("fe80:0:0:0:0:0:0:1|64", Err(())),
711            ("fe80::|64", Err(())),
712            ("fe80::1::/64", Err(())),
713        ];
714        check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6);
715    }
716
717    #[test]
718    #[cfg(feature = "proto-ipv4")]
719    fn test_endpoint_ipv4() {
720        assert_eq!(IpEndpoint::from_str(""), Err(()));
721        assert_eq!(IpEndpoint::from_str("x"), Err(()));
722        assert_eq!(
723            IpEndpoint::from_str("127.0.0.1"),
724            Ok(IpEndpoint {
725                addr: IpAddress::v4(127, 0, 0, 1),
726                port: 0
727            })
728        );
729        assert_eq!(
730            IpEndpoint::from_str("127.0.0.1:12345"),
731            Ok(IpEndpoint {
732                addr: IpAddress::v4(127, 0, 0, 1),
733                port: 12345
734            })
735        );
736    }
737
738    #[test]
739    #[cfg(feature = "proto-ipv6")]
740    fn test_endpoint_ipv6() {
741        assert_eq!(IpEndpoint::from_str(""), Err(()));
742        assert_eq!(IpEndpoint::from_str("x"), Err(()));
743        assert_eq!(
744            IpEndpoint::from_str("fe80::1"),
745            Ok(IpEndpoint {
746                addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
747                port: 0
748            })
749        );
750        assert_eq!(
751            IpEndpoint::from_str("[fe80::1]:12345"),
752            Ok(IpEndpoint {
753                addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
754                port: 12345
755            })
756        );
757        assert_eq!(
758            IpEndpoint::from_str("[::]:12345"),
759            Ok(IpEndpoint {
760                addr: IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0),
761                port: 12345
762            })
763        );
764    }
765}