parsers.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #![cfg_attr(
  2. not(all(feature = "proto-ipv6", feature = "proto-ipv4")),
  3. allow(dead_code)
  4. )]
  5. use core::result;
  6. use core::str::FromStr;
  7. #[cfg(feature = "medium-ethernet")]
  8. use crate::wire::EthernetAddress;
  9. use crate::wire::{IpAddress, IpCidr, IpEndpoint};
  10. #[cfg(feature = "proto-ipv4")]
  11. use crate::wire::{Ipv4Address, Ipv4AddressExt, Ipv4Cidr};
  12. #[cfg(feature = "proto-ipv6")]
  13. use crate::wire::{Ipv6Address, Ipv6Cidr};
  14. type Result<T> = result::Result<T, ()>;
  15. struct Parser<'a> {
  16. data: &'a [u8],
  17. pos: usize,
  18. }
  19. impl<'a> Parser<'a> {
  20. fn new(data: &'a str) -> Parser<'a> {
  21. Parser {
  22. data: data.as_bytes(),
  23. pos: 0,
  24. }
  25. }
  26. fn lookahead_char(&self, ch: u8) -> bool {
  27. if self.pos < self.data.len() {
  28. self.data[self.pos] == ch
  29. } else {
  30. false
  31. }
  32. }
  33. fn advance(&mut self) -> Result<u8> {
  34. match self.data.get(self.pos) {
  35. Some(&chr) => {
  36. self.pos += 1;
  37. Ok(chr)
  38. }
  39. None => Err(()),
  40. }
  41. }
  42. fn try_do<F, T>(&mut self, f: F) -> Option<T>
  43. where
  44. F: FnOnce(&mut Parser<'a>) -> Result<T>,
  45. {
  46. let pos = self.pos;
  47. match f(self) {
  48. Ok(res) => Some(res),
  49. Err(()) => {
  50. self.pos = pos;
  51. None
  52. }
  53. }
  54. }
  55. fn accept_eof(&mut self) -> Result<()> {
  56. if self.data.len() == self.pos {
  57. Ok(())
  58. } else {
  59. Err(())
  60. }
  61. }
  62. fn until_eof<F, T>(&mut self, f: F) -> Result<T>
  63. where
  64. F: FnOnce(&mut Parser<'a>) -> Result<T>,
  65. {
  66. let res = f(self)?;
  67. self.accept_eof()?;
  68. Ok(res)
  69. }
  70. fn accept_char(&mut self, chr: u8) -> Result<()> {
  71. if self.advance()? == chr {
  72. Ok(())
  73. } else {
  74. Err(())
  75. }
  76. }
  77. fn accept_str(&mut self, string: &[u8]) -> Result<()> {
  78. for byte in string.iter() {
  79. self.accept_char(*byte)?;
  80. }
  81. Ok(())
  82. }
  83. fn accept_digit(&mut self, hex: bool) -> Result<u8> {
  84. let digit = self.advance()?;
  85. if digit.is_ascii_digit() {
  86. Ok(digit - b'0')
  87. } else if hex && (b'a'..=b'f').contains(&digit) {
  88. Ok(digit - b'a' + 10)
  89. } else if hex && (b'A'..=b'F').contains(&digit) {
  90. Ok(digit - b'A' + 10)
  91. } else {
  92. Err(())
  93. }
  94. }
  95. fn accept_number(&mut self, max_digits: usize, max_value: u32, hex: bool) -> Result<u32> {
  96. let mut value = self.accept_digit(hex)? as u32;
  97. for _ in 1..max_digits {
  98. match self.try_do(|p| p.accept_digit(hex)) {
  99. Some(digit) => {
  100. value *= if hex { 16 } else { 10 };
  101. value += digit as u32;
  102. }
  103. None => break,
  104. }
  105. }
  106. if value < max_value {
  107. Ok(value)
  108. } else {
  109. Err(())
  110. }
  111. }
  112. #[cfg(feature = "medium-ethernet")]
  113. fn accept_mac_joined_with(&mut self, separator: u8) -> Result<EthernetAddress> {
  114. let mut octets = [0u8; 6];
  115. for (n, octet) in octets.iter_mut().enumerate() {
  116. *octet = self.accept_number(2, 0x100, true)? as u8;
  117. if n != 5 {
  118. self.accept_char(separator)?;
  119. }
  120. }
  121. Ok(EthernetAddress(octets))
  122. }
  123. #[cfg(feature = "medium-ethernet")]
  124. fn accept_mac(&mut self) -> Result<EthernetAddress> {
  125. if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b'-')) {
  126. return Ok(mac);
  127. }
  128. if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b':')) {
  129. return Ok(mac);
  130. }
  131. Err(())
  132. }
  133. #[cfg(feature = "proto-ipv6")]
  134. fn accept_ipv4_mapped_ipv6_part(&mut self, parts: &mut [u16], idx: &mut usize) -> Result<()> {
  135. let octets = self.accept_ipv4_octets()?;
  136. parts[*idx] = ((octets[0] as u16) << 8) | (octets[1] as u16);
  137. *idx += 1;
  138. parts[*idx] = ((octets[2] as u16) << 8) | (octets[3] as u16);
  139. *idx += 1;
  140. Ok(())
  141. }
  142. #[cfg(feature = "proto-ipv6")]
  143. fn accept_ipv6_part(
  144. &mut self,
  145. (head, tail): (&mut [u16; 8], &mut [u16; 6]),
  146. (head_idx, tail_idx): (&mut usize, &mut usize),
  147. mut use_tail: bool,
  148. ) -> Result<()> {
  149. let double_colon = match self.try_do(|p| p.accept_str(b"::")) {
  150. Some(_) if !use_tail && *head_idx < 7 => {
  151. // Found a double colon. Start filling out the
  152. // tail and set the double colon flag in case
  153. // this is the last character we can parse.
  154. use_tail = true;
  155. true
  156. }
  157. Some(_) => {
  158. // This is a bad address. Only one double colon is
  159. // allowed and an address is only 128 bits.
  160. return Err(());
  161. }
  162. None => {
  163. if *head_idx != 0 || use_tail && *tail_idx != 0 {
  164. // If this is not the first number or the position following
  165. // a double colon, we expect there to be a single colon.
  166. self.accept_char(b':')?;
  167. }
  168. false
  169. }
  170. };
  171. match self.try_do(|p| p.accept_number(4, 0x10000, true)) {
  172. Some(part) if !use_tail && *head_idx < 8 => {
  173. // Valid u16 to be added to the address
  174. head[*head_idx] = part as u16;
  175. *head_idx += 1;
  176. if *head_idx == 6 && head[0..*head_idx] == [0, 0, 0, 0, 0, 0xffff] {
  177. self.try_do(|p| {
  178. p.accept_char(b':')?;
  179. p.accept_ipv4_mapped_ipv6_part(head, head_idx)
  180. });
  181. }
  182. Ok(())
  183. }
  184. Some(part) if *tail_idx < 6 => {
  185. // Valid u16 to be added to the address
  186. tail[*tail_idx] = part as u16;
  187. *tail_idx += 1;
  188. if *tail_idx == 1 && tail[0] == 0xffff && head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] {
  189. self.try_do(|p| {
  190. p.accept_char(b':')?;
  191. p.accept_ipv4_mapped_ipv6_part(tail, tail_idx)
  192. });
  193. }
  194. Ok(())
  195. }
  196. Some(_) => {
  197. // Tail or head section is too long
  198. Err(())
  199. }
  200. None if double_colon => {
  201. // The address ends with "::". E.g. 1234:: or ::
  202. Ok(())
  203. }
  204. None => {
  205. // Invalid address
  206. Err(())
  207. }
  208. }?;
  209. if *head_idx + *tail_idx > 8 {
  210. // The head and tail indexes add up to a bad address length.
  211. Err(())
  212. } else if !self.lookahead_char(b':') {
  213. if *head_idx < 8 && !use_tail {
  214. // There was no double colon found, and the head is too short
  215. return Err(());
  216. }
  217. Ok(())
  218. } else {
  219. // Continue recursing
  220. self.accept_ipv6_part((head, tail), (head_idx, tail_idx), use_tail)
  221. }
  222. }
  223. #[cfg(feature = "proto-ipv6")]
  224. fn accept_ipv6(&mut self) -> Result<Ipv6Address> {
  225. // IPv6 addresses may contain a "::" to indicate a series of
  226. // 16 bit sections that evaluate to 0. E.g.
  227. //
  228. // fe80:0000:0000:0000:0000:0000:0000:0001
  229. //
  230. // May be written as
  231. //
  232. // fe80::1
  233. //
  234. // As a result, we need to find the first section of colon
  235. // delimited u16's before a possible "::", then the
  236. // possible second section after the "::", and finally
  237. // combine the second optional section to the end of the
  238. // final address.
  239. //
  240. // See https://tools.ietf.org/html/rfc4291#section-2.2
  241. // for details.
  242. let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]);
  243. let (mut head_idx, mut tail_idx) = (0, 0);
  244. self.accept_ipv6_part(
  245. (&mut addr, &mut tail),
  246. (&mut head_idx, &mut tail_idx),
  247. false,
  248. )?;
  249. // We need to copy the tail portion (the portion following the "::") to the
  250. // end of the address.
  251. addr[8 - tail_idx..].copy_from_slice(&tail[..tail_idx]);
  252. Ok(Ipv6Address::from(addr))
  253. }
  254. fn accept_ipv4_octets(&mut self) -> Result<[u8; 4]> {
  255. let mut octets = [0u8; 4];
  256. for (n, octet) in octets.iter_mut().enumerate() {
  257. *octet = self.accept_number(3, 0x100, false)? as u8;
  258. if n != 3 {
  259. self.accept_char(b'.')?;
  260. }
  261. }
  262. Ok(octets)
  263. }
  264. #[cfg(feature = "proto-ipv4")]
  265. fn accept_ipv4(&mut self) -> Result<Ipv4Address> {
  266. let octets = self.accept_ipv4_octets()?;
  267. Ok(Ipv4Address::from_bytes(&octets))
  268. }
  269. fn accept_ip(&mut self) -> Result<IpAddress> {
  270. #[cfg(feature = "proto-ipv4")]
  271. #[allow(clippy::single_match)]
  272. match self.try_do(|p| p.accept_ipv4()) {
  273. Some(ipv4) => return Ok(IpAddress::Ipv4(ipv4)),
  274. None => (),
  275. }
  276. #[cfg(feature = "proto-ipv6")]
  277. #[allow(clippy::single_match)]
  278. match self.try_do(|p| p.accept_ipv6()) {
  279. Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)),
  280. None => (),
  281. }
  282. Err(())
  283. }
  284. #[cfg(feature = "proto-ipv4")]
  285. fn accept_ipv4_endpoint(&mut self) -> Result<IpEndpoint> {
  286. let ip = self.accept_ipv4()?;
  287. let port = if self.accept_eof().is_ok() {
  288. 0
  289. } else {
  290. self.accept_char(b':')?;
  291. self.accept_number(5, 65535, false)?
  292. };
  293. Ok(IpEndpoint {
  294. addr: IpAddress::Ipv4(ip),
  295. port: port as u16,
  296. })
  297. }
  298. #[cfg(feature = "proto-ipv6")]
  299. fn accept_ipv6_endpoint(&mut self) -> Result<IpEndpoint> {
  300. if self.lookahead_char(b'[') {
  301. self.accept_char(b'[')?;
  302. let ip = self.accept_ipv6()?;
  303. self.accept_char(b']')?;
  304. self.accept_char(b':')?;
  305. let port = self.accept_number(5, 65535, false)?;
  306. Ok(IpEndpoint {
  307. addr: IpAddress::Ipv6(ip),
  308. port: port as u16,
  309. })
  310. } else {
  311. let ip = self.accept_ipv6()?;
  312. Ok(IpEndpoint {
  313. addr: IpAddress::Ipv6(ip),
  314. port: 0,
  315. })
  316. }
  317. }
  318. fn accept_ip_endpoint(&mut self) -> Result<IpEndpoint> {
  319. #[cfg(feature = "proto-ipv4")]
  320. #[allow(clippy::single_match)]
  321. match self.try_do(|p| p.accept_ipv4_endpoint()) {
  322. Some(ipv4) => return Ok(ipv4),
  323. None => (),
  324. }
  325. #[cfg(feature = "proto-ipv6")]
  326. #[allow(clippy::single_match)]
  327. match self.try_do(|p| p.accept_ipv6_endpoint()) {
  328. Some(ipv6) => return Ok(ipv6),
  329. None => (),
  330. }
  331. Err(())
  332. }
  333. }
  334. #[cfg(feature = "medium-ethernet")]
  335. impl FromStr for EthernetAddress {
  336. type Err = ();
  337. /// Parse a string representation of an Ethernet address.
  338. fn from_str(s: &str) -> Result<EthernetAddress> {
  339. Parser::new(s).until_eof(|p| p.accept_mac())
  340. }
  341. }
  342. impl FromStr for IpAddress {
  343. type Err = ();
  344. /// Parse a string representation of an IP address.
  345. fn from_str(s: &str) -> Result<IpAddress> {
  346. Parser::new(s).until_eof(|p| p.accept_ip())
  347. }
  348. }
  349. #[cfg(feature = "proto-ipv4")]
  350. impl FromStr for Ipv4Cidr {
  351. type Err = ();
  352. /// Parse a string representation of an IPv4 CIDR.
  353. fn from_str(s: &str) -> Result<Ipv4Cidr> {
  354. Parser::new(s).until_eof(|p| {
  355. let ip = p.accept_ipv4()?;
  356. p.accept_char(b'/')?;
  357. let prefix_len = p.accept_number(2, 33, false)? as u8;
  358. Ok(Ipv4Cidr::new(ip, prefix_len))
  359. })
  360. }
  361. }
  362. #[cfg(feature = "proto-ipv6")]
  363. impl FromStr for Ipv6Cidr {
  364. type Err = ();
  365. /// Parse a string representation of an IPv6 CIDR.
  366. fn from_str(s: &str) -> Result<Ipv6Cidr> {
  367. // https://tools.ietf.org/html/rfc4291#section-2.3
  368. Parser::new(s).until_eof(|p| {
  369. let ip = p.accept_ipv6()?;
  370. p.accept_char(b'/')?;
  371. let prefix_len = p.accept_number(3, 129, false)? as u8;
  372. Ok(Ipv6Cidr::new(ip, prefix_len))
  373. })
  374. }
  375. }
  376. impl FromStr for IpCidr {
  377. type Err = ();
  378. /// Parse a string representation of an IP CIDR.
  379. fn from_str(s: &str) -> Result<IpCidr> {
  380. #[cfg(feature = "proto-ipv4")]
  381. #[allow(clippy::single_match)]
  382. match Ipv4Cidr::from_str(s) {
  383. Ok(cidr) => return Ok(IpCidr::Ipv4(cidr)),
  384. Err(_) => (),
  385. }
  386. #[cfg(feature = "proto-ipv6")]
  387. #[allow(clippy::single_match)]
  388. match Ipv6Cidr::from_str(s) {
  389. Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)),
  390. Err(_) => (),
  391. }
  392. Err(())
  393. }
  394. }
  395. impl FromStr for IpEndpoint {
  396. type Err = ();
  397. fn from_str(s: &str) -> Result<IpEndpoint> {
  398. Parser::new(s).until_eof(|p| p.accept_ip_endpoint())
  399. }
  400. }
  401. #[cfg(test)]
  402. mod test {
  403. use super::*;
  404. macro_rules! check_cidr_test_array {
  405. ($tests:expr, $from_str:path, $variant:path) => {
  406. for &(s, cidr) in &$tests {
  407. assert_eq!($from_str(s), cidr);
  408. assert_eq!(IpCidr::from_str(s), cidr.map($variant));
  409. if let Ok(cidr) = cidr {
  410. assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr));
  411. assert_eq!(IpCidr::from_str(&format!("{}", cidr)), Ok($variant(cidr)));
  412. }
  413. }
  414. };
  415. }
  416. #[test]
  417. #[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
  418. fn test_mac() {
  419. assert_eq!(EthernetAddress::from_str(""), Err(()));
  420. assert_eq!(
  421. EthernetAddress::from_str("02:00:00:00:00:00"),
  422. Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00]))
  423. );
  424. assert_eq!(
  425. EthernetAddress::from_str("01:23:45:67:89:ab"),
  426. Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab]))
  427. );
  428. assert_eq!(
  429. EthernetAddress::from_str("cd:ef:10:00:00:00"),
  430. Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00]))
  431. );
  432. assert_eq!(
  433. EthernetAddress::from_str("00:00:00:ab:cd:ef"),
  434. Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
  435. );
  436. assert_eq!(
  437. EthernetAddress::from_str("00-00-00-ab-cd-ef"),
  438. Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
  439. );
  440. assert_eq!(
  441. EthernetAddress::from_str("AB-CD-EF-00-00-00"),
  442. Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00]))
  443. );
  444. assert_eq!(EthernetAddress::from_str("100:00:00:00:00:00"), Err(()));
  445. assert_eq!(EthernetAddress::from_str("002:00:00:00:00:00"), Err(()));
  446. assert_eq!(EthernetAddress::from_str("02:00:00:00:00:000"), Err(()));
  447. assert_eq!(EthernetAddress::from_str("02:00:00:00:00:0x"), Err(()));
  448. }
  449. #[test]
  450. #[cfg(feature = "proto-ipv4")]
  451. fn test_ip_ipv4() {
  452. assert_eq!(IpAddress::from_str(""), Err(()));
  453. assert_eq!(
  454. IpAddress::from_str("1.2.3.4"),
  455. Ok(IpAddress::Ipv4(Ipv4Address::new(1, 2, 3, 4)))
  456. );
  457. assert_eq!(IpAddress::from_str("x"), Err(()));
  458. }
  459. #[test]
  460. #[cfg(feature = "proto-ipv6")]
  461. fn test_ip_ipv6() {
  462. assert_eq!(IpAddress::from_str(""), Err(()));
  463. assert_eq!(
  464. IpAddress::from_str("fe80::1"),
  465. Ok(IpAddress::Ipv6(Ipv6Address::new(
  466. 0xfe80, 0, 0, 0, 0, 0, 0, 1
  467. )))
  468. );
  469. assert_eq!(IpAddress::from_str("x"), Err(()));
  470. }
  471. #[test]
  472. #[cfg(feature = "proto-ipv4")]
  473. fn test_cidr_ipv4() {
  474. let tests = [
  475. (
  476. "127.0.0.1/8",
  477. Ok(Ipv4Cidr::new(Ipv4Address::new(127, 0, 0, 1), 8u8)),
  478. ),
  479. (
  480. "192.168.1.1/24",
  481. Ok(Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 1), 24u8)),
  482. ),
  483. (
  484. "8.8.8.8/32",
  485. Ok(Ipv4Cidr::new(Ipv4Address::new(8, 8, 8, 8), 32u8)),
  486. ),
  487. (
  488. "8.8.8.8/0",
  489. Ok(Ipv4Cidr::new(Ipv4Address::new(8, 8, 8, 8), 0u8)),
  490. ),
  491. ("", Err(())),
  492. ("1", Err(())),
  493. ("127.0.0.1", Err(())),
  494. ("127.0.0.1/", Err(())),
  495. ("127.0.0.1/33", Err(())),
  496. ("127.0.0.1/111", Err(())),
  497. ("/32", Err(())),
  498. ];
  499. check_cidr_test_array!(tests, Ipv4Cidr::from_str, IpCidr::Ipv4);
  500. }
  501. #[test]
  502. #[cfg(feature = "proto-ipv6")]
  503. fn test_cidr_ipv6() {
  504. let tests = [
  505. (
  506. "fe80::1/64",
  507. Ok(Ipv6Cidr::new(
  508. Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
  509. 64u8,
  510. )),
  511. ),
  512. (
  513. "fe80::/64",
  514. Ok(Ipv6Cidr::new(
  515. Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
  516. 64u8,
  517. )),
  518. ),
  519. ("::1/128", Ok(Ipv6Cidr::new(Ipv6Address::LOCALHOST, 128u8))),
  520. ("::/128", Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))),
  521. (
  522. "fe80:0:0:0:0:0:0:1/64",
  523. Ok(Ipv6Cidr::new(
  524. Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
  525. 64u8,
  526. )),
  527. ),
  528. ("fe80:0:0:0:0:0:0:1|64", Err(())),
  529. ("fe80::|64", Err(())),
  530. ("fe80::1::/64", Err(())),
  531. ];
  532. check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6);
  533. }
  534. #[test]
  535. #[cfg(feature = "proto-ipv4")]
  536. fn test_endpoint_ipv4() {
  537. assert_eq!(IpEndpoint::from_str(""), Err(()));
  538. assert_eq!(IpEndpoint::from_str("x"), Err(()));
  539. assert_eq!(
  540. IpEndpoint::from_str("127.0.0.1"),
  541. Ok(IpEndpoint {
  542. addr: IpAddress::v4(127, 0, 0, 1),
  543. port: 0
  544. })
  545. );
  546. assert_eq!(
  547. IpEndpoint::from_str("127.0.0.1:12345"),
  548. Ok(IpEndpoint {
  549. addr: IpAddress::v4(127, 0, 0, 1),
  550. port: 12345
  551. })
  552. );
  553. }
  554. #[test]
  555. #[cfg(feature = "proto-ipv6")]
  556. fn test_endpoint_ipv6() {
  557. assert_eq!(IpEndpoint::from_str(""), Err(()));
  558. assert_eq!(IpEndpoint::from_str("x"), Err(()));
  559. assert_eq!(
  560. IpEndpoint::from_str("fe80::1"),
  561. Ok(IpEndpoint {
  562. addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
  563. port: 0
  564. })
  565. );
  566. assert_eq!(
  567. IpEndpoint::from_str("[fe80::1]:12345"),
  568. Ok(IpEndpoint {
  569. addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
  570. port: 12345
  571. })
  572. );
  573. assert_eq!(
  574. IpEndpoint::from_str("[::]:12345"),
  575. Ok(IpEndpoint {
  576. addr: IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0),
  577. port: 12345
  578. })
  579. );
  580. }
  581. }