nreplops_tool/conn_expr/
addr.rs

1// conn_expr/addr.rs
2// Copyright 2022 Matti Hänninen
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License. You may obtain a copy of
6// the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations under
14// the License.
15
16use std::{fmt, net, str};
17
18use super::parser::{ConnectionExprLanguage, Pair, Parser, Rule};
19
20#[derive(Clone, Debug, PartialEq)]
21pub enum Addr {
22  Domain(String),
23  IP(net::IpAddr),
24}
25
26impl fmt::Display for Addr {
27  fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
28    match self {
29      Addr::Domain(domain) => domain.fmt(fmt),
30      Addr::IP(ip) => ip.fmt(fmt),
31    }
32  }
33}
34
35impl<'a> TryFrom<Pair<'a, Rule>> for Addr {
36  type Error = ConversionError;
37
38  fn try_from(pair: Pair<'a, Rule>) -> Result<Self, Self::Error> {
39    if matches!(pair.as_rule(), Rule::addr) {
40      let addr = pair
41        .into_inner()
42        .next()
43        .expect("grammar guarantees specific inner address");
44      Ok(match addr.as_rule() {
45        Rule::ipv4_addr => Addr::IP(
46          addr
47            .as_str()
48            .parse::<net::Ipv4Addr>()
49            .expect("grammar guarantees legal IPv4 address")
50            .into(),
51        ),
52        Rule::ipv6_addr => Addr::IP(
53          addr
54            .as_str()
55            .parse::<net::Ipv6Addr>()
56            .expect("grammar guarantees legal IPv6 address")
57            .into(),
58        ),
59        Rule::domain_addr => {
60          let domain = addr.as_str().to_owned();
61          if addr.as_str().len() > 253
62            || addr.into_inner().any(|l| l.as_str().len() > 63)
63          {
64            return Err(ConversionError);
65          }
66          Addr::Domain(domain)
67        }
68        _ => unreachable!("grammar guarantees IPv6, IPv4, or domain address"),
69      })
70    } else {
71      Err(ConversionError)
72    }
73  }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
77#[error("failed to convert into address")]
78pub struct ConversionError;
79
80impl str::FromStr for Addr {
81  type Err = ParseError;
82
83  fn from_str(s: &str) -> Result<Self, Self::Err> {
84    ConnectionExprLanguage::parse(Rule::addr_expr, s)
85      .map_err(|_| ParseError)?
86      .next()
87      .expect("grammar guaranteed addr_expr")
88      .into_inner()
89      .next()
90      .expect("grammar guarantees addr")
91      .try_into()
92      .map_err(|_| ParseError)
93  }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
97#[error("failed to parse address")]
98pub struct ParseError;
99
100#[cfg(test)]
101mod test {
102
103  use super::*;
104
105  #[test]
106  fn parse_domain_name() {
107    let d = |s: &str| Ok(Addr::Domain(s.to_string()));
108    assert_eq!("localhost".parse(), d("localhost"));
109    assert_eq!("localhost.".parse(), d("localhost."));
110    assert_eq!("a.b.c.d".parse(), d("a.b.c.d"));
111    assert_eq!("a1.b-2.c--3.d---4".parse(), d("a1.b-2.c--3.d---4"));
112  }
113
114  #[test]
115  fn parse_ipv4_address() {
116    let ip4 = |a, b, c, d| Ok(Addr::IP(net::Ipv4Addr::new(a, b, c, d).into()));
117    assert_eq!("0.0.0.0".parse(), ip4(0, 0, 0, 0));
118    assert_eq!("1.2.3.4".parse(), ip4(1, 2, 3, 4));
119    assert_eq!("255.255.255.255".parse(), ip4(255, 255, 255, 255));
120  }
121
122  #[test]
123  fn parse_ipv6_address() {
124    let ip6 = |a, b, c, d, e, f, g, h| {
125      Ok(Addr::IP(net::Ipv6Addr::new(a, b, c, d, e, f, g, h).into()))
126    };
127    assert_eq!("[::]".parse(), ip6(0, 0, 0, 0, 0, 0, 0, 0));
128    assert_eq!("[::1]".parse(), ip6(0, 0, 0, 0, 0, 0, 0, 1));
129    assert_eq!("[1::]".parse(), ip6(1, 0, 0, 0, 0, 0, 0, 0));
130    assert_eq!("[::0.0.0.0]".parse(), ip6(0, 0, 0, 0, 0, 0, 0, 0));
131    assert_eq!(
132      "[DEAD::BEEF]".parse(),
133      ip6(0xDEAD, 0, 0, 0, 0, 0, 0, 0xBEEF)
134    );
135    assert_eq!(
136      "[dead::beef]".parse(),
137      ip6(0xDEAD, 0, 0, 0, 0, 0, 0, 0xBEEF)
138    );
139    assert_eq!(
140      "[1:23:456:789a::127.0.0.1]".parse(),
141      ip6(0x0001, 0x0023, 0x0456, 0x789A, 0, 0, 0x7F00, 1),
142    );
143  }
144
145  #[test]
146  fn parse_bad_address() {
147    let err = Err(ParseError);
148    assert_eq!(" 1.2.3.4".parse::<Addr>(), err);
149    assert_eq!("".parse::<Addr>(), err);
150    assert_eq!(".".parse::<Addr>(), err);
151    assert_eq!("01.2.3.4".parse::<Addr>(), err);
152    assert_eq!("1. 2.3.4".parse::<Addr>(), err);
153    assert_eq!("1.2.3.04".parse::<Addr>(), err);
154    assert_eq!("1.2.3.256".parse::<Addr>(), err);
155    assert_eq!("1.2.3.4 ".parse::<Addr>(), err);
156    assert_eq!("1.2.3.4.com".parse::<Addr>(), err);
157    assert_eq!("1.com".parse::<Addr>(), err);
158    assert_eq!("[ ::]".parse::<Addr>(), err);
159    assert_eq!("[:: ]".parse::<Addr>(), err);
160    assert_eq!("[]".parse::<Addr>(), err);
161    assert_eq!("dash-.com".parse::<Addr>(), err);
162    assert_eq!("tyre.8bar.com".parse::<Addr>(), err);
163    // XXX(soija) These are actually legal IP addresses but ¯\_(ツ)_/¯
164    assert_eq!("1".parse(), err);
165    assert_eq!("1.2".parse(), err);
166    assert_eq!("1.2.3".parse(), err);
167  }
168}