1use crate::condition::{IpAddrMask, Protocol};
7use crate::layer::FilterWeight;
8use std::fmt;
9use std::path::PathBuf;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum Direction {
14 Inbound,
16 Outbound,
18}
19
20impl fmt::Display for Direction {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Direction::Inbound => write!(f, "Inbound"),
24 Direction::Outbound => write!(f, "Outbound"),
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum Action {
32 Permit,
34 Block,
36}
37
38impl fmt::Display for Action {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Action::Permit => write!(f, "Permit"),
42 Action::Block => write!(f, "Block"),
43 }
44 }
45}
46
47#[derive(Debug, Clone)]
68pub struct FilterRule {
69 pub name: String,
71 pub direction: Direction,
73 pub action: Action,
75 pub weight: u64,
77 pub app_path: Option<PathBuf>,
79 pub service_name: Option<String>,
81 pub app_container_sid: Option<String>,
83 pub local_ip: Option<IpAddrMask>,
85 pub remote_ip: Option<IpAddrMask>,
87 pub local_port: Option<u16>,
89 pub remote_port: Option<u16>,
91 pub protocol: Option<Protocol>,
93}
94
95impl FilterRule {
96 pub fn new(name: impl Into<String>, direction: Direction, action: Action) -> Self {
98 Self {
99 name: name.into(),
100 direction,
101 action,
102 weight: FilterWeight::UserPermit.value(),
103 app_path: None,
104 service_name: None,
105 app_container_sid: None,
106 local_ip: None,
107 remote_ip: None,
108 local_port: None,
109 remote_port: None,
110 protocol: None,
111 }
112 }
113
114 pub fn with_weight(mut self, weight: FilterWeight) -> Self {
116 self.weight = weight.value();
117 self
118 }
119
120 pub fn with_raw_weight(mut self, weight: u64) -> Self {
122 self.weight = weight;
123 self
124 }
125
126 pub fn with_app_path(mut self, path: impl Into<PathBuf>) -> Self {
128 self.app_path = Some(path.into());
129 self
130 }
131
132 pub fn with_protocol(mut self, protocol: Protocol) -> Self {
134 self.protocol = Some(protocol);
135 self
136 }
137
138 pub fn with_remote_port(mut self, port: u16) -> Self {
140 self.remote_port = Some(port);
141 self
142 }
143
144 pub fn with_local_port(mut self, port: u16) -> Self {
146 self.local_port = Some(port);
147 self
148 }
149
150 pub fn with_remote_ip(mut self, ip: IpAddrMask) -> Self {
152 self.remote_ip = Some(ip);
153 self
154 }
155
156 pub fn with_local_ip(mut self, ip: IpAddrMask) -> Self {
158 self.local_ip = Some(ip);
159 self
160 }
161
162 pub fn with_service_name(mut self, name: impl Into<String>) -> Self {
164 self.service_name = Some(name.into());
165 self
166 }
167
168 pub fn with_app_container_sid(mut self, sid: impl Into<String>) -> Self {
170 self.app_container_sid = Some(sid.into());
171 self
172 }
173
174 pub fn block_all_outbound() -> Self {
176 Self::new("Block All Outbound", Direction::Outbound, Action::Block)
177 .with_weight(FilterWeight::Blocklist)
178 }
179
180 pub fn allow_all_outbound() -> Self {
182 Self::new("Allow All Outbound", Direction::Outbound, Action::Permit)
183 .with_weight(FilterWeight::DefaultPermit)
184 }
185
186 pub fn block_all_inbound() -> Self {
188 Self::new("Block All Inbound", Direction::Inbound, Action::Block)
189 .with_weight(FilterWeight::DefaultBlock)
190 }
191
192 pub fn default_block() -> Self {
194 Self::new("Default Block", Direction::Outbound, Action::Block)
195 .with_weight(FilterWeight::DefaultBlock)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_new_filter_rule_defaults() {
205 let rule = FilterRule::new("Test", Direction::Outbound, Action::Block);
206 assert_eq!(rule.name, "Test");
207 assert_eq!(rule.direction, Direction::Outbound);
208 assert_eq!(rule.action, Action::Block);
209 assert_eq!(rule.weight, FilterWeight::UserPermit.value());
210 assert!(rule.app_path.is_none());
211 assert!(rule.protocol.is_none());
212 }
213
214 #[test]
215 fn test_builder_pattern() {
216 let rule = FilterRule::new("Block curl", Direction::Outbound, Action::Block)
217 .with_weight(FilterWeight::UserBlock)
218 .with_app_path(r"C:\Windows\System32\curl.exe")
219 .with_protocol(Protocol::Tcp)
220 .with_remote_port(443);
221
222 assert_eq!(rule.weight, FilterWeight::UserBlock.value());
223 assert_eq!(rule.protocol, Some(Protocol::Tcp));
224 assert_eq!(rule.remote_port, Some(443));
225 }
226
227 #[test]
228 fn test_convenience_constructors() {
229 let block = FilterRule::block_all_outbound();
230 assert_eq!(block.action, Action::Block);
231 assert_eq!(block.weight, FilterWeight::Blocklist.value());
232
233 let allow = FilterRule::allow_all_outbound();
234 assert_eq!(allow.action, Action::Permit);
235
236 let default = FilterRule::default_block();
237 assert_eq!(default.weight, FilterWeight::DefaultBlock.value());
238 }
239
240 #[test]
241 fn test_with_raw_weight() {
242 let rule = FilterRule::new("Custom", Direction::Outbound, Action::Permit)
243 .with_raw_weight(42_000_000);
244 assert_eq!(rule.weight, 42_000_000);
245 }
246
247 #[test]
248 fn test_with_ip_conditions() {
249 use std::net::IpAddr;
250 let rule = FilterRule::new("IP filter", Direction::Outbound, Action::Block)
251 .with_remote_ip(IpAddrMask::new(
252 "192.168.1.0".parse::<IpAddr>().unwrap(),
253 24,
254 ))
255 .with_local_ip(IpAddrMask::new("10.0.0.1".parse::<IpAddr>().unwrap(), 32));
256
257 assert!(rule.remote_ip.is_some());
258 assert_eq!(rule.remote_ip.as_ref().unwrap().prefix_len, 24);
259 }
260
261 #[test]
262 fn test_with_service_name() {
263 let rule = FilterRule::new("Svc filter", Direction::Outbound, Action::Permit)
264 .with_service_name("dnscache");
265 assert_eq!(rule.service_name.as_deref(), Some("dnscache"));
266 }
267
268 #[test]
269 fn test_with_app_container_sid() {
270 let rule = FilterRule::new("UWP filter", Direction::Outbound, Action::Permit)
271 .with_app_container_sid("S-1-15-2-1234");
272 assert_eq!(rule.app_container_sid.as_deref(), Some("S-1-15-2-1234"));
273 }
274
275 #[test]
276 fn test_with_local_port() {
277 let rule = FilterRule::new("Port filter", Direction::Inbound, Action::Permit)
278 .with_local_port(8080);
279 assert_eq!(rule.local_port, Some(8080));
280 }
281
282 #[test]
283 fn test_block_all_inbound() {
284 let rule = FilterRule::block_all_inbound();
285 assert_eq!(rule.direction, Direction::Inbound);
286 assert_eq!(rule.action, Action::Block);
287 assert_eq!(rule.weight, FilterWeight::DefaultBlock.value());
288 }
289
290 #[test]
291 fn test_all_defaults_none() {
292 let rule = FilterRule::new("Empty", Direction::Outbound, Action::Permit);
293 assert!(rule.app_path.is_none());
294 assert!(rule.service_name.is_none());
295 assert!(rule.app_container_sid.is_none());
296 assert!(rule.local_ip.is_none());
297 assert!(rule.remote_ip.is_none());
298 assert!(rule.local_port.is_none());
299 assert!(rule.remote_port.is_none());
300 assert!(rule.protocol.is_none());
301 }
302
303 #[test]
304 fn test_full_builder_chain() {
305 use std::net::IpAddr;
306 let rule = FilterRule::new("Full", Direction::Outbound, Action::Block)
307 .with_weight(FilterWeight::UserBlock)
308 .with_app_path(r"C:\test.exe")
309 .with_protocol(Protocol::Tcp)
310 .with_remote_port(443)
311 .with_local_port(0)
312 .with_remote_ip(IpAddrMask::new("1.1.1.1".parse::<IpAddr>().unwrap(), 32))
313 .with_local_ip(IpAddrMask::new("10.0.0.1".parse::<IpAddr>().unwrap(), 32))
314 .with_service_name("svc")
315 .with_app_container_sid("sid");
316
317 assert_eq!(rule.name, "Full");
318 assert!(rule.app_path.is_some());
319 assert_eq!(rule.protocol, Some(Protocol::Tcp));
320 assert_eq!(rule.remote_port, Some(443));
321 assert_eq!(rule.local_port, Some(0));
322 assert!(rule.remote_ip.is_some());
323 assert!(rule.local_ip.is_some());
324 assert_eq!(rule.service_name.as_deref(), Some("svc"));
325 assert_eq!(rule.app_container_sid.as_deref(), Some("sid"));
326 }
327
328 #[test]
329 fn test_direction_copy_eq() {
330 let d1 = Direction::Outbound;
331 let d2 = d1; assert_eq!(d1, d2);
333 assert_ne!(Direction::Inbound, Direction::Outbound);
334 }
335
336 #[test]
337 fn test_action_copy_eq() {
338 let a1 = Action::Permit;
339 let a2 = a1; assert_eq!(a1, a2);
341 assert_ne!(Action::Permit, Action::Block);
342 }
343
344 #[test]
345 fn test_direction_display() {
346 assert_eq!(Direction::Inbound.to_string(), "Inbound");
347 assert_eq!(Direction::Outbound.to_string(), "Outbound");
348 }
349
350 #[test]
351 fn test_action_display() {
352 assert_eq!(Action::Permit.to_string(), "Permit");
353 assert_eq!(Action::Block.to_string(), "Block");
354 }
355}