nreplops_tool/conn_expr/
conn_expr.rs

1// conn_expr/conn_expr.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::str;
17
18use super::{
19  addr::Addr,
20  parser::{ConnectionExprLanguage, Pairs, Parser, Rule},
21  port_set::PortSet,
22};
23
24#[derive(Clone, Debug, PartialEq)]
25pub enum ConnectionExpr {
26  RouteExpr(RouteExpr),
27  HostKey(String),
28}
29
30impl ConnectionExpr {
31  pub fn try_as_route_expr(&self) -> Option<&RouteExpr> {
32    if let ConnectionExpr::RouteExpr(ref e) = *self {
33      Some(e)
34    } else {
35      None
36    }
37  }
38}
39
40impl From<RouteExpr> for ConnectionExpr {
41  fn from(route_expr: RouteExpr) -> Self {
42    ConnectionExpr::RouteExpr(route_expr)
43  }
44}
45
46#[derive(Clone, Debug, PartialEq)]
47pub struct RouteExpr {
48  pub ports: PortSet,
49  pub addr: Option<Addr>,
50  pub tunnel: Option<TunnelExpr>,
51}
52
53#[derive(Clone, Debug, PartialEq)]
54pub struct TunnelExpr {
55  pub user: Option<String>,
56  pub addr: Addr,
57  pub ports: Option<PortSet>,
58}
59
60impl str::FromStr for ConnectionExpr {
61  type Err = ParseError;
62
63  fn from_str(s: &str) -> Result<Self, Self::Err> {
64    let top_pair = ConnectionExprLanguage::parse(Rule::connection_expr, s)
65      .map_err(|_| ParseError)?
66      .next()
67      .expect("grammar guarantees host expression")
68      .into_inner()
69      .next()
70      .expect("grammar guarantees inner specific host expression");
71    match top_pair.as_rule() {
72      Rule::local_connection_expr => {
73        connection_expr_from_local_connection_expr_pair(top_pair.into_inner())
74          .map(|e| e.into())
75      }
76      Rule::remote_connection_expr => {
77        connection_expr_from_remote_connection_expr_pair(top_pair.into_inner())
78          .map(|e| e.into())
79      }
80      Rule::tunneled_connection_expr => {
81        connection_expr_from_tunneled_connection_expr_pair(
82          top_pair.into_inner(),
83        )
84        .map(|e| e.into())
85      }
86      Rule::host_key_expr => {
87        Ok(ConnectionExpr::HostKey(top_pair.as_str().to_string()))
88      }
89      _ => unreachable!(
90        r#"grammar guarantees a local, remote, or tunneled route
91                   expression to a remote host, or a host key reference"#
92      ),
93    }
94  }
95}
96
97fn connection_expr_from_local_connection_expr_pair(
98  mut pairs: Pairs<Rule>,
99) -> Result<RouteExpr, ParseError> {
100  Ok(RouteExpr {
101    ports: pairs
102      .next()
103      .expect("grammar guarantees a port set")
104      .try_into()
105      // grammar does not limit the port to u16
106      .map_err(|_| ParseError)?,
107    addr: None,
108    tunnel: None,
109  })
110}
111
112fn connection_expr_from_remote_connection_expr_pair(
113  mut pairs: Pairs<Rule>,
114) -> Result<RouteExpr, ParseError> {
115  let addr = pairs
116    .next()
117    .expect("grammar guarantees an address")
118    .try_into()
119    .expect("grammar guarantees the address is legal");
120  let mut connection_expr = connection_expr_from_local_connection_expr_pair(
121    pairs
122      .next()
123      .expect("grammar guarantees a local port expression")
124      .into_inner(),
125  )?;
126  connection_expr.addr = Some(addr);
127  Ok(connection_expr)
128}
129
130fn connection_expr_from_tunneled_connection_expr_pair(
131  pairs: Pairs<Rule>,
132) -> Result<RouteExpr, ParseError> {
133  let (tunnel, mut pairs) = tunnel_from_pairs(pairs)?;
134  let mut connection_expr = connection_expr_from_remote_connection_expr_pair(
135    pairs
136      .next()
137      .expect("grammar guarantees a remote port expression")
138      .into_inner(),
139  )?;
140  connection_expr.tunnel = Some(tunnel);
141  Ok(connection_expr)
142}
143
144fn tunnel_from_pairs(
145  mut pairs: Pairs<Rule>,
146) -> Result<(TunnelExpr, Pairs<Rule>), ParseError> {
147  let mut next = pairs.next().expect("grammar guarantees a user or address");
148  let user = if matches!(next.as_rule(), Rule::user) {
149    let s = next.as_str().to_owned();
150    next = pairs.next().expect("grammar guarantees an address");
151    Some(s)
152  } else {
153    None
154  };
155  match next.as_rule() {
156    Rule::addr => Ok((
157      TunnelExpr {
158        user,
159        addr: next.try_into().expect("grammar guarantess addr is legal"),
160        ports: None,
161      },
162      pairs,
163    )),
164    Rule::addr_and_port => {
165      let mut inner = next.into_inner();
166      let addr = inner
167        .next()
168        .expect("addr by grammar")
169        .try_into()
170        .expect("correct by gramma");
171      let ports = inner
172        .next()
173        .expect("port_set by grammar")
174        .try_into()
175        .map_err(|_| ParseError)?;
176      Ok((
177        TunnelExpr {
178          user,
179          addr,
180          ports: Some(ports),
181        },
182        pairs,
183      ))
184    }
185    _ => unreachable!("grammar guarantees addr or addr_and_port"),
186  }
187}
188
189#[derive(Debug, PartialEq, thiserror::Error)]
190#[error("parse error")]
191pub struct ParseError;
192
193#[cfg(test)]
194mod test {
195
196  use super::*;
197  use std::net;
198
199  fn ps(ports: &[u16]) -> PortSet {
200    maybe_ps(ports).unwrap()
201  }
202
203  fn maybe_ps(ports: &[u16]) -> Option<PortSet> {
204    PortSet::try_from_iter(ports.iter().cloned())
205  }
206
207  fn ip4(a: u8, b: u8, c: u8, d: u8) -> Addr {
208    Addr::IP(net::Ipv4Addr::new(a, b, c, d).into())
209  }
210
211  fn ip6(
212    a: u16,
213    b: u16,
214    c: u16,
215    d: u16,
216    e: u16,
217    f: u16,
218    g: u16,
219    h: u16,
220  ) -> Addr {
221    Addr::IP(net::Ipv6Addr::new(a, b, c, d, e, f, g, h).into())
222  }
223
224  fn dom(domain: &str) -> Addr {
225    Addr::Domain(domain.to_owned())
226  }
227
228  #[test]
229  fn local_connection_expr_parsing() {
230    let mk = |ports| {
231      Ok(ConnectionExpr::RouteExpr(RouteExpr {
232        ports: ps(ports),
233        addr: None,
234        tunnel: None,
235      }))
236    };
237    assert_eq!("1".parse(), mk(&[1]));
238    assert_eq!("1,2".parse(), mk(&[1, 2]));
239    assert_eq!("1-3".parse(), mk(&[1, 2, 3]));
240    assert_eq!("1,3,1-3".parse(), mk(&[1, 3, 2]));
241  }
242
243  #[test]
244  fn remote_connection_expr_parsing() {
245    let mk = |addr, ports| {
246      Ok(ConnectionExpr::RouteExpr(RouteExpr {
247        ports: ps(ports),
248        addr: Some(addr),
249        tunnel: None,
250      }))
251    };
252    assert_eq!("1.2.3.4:1,2-3".parse(), mk(ip4(1, 2, 3, 4), &[1, 2, 3]));
253    assert_eq!(
254      "[0:dead::beef:0]:1,2-3".parse(),
255      mk(ip6(0, 0xDEAD, 0, 0, 0, 0, 0xBEEF, 0), &[1, 2, 3])
256    );
257    assert_eq!("localhost:1,2-3".parse(), mk(dom("localhost"), &[1, 2, 3]));
258  }
259
260  #[test]
261  fn tunneled_connection_expr_parsing() {
262    let mk = |tunnel_user: Option<&str>,
263              tunnel_addr,
264              tunnel_ports: &[u16],
265              server_addr,
266              server_ports| {
267      Ok(ConnectionExpr::RouteExpr(RouteExpr {
268        ports: ps(server_ports),
269        addr: Some(server_addr),
270        tunnel: Some(TunnelExpr {
271          user: tunnel_user.map(|s| s.to_owned()),
272          addr: tunnel_addr,
273          ports: maybe_ps(tunnel_ports),
274        }),
275      }))
276    };
277    assert_eq!(
278      "1.2.3.4:5.6.7.8:9".parse(),
279      mk(None, ip4(1, 2, 3, 4), &[], ip4(5, 6, 7, 8), &[9])
280    );
281    assert_eq!(
282      "1.2.3.4:5:6.7.8.9:10".parse(),
283      mk(None, ip4(1, 2, 3, 4), &[5], ip4(6, 7, 8, 9), &[10])
284    );
285    assert_eq!(
286      "1.2.3.4:5,6-7:8.9.10.11:12-14,15-16".parse(),
287      mk(
288        None,
289        ip4(1, 2, 3, 4),
290        &[5, 6, 7],
291        ip4(8, 9, 10, 11),
292        &[12, 13, 14, 15, 16]
293      )
294    );
295    assert_eq!(
296      "a@1.2.3.4:5.6.7.8:9".parse(),
297      mk(Some("a"), ip4(1, 2, 3, 4), &[], ip4(5, 6, 7, 8), &[9])
298    );
299    assert_eq!(
300      "a@1.2.3.4:5:6.7.8.9:10".parse(),
301      mk(Some("a"), ip4(1, 2, 3, 4), &[5], ip4(6, 7, 8, 9), &[10])
302    );
303    assert_eq!(
304      "[::]:[::]:1".parse(),
305      mk(
306        None,
307        ip6(0, 0, 0, 0, 0, 0, 0, 0),
308        &[],
309        ip6(0, 0, 0, 0, 0, 0, 0, 0),
310        &[1]
311      )
312    );
313    assert_eq!(
314      "[::]:1:[::]:1".parse(),
315      mk(
316        None,
317        ip6(0, 0, 0, 0, 0, 0, 0, 0),
318        &[1],
319        ip6(0, 0, 0, 0, 0, 0, 0, 0),
320        &[1]
321      )
322    );
323    assert_eq!(
324      "a@[1:2:3:4:5:6:7:8]:9:[10:11:12:13:14:15:16:17]:18".parse(),
325      mk(
326        Some("a"),
327        ip6(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
328        &[9],
329        ip6(0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17),
330        &[18]
331      )
332    );
333    assert_eq!(
334      "a@b.c.d:e.f.g:1".parse(),
335      mk(Some("a"), dom("b.c.d"), &[], dom("e.f.g"), &[1])
336    );
337    assert_eq!(
338      "a@b.c.d.:1:e.f.g.:2".parse(),
339      mk(Some("a"), dom("b.c.d."), &[1], dom("e.f.g."), &[2])
340    );
341  }
342
343  #[test]
344  fn host_key_expr_parsing() {
345    let mk = |key: &str| Ok(ConnectionExpr::HostKey(key.to_owned()));
346    assert_eq!("x".parse(), mk("x"));
347    assert_eq!("my_prod_host_1".parse(), mk("my_prod_host_1"));
348  }
349}