1use crate::condition::{IpAddrMask, Protocol};
7use crate::layer::FilterWeight;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Direction {
13 Inbound,
15 Outbound,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum Action {
22 Permit,
24 Block,
26}
27
28#[derive(Debug, Clone)]
49pub struct FilterRule {
50 pub name: String,
52 pub direction: Direction,
54 pub action: Action,
56 pub weight: u64,
58 pub app_path: Option<PathBuf>,
60 pub service_name: Option<String>,
62 pub app_container_sid: Option<String>,
64 pub local_ip: Option<IpAddrMask>,
66 pub remote_ip: Option<IpAddrMask>,
68 pub local_port: Option<u16>,
70 pub remote_port: Option<u16>,
72 pub protocol: Option<Protocol>,
74}
75
76impl FilterRule {
77 pub fn new(name: impl Into<String>, direction: Direction, action: Action) -> Self {
79 Self {
80 name: name.into(),
81 direction,
82 action,
83 weight: FilterWeight::UserPermit.value(),
84 app_path: None,
85 service_name: None,
86 app_container_sid: None,
87 local_ip: None,
88 remote_ip: None,
89 local_port: None,
90 remote_port: None,
91 protocol: None,
92 }
93 }
94
95 pub fn with_weight(mut self, weight: FilterWeight) -> Self {
97 self.weight = weight.value();
98 self
99 }
100
101 pub fn with_raw_weight(mut self, weight: u64) -> Self {
103 self.weight = weight;
104 self
105 }
106
107 pub fn with_app_path(mut self, path: impl Into<PathBuf>) -> Self {
109 self.app_path = Some(path.into());
110 self
111 }
112
113 pub fn with_protocol(mut self, protocol: Protocol) -> Self {
115 self.protocol = Some(protocol);
116 self
117 }
118
119 pub fn with_remote_port(mut self, port: u16) -> Self {
121 self.remote_port = Some(port);
122 self
123 }
124
125 pub fn with_local_port(mut self, port: u16) -> Self {
127 self.local_port = Some(port);
128 self
129 }
130
131 pub fn with_remote_ip(mut self, ip: IpAddrMask) -> Self {
133 self.remote_ip = Some(ip);
134 self
135 }
136
137 pub fn with_local_ip(mut self, ip: IpAddrMask) -> Self {
139 self.local_ip = Some(ip);
140 self
141 }
142
143 pub fn with_service_name(mut self, name: impl Into<String>) -> Self {
145 self.service_name = Some(name.into());
146 self
147 }
148
149 pub fn with_app_container_sid(mut self, sid: impl Into<String>) -> Self {
151 self.app_container_sid = Some(sid.into());
152 self
153 }
154
155 pub fn block_all_outbound() -> Self {
157 Self::new("Block All Outbound", Direction::Outbound, Action::Block)
158 .with_weight(FilterWeight::Blocklist)
159 }
160
161 pub fn allow_all_outbound() -> Self {
163 Self::new("Allow All Outbound", Direction::Outbound, Action::Permit)
164 .with_weight(FilterWeight::DefaultPermit)
165 }
166
167 pub fn block_all_inbound() -> Self {
169 Self::new("Block All Inbound", Direction::Inbound, Action::Block)
170 .with_weight(FilterWeight::DefaultBlock)
171 }
172
173 pub fn default_block() -> Self {
175 Self::new("Default Block", Direction::Outbound, Action::Block)
176 .with_weight(FilterWeight::DefaultBlock)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_new_filter_rule_defaults() {
186 let rule = FilterRule::new("Test", Direction::Outbound, Action::Block);
187 assert_eq!(rule.name, "Test");
188 assert_eq!(rule.direction, Direction::Outbound);
189 assert_eq!(rule.action, Action::Block);
190 assert_eq!(rule.weight, FilterWeight::UserPermit.value());
191 assert!(rule.app_path.is_none());
192 assert!(rule.protocol.is_none());
193 }
194
195 #[test]
196 fn test_builder_pattern() {
197 let rule = FilterRule::new("Block curl", Direction::Outbound, Action::Block)
198 .with_weight(FilterWeight::UserBlock)
199 .with_app_path(r"C:\Windows\System32\curl.exe")
200 .with_protocol(Protocol::Tcp)
201 .with_remote_port(443);
202
203 assert_eq!(rule.weight, FilterWeight::UserBlock.value());
204 assert_eq!(rule.protocol, Some(Protocol::Tcp));
205 assert_eq!(rule.remote_port, Some(443));
206 }
207
208 #[test]
209 fn test_convenience_constructors() {
210 let block = FilterRule::block_all_outbound();
211 assert_eq!(block.action, Action::Block);
212 assert_eq!(block.weight, FilterWeight::Blocklist.value());
213
214 let allow = FilterRule::allow_all_outbound();
215 assert_eq!(allow.action, Action::Permit);
216
217 let default = FilterRule::default_block();
218 assert_eq!(default.weight, FilterWeight::DefaultBlock.value());
219 }
220
221 #[test]
222 fn test_with_raw_weight() {
223 let rule = FilterRule::new("Custom", Direction::Outbound, Action::Permit)
224 .with_raw_weight(42_000_000);
225 assert_eq!(rule.weight, 42_000_000);
226 }
227
228 #[test]
229 fn test_with_ip_conditions() {
230 use std::net::IpAddr;
231 let rule = FilterRule::new("IP filter", Direction::Outbound, Action::Block)
232 .with_remote_ip(IpAddrMask::new(
233 "192.168.1.0".parse::<IpAddr>().unwrap(),
234 24,
235 ))
236 .with_local_ip(IpAddrMask::new("10.0.0.1".parse::<IpAddr>().unwrap(), 32));
237
238 assert!(rule.remote_ip.is_some());
239 assert_eq!(rule.remote_ip.as_ref().unwrap().prefix_len, 24);
240 }
241
242 #[test]
243 fn test_with_service_name() {
244 let rule = FilterRule::new("Svc filter", Direction::Outbound, Action::Permit)
245 .with_service_name("dnscache");
246 assert_eq!(rule.service_name.as_deref(), Some("dnscache"));
247 }
248
249 #[test]
250 fn test_with_app_container_sid() {
251 let rule = FilterRule::new("UWP filter", Direction::Outbound, Action::Permit)
252 .with_app_container_sid("S-1-15-2-1234");
253 assert_eq!(rule.app_container_sid.as_deref(), Some("S-1-15-2-1234"));
254 }
255
256 #[test]
257 fn test_with_local_port() {
258 let rule = FilterRule::new("Port filter", Direction::Inbound, Action::Permit)
259 .with_local_port(8080);
260 assert_eq!(rule.local_port, Some(8080));
261 }
262
263 #[test]
264 fn test_block_all_inbound() {
265 let rule = FilterRule::block_all_inbound();
266 assert_eq!(rule.direction, Direction::Inbound);
267 assert_eq!(rule.action, Action::Block);
268 assert_eq!(rule.weight, FilterWeight::DefaultBlock.value());
269 }
270
271 #[test]
272 fn test_all_defaults_none() {
273 let rule = FilterRule::new("Empty", Direction::Outbound, Action::Permit);
274 assert!(rule.app_path.is_none());
275 assert!(rule.service_name.is_none());
276 assert!(rule.app_container_sid.is_none());
277 assert!(rule.local_ip.is_none());
278 assert!(rule.remote_ip.is_none());
279 assert!(rule.local_port.is_none());
280 assert!(rule.remote_port.is_none());
281 assert!(rule.protocol.is_none());
282 }
283
284 #[test]
285 fn test_full_builder_chain() {
286 use std::net::IpAddr;
287 let rule = FilterRule::new("Full", Direction::Outbound, Action::Block)
288 .with_weight(FilterWeight::UserBlock)
289 .with_app_path(r"C:\test.exe")
290 .with_protocol(Protocol::Tcp)
291 .with_remote_port(443)
292 .with_local_port(0)
293 .with_remote_ip(IpAddrMask::new("1.1.1.1".parse::<IpAddr>().unwrap(), 32))
294 .with_local_ip(IpAddrMask::new("10.0.0.1".parse::<IpAddr>().unwrap(), 32))
295 .with_service_name("svc")
296 .with_app_container_sid("sid");
297
298 assert_eq!(rule.name, "Full");
299 assert!(rule.app_path.is_some());
300 assert_eq!(rule.protocol, Some(Protocol::Tcp));
301 assert_eq!(rule.remote_port, Some(443));
302 assert_eq!(rule.local_port, Some(0));
303 assert!(rule.remote_ip.is_some());
304 assert!(rule.local_ip.is_some());
305 assert_eq!(rule.service_name.as_deref(), Some("svc"));
306 assert_eq!(rule.app_container_sid.as_deref(), Some("sid"));
307 }
308
309 #[test]
310 fn test_direction_copy_eq() {
311 let d1 = Direction::Outbound;
312 let d2 = d1; assert_eq!(d1, d2);
314 assert_ne!(Direction::Inbound, Direction::Outbound);
315 }
316
317 #[test]
318 fn test_action_copy_eq() {
319 let a1 = Action::Permit;
320 let a2 = a1; assert_eq!(a1, a2);
322 assert_ne!(Action::Permit, Action::Block);
323 }
324}