windows_wfp/engine.rs
1//! WFP Engine wrapper with RAII handle management
2//!
3//! Provides safe Rust wrapper around WFP engine session.
4
5use crate::errors::{WfpError, WfpResult};
6use std::ptr;
7use windows::core::{PCWSTR, PWSTR};
8use windows::Win32::Foundation::{ERROR_SUCCESS, HANDLE};
9use windows::Win32::NetworkManagement::WindowsFilteringPlatform::{
10 FwpmEngineClose0, FwpmEngineOpen0, FWPM_SESSION0, FWPM_SESSION_FLAG_DYNAMIC,
11};
12
13/// WFP Engine session with RAII handle management
14///
15/// Opens a session to the Windows Filtering Platform on creation
16/// and automatically closes it on drop.
17///
18/// # Examples
19///
20/// ```no_run
21/// use windows_wfp::WfpEngine;
22///
23/// let engine = WfpEngine::new()?;
24/// // Use engine for filter operations
25/// // Session automatically closed when engine goes out of scope
26/// # Ok::<(), windows_wfp::WfpError>(())
27/// ```
28#[derive(Debug)]
29pub struct WfpEngine {
30 /// Handle to WFP engine session
31 handle: HANDLE,
32}
33
34impl WfpEngine {
35 /// Open a new WFP engine session
36 ///
37 /// Requires administrator privileges.
38 ///
39 /// # Errors
40 ///
41 /// Returns `WfpError::EngineOpenFailed` if:
42 /// - Insufficient permissions (not running as admin)
43 /// - WFP service not available
44 /// - Session creation failed
45 ///
46 /// # Examples
47 ///
48 /// ```no_run
49 /// use windows_wfp::WfpEngine;
50 ///
51 /// let engine = WfpEngine::new()?;
52 /// # Ok::<(), windows_wfp::WfpError>(())
53 /// ```
54 pub fn new() -> WfpResult<Self> {
55 Self::new_with_flags(FWPM_SESSION_FLAG_DYNAMIC)
56 }
57
58 /// Open a new WFP engine session with custom flags
59 ///
60 /// # Arguments
61 ///
62 /// * `flags` - Session flags (e.g., `FWPM_SESSION_FLAG_DYNAMIC` for automatic cleanup)
63 ///
64 /// # Errors
65 ///
66 /// Returns `WfpError::EngineOpenFailed` if session creation fails.
67 pub fn new_with_flags(flags: u32) -> WfpResult<Self> {
68 let session = FWPM_SESSION0 {
69 sessionKey: windows::core::GUID::zeroed(),
70 displayData: Default::default(),
71 flags,
72 txnWaitTimeoutInMSec: 0,
73 processId: 0,
74 sid: ptr::null_mut(),
75 username: PWSTR::null(),
76 kernelMode: false.into(),
77 };
78
79 let mut handle = HANDLE::default();
80
81 unsafe {
82 let result = FwpmEngineOpen0(
83 PCWSTR::null(),
84 10, // RPC_C_AUTHN_WINNT (Windows NT authentication)
85 None,
86 Some(&session),
87 &mut handle,
88 );
89
90 if result != ERROR_SUCCESS.0 {
91 return match result {
92 5 => Err(WfpError::InsufficientPermissions),
93 1062 | 1075 => Err(WfpError::ServiceNotAvailable),
94 _ => Err(WfpError::Other(format!(
95 "FwpmEngineOpen0 failed with Windows error code: {} (0x{:X}). \
96 Common causes: BFE service not running, insufficient privileges, or WFP driver not loaded.",
97 result, result
98 ))),
99 };
100 }
101 }
102
103 Ok(Self { handle })
104 }
105
106 /// Get raw handle to WFP engine session
107 ///
108 /// # Safety
109 ///
110 /// The handle is only valid while this `WfpEngine` instance is alive.
111 /// Do not close the handle manually - it will be closed automatically on drop.
112 pub fn handle(&self) -> HANDLE {
113 self.handle
114 }
115
116 /// Check if session is valid
117 pub fn is_valid(&self) -> bool {
118 !self.handle.is_invalid()
119 }
120}
121
122impl Drop for WfpEngine {
123 /// Automatically close WFP engine session when dropped
124 fn drop(&mut self) {
125 if !self.handle.is_invalid() {
126 unsafe {
127 let _ = FwpmEngineClose0(self.handle);
128 }
129 }
130 }
131}
132
133// WfpEngine is Send because the handle can be safely transferred between threads
134unsafe impl Send for WfpEngine {}
135
136// WfpEngine is NOT Sync because concurrent access requires synchronization
137// Multiple threads should not access the same session simultaneously
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 #[ignore] // Requires admin privileges
145 fn test_engine_creation() {
146 let engine = WfpEngine::new();
147 assert!(engine.is_ok(), "Failed to create WFP engine (run as admin)");
148
149 if let Ok(engine) = engine {
150 assert!(engine.is_valid());
151 assert!(!engine.handle().is_invalid());
152 }
153 }
154
155 #[test]
156 #[ignore] // Requires admin privileges
157 fn test_engine_drop_closes_session() {
158 {
159 let _engine = WfpEngine::new().expect("Failed to create engine");
160 // Engine should be valid here
161 }
162 // Engine dropped - session should be closed automatically
163 // No way to verify programmatically without leaking handles
164 }
165
166 #[test]
167 fn test_engine_without_permissions() {
168 // This test will fail if run as admin
169 // Useful for CI/CD without admin rights
170 let result = WfpEngine::new();
171
172 if let Err(err) = result {
173 // Expected when not running as admin
174 match err {
175 WfpError::InsufficientPermissions => (),
176 WfpError::ServiceNotAvailable => (),
177 WfpError::EngineOpenFailed => (),
178 other => panic!("Unexpected error: {:?}", other),
179 }
180 }
181 }
182}