1mod addrpolicy;
20mod portpolicy;
21
22use std::fmt;
23use std::str::FromStr;
24use std::{collections::BTreeSet, fmt::Display};
25use thiserror::Error;
26use tor_basic_utils::iter_join;
27
28pub use addrpolicy::{AddrPolicy, AddrPortPattern};
29pub use portpolicy::PortPolicy;
30
31use crate::NormalItemArgument;
32use crate::parse2::{ArgumentError, ArgumentStream, ItemArgumentParseable};
33
34#[derive(Debug, Error, Clone, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum PolicyError {
38 #[error("Invalid port")]
40 InvalidPort,
41 #[error("Invalid port range")]
43 InvalidRange,
44 #[error("Invalid address")]
46 InvalidAddress,
47 #[error("mask or prefix length with star")]
50 MaskWithStar,
51 #[error("invalid prefix length or mask")]
54 InvalidMask,
55 #[error("Invalid policy")]
57 InvalidPolicy,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
75#[allow(clippy::exhaustive_structs)]
76pub struct PortRange {
77 lo: u16,
79 hi: u16,
81}
82
83impl PortRange {
84 const fn new_unchecked(lo: u16, hi: u16) -> Self {
87 assert!(lo != 0);
88 assert!(lo <= hi);
89 PortRange { lo, hi }
90 }
91 pub const fn new_all() -> Self {
93 PortRange::new_unchecked(1, 65535)
94 }
95 pub fn new(lo: u16, hi: u16) -> Option<Self> {
101 if lo != 0 && lo <= hi {
102 Some(PortRange { lo, hi })
103 } else {
104 None
105 }
106 }
107 pub fn contains(&self, port: u16) -> bool {
109 self.lo <= port && port <= self.hi
110 }
111 pub fn is_all(&self) -> bool {
113 self.lo == 1 && self.hi == 65535
114 }
115
116 fn compare_to_port(&self, port: u16) -> std::cmp::Ordering {
122 use std::cmp::Ordering::*;
123 if port < self.lo {
124 Greater
125 } else if port <= self.hi {
126 Equal
127 } else {
128 Less
129 }
130 }
131}
132
133impl Display for PortRange {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 if self.lo == self.hi {
139 write!(f, "{}", self.lo)
140 } else {
141 write!(f, "{}-{}", self.lo, self.hi)
142 }
143 }
144}
145
146impl FromStr for PortRange {
147 type Err = PolicyError;
148 fn from_str(s: &str) -> Result<Self, PolicyError> {
149 let (lo, hi) = match s.split_once('-') {
150 Some((lo, hi)) => (
151 lo.parse::<u16>().map_err(|_| PolicyError::InvalidPort)?,
152 hi.parse::<u16>().map_err(|_| PolicyError::InvalidPort)?,
153 ),
154 None => {
155 let v = s.parse::<u16>().map_err(|_| PolicyError::InvalidPort)?;
157 (v, v)
158 }
159 };
160 PortRange::new(lo, hi).ok_or(PolicyError::InvalidRange)
161 }
162}
163
164impl NormalItemArgument for PortRange {}
165
166#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
173struct PortRanges(Vec<PortRange>);
177
178impl PortRanges {
179 fn new() -> Self {
181 Self(Vec::new())
182 }
183
184 fn is_empty(&self) -> bool {
186 self.0.is_empty()
187 }
188
189 fn push_ordered(&mut self, item: PortRange) -> Result<(), PolicyError> {
195 if let Some(prev) = self.0.last() {
196 if prev.hi >= item.lo {
199 return Err(PolicyError::InvalidPolicy);
200 } else if prev.hi == item.lo - 1 {
201 let r = PortRange::new_unchecked(prev.lo, item.hi);
203 self.0.pop();
204 self.0.push(r);
205 return Ok(());
206 }
207 }
208
209 self.0.push(item);
210 Ok(())
211 }
212
213 fn contains(&self, port: u16) -> bool {
219 debug_assert!(self.0.is_sorted_by(|a, b| a.lo < b.lo));
220 self.0
221 .binary_search_by(|range| range.compare_to_port(port))
222 .is_ok()
223 }
224
225 fn inverted(&self) -> PortRanges {
229 let mut prev_hi = 0;
230 let mut new_allowed = Vec::new();
231 for entry in &self.0 {
232 if entry.lo > prev_hi + 1 {
235 new_allowed.push(PortRange::new_unchecked(prev_hi + 1, entry.lo - 1));
236 }
237 prev_hi = entry.hi;
238 }
239 if prev_hi < 65535 {
240 new_allowed.push(PortRange::new_unchecked(prev_hi + 1, 65535));
241 }
242 PortRanges(new_allowed)
243 }
244
245 fn invert(&mut self) {
249 *self = self.inverted();
250 }
251
252 fn iter(&self) -> impl Iterator<Item = &PortRange> + Clone {
254 self.0.iter()
255 }
256
257 fn display(&self) -> Option<impl Display + '_> {
265 struct DisplayWrapper<'r>(&'r PortRanges);
266
267 impl Display for DisplayWrapper<'_> {
268 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269 write!(f, "{}", iter_join(",", self.0.iter()))
270 }
271 }
272
273 (!self.is_empty()).then_some(DisplayWrapper(self))
274 }
275}
276
277impl FromIterator<u16> for PortRanges {
278 fn from_iter<I: IntoIterator<Item = u16>>(iter: I) -> Self {
279 let ports = iter.into_iter().collect::<BTreeSet<_>>();
281 let mut ports = ports.into_iter().peekable();
282
283 let mut out = Self::new();
284 let mut current_min = None;
285 while let Some(port) = ports.next() {
286 if current_min.is_none() {
287 current_min = Some(port);
288 }
289 if let Some(next_port) = ports.peek().copied() {
290 if next_port != port + 1 {
295 let _ = out.push_ordered(PortRange::new_unchecked(
296 current_min.expect("Don't have min port number"),
297 port,
298 ));
299 current_min = None;
300 }
301 } else {
302 let _ = out.push_ordered(PortRange::new_unchecked(
303 current_min.expect("Don't have min port number"),
304 port,
305 ));
306 }
307 }
308
309 out
310 }
311}
312
313impl FromStr for PortRanges {
314 type Err = PolicyError;
315
316 fn from_str(s: &str) -> Result<Self, Self::Err> {
317 let mut ranges = Self::new();
320 for range in s.split(',') {
321 ranges.push_ordered(range.parse()?)?;
322 }
323 Ok(ranges)
324 }
325}
326
327impl ItemArgumentParseable for PortRanges {
328 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError> {
331 args.next()
332 .map(Self::from_str)
333 .unwrap_or(Ok(Self::new()))
334 .map_err(|_| ArgumentError::Invalid)
335 }
336}
337
338#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, derive_more::Display, derive_more::FromStr)]
341#[display(rename_all = "lowercase")]
342#[from_str(rename_all = "lowercase")]
343#[allow(clippy::exhaustive_enums)]
344pub enum RuleKind {
345 Accept,
347 Reject,
349}
350
351impl NormalItemArgument for RuleKind {}
352
353#[cfg(test)]
354mod test {
355 #![allow(clippy::bool_assert_comparison)]
357 #![allow(clippy::clone_on_copy)]
358 #![allow(clippy::dbg_macro)]
359 #![allow(clippy::mixed_attributes_style)]
360 #![allow(clippy::print_stderr)]
361 #![allow(clippy::print_stdout)]
362 #![allow(clippy::single_char_pattern)]
363 #![allow(clippy::unwrap_used)]
364 #![allow(clippy::unchecked_time_subtraction)]
365 #![allow(clippy::useless_vec)]
366 #![allow(clippy::needless_pass_by_value)]
367 #![allow(clippy::string_slice)] use super::*;
370 use crate::Result;
371 use crate::parse2::{self, ParseInput};
372
373 #[test]
374 fn parse_portrange() -> Result<()> {
375 assert_eq!(
376 "1-100".parse::<PortRange>()?,
377 PortRange::new(1, 100).unwrap()
378 );
379 assert_eq!(
380 "01-100".parse::<PortRange>()?,
381 PortRange::new(1, 100).unwrap()
382 );
383 assert_eq!("1-65535".parse::<PortRange>()?, PortRange::new_all());
384 assert_eq!(
385 "10-30".parse::<PortRange>()?,
386 PortRange::new(10, 30).unwrap()
387 );
388 assert_eq!(
389 "9001".parse::<PortRange>()?,
390 PortRange::new(9001, 9001).unwrap()
391 );
392 assert_eq!(
393 "9001-9001".parse::<PortRange>()?,
394 PortRange::new(9001, 9001).unwrap()
395 );
396
397 assert!("hello".parse::<PortRange>().is_err());
398 assert!("0".parse::<PortRange>().is_err());
399 assert!("65536".parse::<PortRange>().is_err());
400 assert!("65537".parse::<PortRange>().is_err());
401 assert!("1-2-3".parse::<PortRange>().is_err());
402 assert!("10-5".parse::<PortRange>().is_err());
403 assert!("1-".parse::<PortRange>().is_err());
404 assert!("-2".parse::<PortRange>().is_err());
405 assert!("-".parse::<PortRange>().is_err());
406 assert!("*".parse::<PortRange>().is_err());
407 Ok(())
408 }
409
410 #[test]
411 fn pr_manip() {
412 assert!(PortRange::new_all().is_all());
413 assert!(!PortRange::new(2, 65535).unwrap().is_all());
414
415 assert!(PortRange::new_all().contains(1));
416 assert!(PortRange::new_all().contains(65535));
417 assert!(PortRange::new_all().contains(7777));
418
419 assert!(PortRange::new(20, 30).unwrap().contains(20));
420 assert!(PortRange::new(20, 30).unwrap().contains(25));
421 assert!(PortRange::new(20, 30).unwrap().contains(30));
422 assert!(!PortRange::new(20, 30).unwrap().contains(19));
423 assert!(!PortRange::new(20, 30).unwrap().contains(31));
424
425 use std::cmp::Ordering::*;
426 assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(7), Greater);
427 assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(20), Equal);
428 assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(25), Equal);
429 assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(30), Equal);
430 assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(100), Less);
431 }
432
433 #[test]
434 fn pr_fmt() {
435 fn chk(a: u16, b: u16, s: &str) {
436 let pr = PortRange::new(a, b).unwrap();
437 assert_eq!(format!("{}", pr), s);
438 }
439
440 chk(1, 65535, "1-65535");
441 chk(10, 20, "10-20");
442 chk(20, 20, "20");
443 }
444
445 #[test]
446 fn port_ranges() {
447 const INPUT: &str = "22,80,443,8000-9000,9002";
448 let ranges = PortRanges::from_str(INPUT).unwrap();
449 assert_eq!(
450 ranges.0,
451 [
452 PortRange::new(22, 22).unwrap(),
453 PortRange::new(80, 80).unwrap(),
454 PortRange::new(443, 443).unwrap(),
455 PortRange::new(8000, 9000).unwrap(),
456 PortRange::new(9002, 9002).unwrap(),
457 ]
458 );
459 assert!(ranges.contains(22));
460 assert!(ranges.contains(80));
461 assert!(ranges.contains(443));
462 assert!(ranges.contains(8000));
463 assert!(ranges.contains(8500));
464 assert!(ranges.contains(9000));
465 assert!(!ranges.contains(9001));
466 assert!(ranges.contains(9002));
467
468 let mut ranges_inverse = ranges.clone();
469 ranges_inverse.invert();
470 assert_eq!(
471 ranges_inverse.0,
472 [
473 PortRange::new(1, 21).unwrap(),
474 PortRange::new(23, 79).unwrap(),
475 PortRange::new(81, 442).unwrap(),
476 PortRange::new(444, 7999).unwrap(),
477 PortRange::new(9001, 9001).unwrap(),
478 PortRange::new(9003, 65535).unwrap(),
479 ]
480 );
481
482 #[derive(derive_deftly::Deftly)]
483 #[derive_deftly(NetdocParseable)]
484 struct Dummy {
485 #[deftly(netdoc(single_arg))]
486 dummy: PortRanges,
487 }
488 let ranges2 =
489 parse2::parse_netdoc::<Dummy>(&ParseInput::new(&format!("dummy {INPUT}\n"), ""))
490 .unwrap();
491 assert_eq!(ranges, ranges2.dummy);
492 }
493}