Skip to main content

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}