Skip to main content

http_ip/
filter.rs

1//!Filtering of IP addresses
2
3use core::{marker, fmt};
4use core::net::{IpAddr, SocketAddr};
5
6///Interface to define function that filters out IP address
7///
8///When match is found, IP address is skipped from being selected as client's IP (e.g. it is load balancer IP)
9pub trait Filter: Sized {
10    ///Returns `true` if `ip` matches
11    fn is_match(&self, ip: IpAddr) -> bool;
12    #[inline(always)]
13    ///Combines `self` with `right` filter in `OR` operation
14    fn or<F2: Filter>(self, right: F2) -> Or<Self, F2> {
15        or(self, right)
16    }
17}
18
19impl Filter for () {
20    #[inline(always)]
21    ///NULL filter, never matching
22    fn is_match(&self, _: IpAddr) -> bool {
23        false
24    }
25}
26
27impl Filter for IpAddr {
28    #[inline(always)]
29    fn is_match(&self, ip: IpAddr) -> bool {
30        *self == ip
31    }
32}
33
34impl Filter for SocketAddr {
35    #[inline(always)]
36    fn is_match(&self, ip: IpAddr) -> bool {
37        self.ip() == ip
38    }
39}
40
41///Combination of filters with `OR` condition
42pub struct Or<F1, F2> {
43    left: F1,
44    right: F2,
45}
46
47impl<F1: Filter, F2: Filter> Filter for Or<F1, F2> {
48    #[inline(always)]
49    fn is_match(&self, ip: IpAddr) -> bool {
50        self.left.is_match(ip) || self.right.is_match(ip)
51    }
52}
53
54///Collection of filters which are matched with `OR` condition
55///
56///`I` must be type that implements `AsRef<[impl Filter]>`
57pub struct CollectionOr<I, F> {
58    collection: I,
59    _filter: marker::PhantomData<F>,
60}
61
62impl<F: Filter, I: AsRef<[F]>> CollectionOr<I, F> {
63    #[inline(always)]
64    ///Creates new collection
65    pub const fn new(collection: I) -> Self {
66        Self {
67            collection,
68            _filter: marker::PhantomData
69        }
70    }
71}
72
73impl<F: Filter, I: AsRef<[F]>> Filter for CollectionOr<I, F> {
74    #[inline(always)]
75    fn is_match(&self, ip: IpAddr) -> bool {
76        self.collection.as_ref().iter().any(|filter| filter.is_match(ip))
77    }
78}
79
80#[derive(Debug, PartialEq, Eq)]
81//Possible errors parsing CIDR
82enum ParseError<'a> {
83    //Error parsing CIDR expression
84    ParseError(ip_cidr::ParseError<'a>),
85    //CIDR expression is valid, but its prefix does not fit type of IP address
86    InvalidPrefix
87}
88
89#[repr(transparent)]
90#[derive(PartialEq, Eq)]
91///Error which is returned when parsing CIDR's textual representation
92pub struct CidrParseError<'a>(ParseError<'a>);
93
94impl fmt::Debug for CidrParseError<'_> {
95    #[inline(always)]
96    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
97        fmt::Debug::fmt(&self.0, fmt)
98    }
99}
100
101impl fmt::Display for CidrParseError<'_> {
102    #[inline(always)]
103    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match &self.0 {
105            ParseError::InvalidPrefix => fmt.write_str("Invalid CIDR prefix"),
106            ParseError::ParseError(error) => fmt::Display::fmt(error, fmt),
107        }
108    }
109}
110
111#[repr(transparent)]
112#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
113///CIDR filter
114pub struct Cidr(ip_cidr::Cidr);
115
116impl Cidr {
117    #[inline]
118    ///Creates new instance from textual representation
119    pub const fn from_text(text: &str) -> Result<Self, CidrParseError<'_>> {
120        match ip_cidr::parse_cidr(text) {
121            Ok(Some(inner)) => Ok(Self(inner)),
122            Ok(None) => Err(CidrParseError(ParseError::InvalidPrefix)),
123            Err(error) => Err(CidrParseError(ParseError::ParseError(error))),
124        }
125    }
126
127    #[inline]
128    ///Creates new instance from IP and prefix, returning error if `prefix` is invalid
129    pub const fn new(ip: IpAddr, prefix: u8) -> Result<Self, CidrParseError<'static>> {
130        match ip_cidr::Cidr::new(ip, prefix) {
131            Some(cidr) => Ok(Self(cidr)),
132            None => Err(CidrParseError(ParseError::InvalidPrefix)),
133        }
134    }
135}
136
137impl Filter for Cidr {
138    #[inline(always)]
139    fn is_match(&self, ip: IpAddr) -> bool {
140        self.0.contains(ip)
141    }
142}
143
144impl fmt::Debug for Cidr {
145    #[inline(always)]
146    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
147        fmt::Debug::fmt(&self.0, fmt)
148    }
149}
150
151impl fmt::Display for Cidr {
152    #[inline(always)]
153    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
154        fmt::Display::fmt(&self.0, fmt)
155    }
156}
157
158#[inline]
159///Creates new `OR` filter out of two filters
160pub const fn or<F1, F2>(left: F1, right: F2) -> Or<F1, F2> {
161    Or {
162        left,
163        right
164    }
165}
166
167#[inline]
168///Creates new `OR` filter out of the `collection`
169pub const fn collection_or<F: Filter, I: AsRef<[F]>>(collection: I) -> CollectionOr<I, F> {
170    CollectionOr::new(collection)
171}