mod addrpolicy;
mod portpolicy;
use std::str::FromStr;
use std::{collections::BTreeSet, fmt::Display};
use thiserror::Error;
pub use addrpolicy::{AddrPolicy, AddrPortPattern};
pub use portpolicy::PortPolicy;
use crate::NormalItemArgument;
use crate::parse2::{ArgumentError, ArgumentStream, ItemArgumentParseable};
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PolicyError {
#[error("Invalid port")]
InvalidPort,
#[error("Invalid port range")]
InvalidRange,
#[error("Invalid address")]
InvalidAddress,
#[error("mask with star")]
MaskWithStar,
#[error("invalid mask")]
InvalidMask,
#[error("Invalid policy")]
InvalidPolicy,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[allow(clippy::exhaustive_structs)]
pub struct PortRange {
lo: u16,
hi: u16,
}
impl PortRange {
fn new_unchecked(lo: u16, hi: u16) -> Self {
assert!(lo != 0);
assert!(lo <= hi);
PortRange { lo, hi }
}
pub fn new_all() -> Self {
PortRange::new_unchecked(1, 65535)
}
pub fn new(lo: u16, hi: u16) -> Option<Self> {
if lo != 0 && lo <= hi {
Some(PortRange { lo, hi })
} else {
None
}
}
pub fn contains(&self, port: u16) -> bool {
self.lo <= port && port <= self.hi
}
pub fn is_all(&self) -> bool {
self.lo == 1 && self.hi == 65535
}
fn compare_to_port(&self, port: u16) -> std::cmp::Ordering {
use std::cmp::Ordering::*;
if port < self.lo {
Greater
} else if port <= self.hi {
Equal
} else {
Less
}
}
}
impl Display for PortRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.lo == self.hi {
write!(f, "{}", self.lo)
} else {
write!(f, "{}-{}", self.lo, self.hi)
}
}
}
impl FromStr for PortRange {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
let idx = s.find('-');
let (lo, hi) = if let Some(pos) = idx {
(
s[..pos]
.parse::<u16>()
.map_err(|_| PolicyError::InvalidPort)?,
s[pos + 1..]
.parse::<u16>()
.map_err(|_| PolicyError::InvalidPort)?,
)
} else {
let v = s.parse::<u16>().map_err(|_| PolicyError::InvalidPort)?;
(v, v)
};
PortRange::new(lo, hi).ok_or(PolicyError::InvalidRange)
}
}
impl NormalItemArgument for PortRange {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct PortRanges(Vec<PortRange>);
impl PortRanges {
fn new() -> Self {
Self(Vec::new())
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn push_ordered(&mut self, item: PortRange) -> Result<(), PolicyError> {
if let Some(prev) = self.0.last() {
if prev.hi >= item.lo {
return Err(PolicyError::InvalidPolicy);
} else if prev.hi == item.lo - 1 {
let r = PortRange::new_unchecked(prev.lo, item.hi);
self.0.pop();
self.0.push(r);
return Ok(());
}
}
self.0.push(item);
Ok(())
}
fn contains(&self, port: u16) -> bool {
debug_assert!(self.0.is_sorted_by(|a, b| a.lo < b.lo));
self.0
.binary_search_by(|range| range.compare_to_port(port))
.is_ok()
}
fn invert(&mut self) {
let mut prev_hi = 0;
let mut new_allowed = Vec::new();
for entry in &self.0 {
if entry.lo > prev_hi + 1 {
new_allowed.push(PortRange::new_unchecked(prev_hi + 1, entry.lo - 1));
}
prev_hi = entry.hi;
}
if prev_hi < 65535 {
new_allowed.push(PortRange::new_unchecked(prev_hi + 1, 65535));
}
self.0 = new_allowed;
}
fn iter(&self) -> impl Iterator<Item = &PortRange> {
self.0.iter()
}
}
impl FromIterator<u16> for PortRanges {
fn from_iter<I: IntoIterator<Item = u16>>(iter: I) -> Self {
let ports = iter.into_iter().collect::<BTreeSet<_>>();
let mut ports = ports.into_iter().peekable();
let mut out = Self::new();
let mut current_min = None;
while let Some(port) = ports.next() {
if current_min.is_none() {
current_min = Some(port);
}
if let Some(next_port) = ports.peek().copied() {
if next_port != port + 1 {
let _ = out.push_ordered(PortRange::new_unchecked(
current_min.expect("Don't have min port number"),
port,
));
current_min = None;
}
} else {
let _ = out.push_ordered(PortRange::new_unchecked(
current_min.expect("Don't have min port number"),
port,
));
}
}
out
}
}
impl FromStr for PortRanges {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut ranges = Self::new();
for range in s.split(',') {
ranges.push_ordered(range.parse()?)?;
}
Ok(ranges)
}
}
impl ItemArgumentParseable for PortRanges {
fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError> {
args.next()
.map(Self::from_str)
.unwrap_or(Ok(Self::new()))
.map_err(|_| ArgumentError::Invalid)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, derive_more::Display, derive_more::FromStr)]
#[display(rename_all = "lowercase")]
#[from_str(rename_all = "lowercase")]
#[allow(clippy::exhaustive_enums)]
pub enum RuleKind {
Accept,
Reject,
}
impl NormalItemArgument for RuleKind {}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use crate::Result;
use crate::parse2::{self, ParseInput};
#[test]
fn parse_portrange() -> Result<()> {
assert_eq!(
"1-100".parse::<PortRange>()?,
PortRange::new(1, 100).unwrap()
);
assert_eq!(
"01-100".parse::<PortRange>()?,
PortRange::new(1, 100).unwrap()
);
assert_eq!("1-65535".parse::<PortRange>()?, PortRange::new_all());
assert_eq!(
"10-30".parse::<PortRange>()?,
PortRange::new(10, 30).unwrap()
);
assert_eq!(
"9001".parse::<PortRange>()?,
PortRange::new(9001, 9001).unwrap()
);
assert_eq!(
"9001-9001".parse::<PortRange>()?,
PortRange::new(9001, 9001).unwrap()
);
assert!("hello".parse::<PortRange>().is_err());
assert!("0".parse::<PortRange>().is_err());
assert!("65536".parse::<PortRange>().is_err());
assert!("65537".parse::<PortRange>().is_err());
assert!("1-2-3".parse::<PortRange>().is_err());
assert!("10-5".parse::<PortRange>().is_err());
assert!("1-".parse::<PortRange>().is_err());
assert!("-2".parse::<PortRange>().is_err());
assert!("-".parse::<PortRange>().is_err());
assert!("*".parse::<PortRange>().is_err());
Ok(())
}
#[test]
fn pr_manip() {
assert!(PortRange::new_all().is_all());
assert!(!PortRange::new(2, 65535).unwrap().is_all());
assert!(PortRange::new_all().contains(1));
assert!(PortRange::new_all().contains(65535));
assert!(PortRange::new_all().contains(7777));
assert!(PortRange::new(20, 30).unwrap().contains(20));
assert!(PortRange::new(20, 30).unwrap().contains(25));
assert!(PortRange::new(20, 30).unwrap().contains(30));
assert!(!PortRange::new(20, 30).unwrap().contains(19));
assert!(!PortRange::new(20, 30).unwrap().contains(31));
use std::cmp::Ordering::*;
assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(7), Greater);
assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(20), Equal);
assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(25), Equal);
assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(30), Equal);
assert_eq!(PortRange::new(20, 30).unwrap().compare_to_port(100), Less);
}
#[test]
fn pr_fmt() {
fn chk(a: u16, b: u16, s: &str) {
let pr = PortRange::new(a, b).unwrap();
assert_eq!(format!("{}", pr), s);
}
chk(1, 65535, "1-65535");
chk(10, 20, "10-20");
chk(20, 20, "20");
}
#[test]
fn port_ranges() {
const INPUT: &str = "22,80,443,8000-9000,9002";
let ranges = PortRanges::from_str(INPUT).unwrap();
assert_eq!(
ranges.0,
[
PortRange::new(22, 22).unwrap(),
PortRange::new(80, 80).unwrap(),
PortRange::new(443, 443).unwrap(),
PortRange::new(8000, 9000).unwrap(),
PortRange::new(9002, 9002).unwrap(),
]
);
assert!(ranges.contains(22));
assert!(ranges.contains(80));
assert!(ranges.contains(443));
assert!(ranges.contains(8000));
assert!(ranges.contains(8500));
assert!(ranges.contains(9000));
assert!(!ranges.contains(9001));
assert!(ranges.contains(9002));
let mut ranges_inverse = ranges.clone();
ranges_inverse.invert();
assert_eq!(
ranges_inverse.0,
[
PortRange::new(1, 21).unwrap(),
PortRange::new(23, 79).unwrap(),
PortRange::new(81, 442).unwrap(),
PortRange::new(444, 7999).unwrap(),
PortRange::new(9001, 9001).unwrap(),
PortRange::new(9003, 65535).unwrap(),
]
);
#[derive(derive_deftly::Deftly)]
#[derive_deftly(NetdocParseable)]
struct Dummy {
#[deftly(netdoc(single_arg))]
dummy: PortRanges,
}
let ranges2 =
parse2::parse_netdoc::<Dummy>(&ParseInput::new(&format!("dummy {INPUT}\n"), ""))
.unwrap();
assert_eq!(ranges, ranges2.dummy);
}
}