1use crate::engine::WfpEngine;
21use crate::errors::{WfpError, WfpResult};
22use std::path::PathBuf;
23use std::ptr;
24use windows::core::GUID;
25use windows::Win32::Foundation::{ERROR_SUCCESS, HANDLE};
26use windows::Win32::NetworkManagement::WindowsFilteringPlatform::{
27 FwpmFilterCreateEnumHandle0, FwpmFilterDestroyEnumHandle0, FwpmFilterEnum0, FwpmFreeMemory0,
28 FWPM_FILTER0, FWP_ACTION_BLOCK, FWP_ACTION_CALLOUT_INSPECTION, FWP_ACTION_CALLOUT_TERMINATING,
29 FWP_ACTION_CALLOUT_UNKNOWN, FWP_ACTION_PERMIT, FWP_BYTE_BLOB_TYPE, FWP_EMPTY, FWP_UINT16,
30 FWP_UINT32, FWP_UINT64, FWP_UINT8,
31};
32
33use crate::constants::CONDITION_ALE_APP_ID;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum FilterAction {
38 Block,
40 Permit,
42 CalloutTerminating,
44 CalloutInspection,
46 CalloutUnknown,
48 Other(u32),
50}
51
52#[derive(Debug, Clone)]
54pub struct FilterInfo {
55 pub id: u64,
57 pub name: String,
59 pub description: String,
61 pub action: FilterAction,
63 pub provider_key: Option<GUID>,
65 pub weight: u64,
67 pub layer_key: GUID,
69 pub sublayer_key: GUID,
71 pub app_path: Option<PathBuf>,
73 pub num_conditions: u32,
75}
76
77pub struct FilterEnumerator;
99
100impl FilterEnumerator {
101 pub fn all(engine: &WfpEngine) -> WfpResult<Vec<FilterInfo>> {
110 Self::enumerate_raw(engine, |filter_array, num_returned, acc: &mut Vec<FilterInfo>| {
111 for i in 0..num_returned {
112 unsafe {
113 let filter = &**filter_array.offset(i as isize);
114 acc.push(parse_filter(filter));
115 }
116 }
117 })
118 }
119
120 pub fn count(engine: &WfpEngine) -> WfpResult<usize> {
125 Self::enumerate_raw(engine, |_filter_array, num_returned, acc: &mut usize| {
126 *acc += num_returned as usize;
127 })
128 }
129
130 fn enumerate_raw<T, F>(engine: &WfpEngine, mut visitor: F) -> WfpResult<T>
135 where
136 T: Default,
137 F: FnMut(*mut *mut FWPM_FILTER0, u32, &mut T),
138 {
139 let mut enum_handle = HANDLE::default();
140
141 unsafe {
142 let result = FwpmFilterCreateEnumHandle0(engine.handle(), None, &mut enum_handle);
143 if result != ERROR_SUCCESS.0 {
144 return Err(WfpError::Other(format!(
145 "Failed to create filter enum handle: error code {}",
146 result
147 )));
148 }
149 }
150
151 let mut accumulator = T::default();
152 let batch_size = 100;
153
154 loop {
155 let mut filter_array: *mut *mut FWPM_FILTER0 = ptr::null_mut();
156 let mut num_returned: u32 = 0;
157
158 let result = unsafe {
159 FwpmFilterEnum0(
160 engine.handle(),
161 enum_handle,
162 batch_size,
163 &mut filter_array,
164 &mut num_returned,
165 )
166 };
167
168 if result != ERROR_SUCCESS.0 {
169 unsafe {
170 let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
171 }
172 return Err(WfpError::Other(format!(
173 "Failed to enumerate filters: error code {}",
174 result
175 )));
176 }
177
178 if num_returned == 0 {
179 unsafe {
180 if !filter_array.is_null() {
181 FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
182 }
183 }
184 break;
185 }
186
187 visitor(filter_array, num_returned, &mut accumulator);
188
189 unsafe {
190 if !filter_array.is_null() {
191 FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
192 }
193 }
194 }
195
196 unsafe {
197 let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
198 }
199
200 Ok(accumulator)
201 }
202}
203
204unsafe fn parse_filter(filter: &FWPM_FILTER0) -> FilterInfo {
210 let name = if !filter.displayData.name.is_null() {
211 filter
212 .displayData
213 .name
214 .to_string()
215 .unwrap_or_else(|_| String::new())
216 } else {
217 String::new()
218 };
219
220 let description = if !filter.displayData.description.is_null() {
221 filter
222 .displayData
223 .description
224 .to_string()
225 .unwrap_or_else(|_| String::new())
226 } else {
227 String::new()
228 };
229
230 let action = match filter.action.r#type {
231 FWP_ACTION_BLOCK => FilterAction::Block,
232 FWP_ACTION_PERMIT => FilterAction::Permit,
233 FWP_ACTION_CALLOUT_TERMINATING => FilterAction::CalloutTerminating,
234 FWP_ACTION_CALLOUT_INSPECTION => FilterAction::CalloutInspection,
235 FWP_ACTION_CALLOUT_UNKNOWN => FilterAction::CalloutUnknown,
236 other => FilterAction::Other(other.0),
237 };
238
239 let provider_key = if !filter.providerKey.is_null() {
240 Some(*filter.providerKey)
241 } else {
242 None
243 };
244
245 let weight = match filter.weight.r#type {
246 FWP_UINT8 => filter.weight.Anonymous.uint8 as u64,
247 FWP_UINT16 => filter.weight.Anonymous.uint16 as u64,
248 FWP_UINT32 => filter.weight.Anonymous.uint32 as u64,
249 FWP_UINT64 => {
250 let ptr = filter.weight.Anonymous.uint64;
251 if ptr.is_null() {
252 0
253 } else {
254 *ptr
255 }
256 }
257 FWP_EMPTY => 0,
258 _ => 0,
259 };
260
261 let app_path = extract_app_path(filter);
262
263 FilterInfo {
264 id: filter.filterId,
265 name,
266 description,
267 action,
268 provider_key,
269 weight,
270 layer_key: filter.layerKey,
271 sublayer_key: filter.subLayerKey,
272 app_path,
273 num_conditions: filter.numFilterConditions,
274 }
275}
276
277unsafe fn extract_app_path(filter: &FWPM_FILTER0) -> Option<PathBuf> {
282 if filter.numFilterConditions == 0 || filter.filterCondition.is_null() {
283 return None;
284 }
285
286 let conditions =
287 std::slice::from_raw_parts(filter.filterCondition, filter.numFilterConditions as usize);
288
289 let condition = conditions
290 .iter()
291 .find(|c| c.fieldKey == CONDITION_ALE_APP_ID)?;
292
293 if condition.conditionValue.r#type != FWP_BYTE_BLOB_TYPE {
294 return None;
295 }
296
297 let blob_ptr = condition.conditionValue.Anonymous.byteBlob;
298 if blob_ptr.is_null() {
299 return None;
300 }
301
302 let blob = &*blob_ptr;
303 if blob.data.is_null() || blob.size == 0 || (blob.size % 2) != 0 {
304 return None;
305 }
306
307 let wide_slice = std::slice::from_raw_parts(blob.data as *const u16, (blob.size / 2) as usize);
308 let null_pos = wide_slice
309 .iter()
310 .position(|&c| c == 0)
311 .unwrap_or(wide_slice.len());
312
313 String::from_utf16(&wide_slice[..null_pos])
314 .ok()
315 .map(PathBuf::from)
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_filter_action_eq() {
324 assert_eq!(FilterAction::Block, FilterAction::Block);
325 assert_eq!(FilterAction::Permit, FilterAction::Permit);
326 assert_ne!(FilterAction::Block, FilterAction::Permit);
327 assert_eq!(FilterAction::Other(42), FilterAction::Other(42));
328 assert_ne!(FilterAction::Other(1), FilterAction::Other(2));
329 }
330
331 #[test]
332 fn test_filter_action_copy() {
333 let action = FilterAction::Block;
334 let copy = action;
335 assert_eq!(action, copy);
336 }
337
338 #[test]
339 fn test_filter_info_clone() {
340 let info = FilterInfo {
341 id: 42,
342 name: "Test filter".to_string(),
343 description: "A test".to_string(),
344 action: FilterAction::Block,
345 provider_key: None,
346 weight: 1000,
347 layer_key: GUID::zeroed(),
348 sublayer_key: GUID::zeroed(),
349 app_path: Some(PathBuf::from(r"C:\test.exe")),
350 num_conditions: 1,
351 };
352
353 let cloned = info.clone();
354 assert_eq!(cloned.id, 42);
355 assert_eq!(cloned.name, "Test filter");
356 assert_eq!(cloned.action, FilterAction::Block);
357 assert!(cloned.app_path.is_some());
358 }
359
360 #[test]
361 fn test_filter_info_default_values() {
362 let info = FilterInfo {
363 id: 0,
364 name: String::new(),
365 description: String::new(),
366 action: FilterAction::Permit,
367 provider_key: None,
368 weight: 0,
369 layer_key: GUID::zeroed(),
370 sublayer_key: GUID::zeroed(),
371 app_path: None,
372 num_conditions: 0,
373 };
374
375 assert_eq!(info.id, 0);
376 assert!(info.name.is_empty());
377 assert!(info.provider_key.is_none());
378 assert!(info.app_path.is_none());
379 }
380
381 #[test]
382 fn test_filter_info_with_provider() {
383 let guid = GUID::from_u128(0x12345678_1234_5678_1234_567812345678);
384 let info = FilterInfo {
385 id: 100,
386 name: "Provider filter".to_string(),
387 description: String::new(),
388 action: FilterAction::Block,
389 provider_key: Some(guid),
390 weight: 5000,
391 layer_key: GUID::zeroed(),
392 sublayer_key: GUID::zeroed(),
393 app_path: None,
394 num_conditions: 3,
395 };
396
397 assert_eq!(info.provider_key, Some(guid));
398 assert_eq!(info.num_conditions, 3);
399 }
400
401 #[test]
402 #[ignore] fn test_enumerate_all_filters() {
404 let engine = WfpEngine::new().expect("Failed to open WFP engine");
405 let filters = FilterEnumerator::all(&engine).expect("Failed to enumerate");
406 assert!(!filters.is_empty(), "Expected at least one WFP filter");
408 }
409
410 #[test]
411 #[ignore] fn test_count_filters() {
413 let engine = WfpEngine::new().expect("Failed to open WFP engine");
414 let count = FilterEnumerator::count(&engine).expect("Failed to count");
415 let all = FilterEnumerator::all(&engine).expect("Failed to enumerate");
416 assert_eq!(count, all.len());
417 }
418}