Skip to main content

windows_wfp/
transaction.rs

1//! WFP Transaction wrapper with RAII rollback
2//!
3//! Provides safe transaction management for WFP operations with automatic rollback on error.
4
5use crate::engine::WfpEngine;
6use crate::errors::{WfpError, WfpResult};
7use windows::Win32::Foundation::ERROR_SUCCESS;
8use windows::Win32::NetworkManagement::WindowsFilteringPlatform::{
9    FwpmTransactionAbort0, FwpmTransactionBegin0, FwpmTransactionCommit0,
10};
11
12/// WFP Transaction with RAII rollback support
13///
14/// Automatically begins a transaction on creation and rolls back on drop
15/// unless explicitly committed.
16///
17/// # Examples
18///
19/// ```no_run
20/// use windows_wfp::{WfpEngine, WfpTransaction};
21///
22/// let engine = WfpEngine::new()?;
23/// let mut txn = WfpTransaction::begin(&engine)?;
24///
25/// // Perform filter operations...
26/// // If any operation fails, transaction will be rolled back automatically
27///
28/// txn.commit()?; // Explicitly commit if all succeeded
29/// # Ok::<(), windows_wfp::WfpError>(())
30/// ```
31pub struct WfpTransaction<'a> {
32    /// Reference to the WFP engine
33    engine: &'a WfpEngine,
34    /// Whether the transaction has been committed
35    committed: bool,
36}
37
38impl<'a> WfpTransaction<'a> {
39    /// Begin a new WFP transaction
40    ///
41    /// The transaction will automatically rollback if dropped without calling `commit()`.
42    ///
43    /// # Errors
44    ///
45    /// Returns `WfpError::TransactionBeginFailed` if:
46    /// - Another transaction is already active on this session
47    /// - WFP engine session is invalid
48    ///
49    /// # Examples
50    ///
51    /// ```no_run
52    /// use windows_wfp::{WfpEngine, WfpTransaction};
53    ///
54    /// let engine = WfpEngine::new()?;
55    /// let mut txn = WfpTransaction::begin(&engine)?;
56    /// // Transaction active...
57    /// txn.commit()?;
58    /// # Ok::<(), windows_wfp::WfpError>(())
59    /// ```
60    pub fn begin(engine: &'a WfpEngine) -> WfpResult<Self> {
61        unsafe {
62            let result = FwpmTransactionBegin0(engine.handle(), 0);
63
64            if result != ERROR_SUCCESS.0 {
65                return Err(WfpError::TransactionBeginFailed);
66            }
67        }
68
69        Ok(Self {
70            engine,
71            committed: false,
72        })
73    }
74
75    /// Commit the transaction
76    ///
77    /// Makes all changes permanent. If not called, the transaction will
78    /// automatically rollback when dropped.
79    ///
80    /// # Errors
81    ///
82    /// Returns `WfpError::TransactionCommitFailed` if the commit operation fails.
83    ///
84    /// # Examples
85    ///
86    /// ```no_run
87    /// use windows_wfp::{WfpEngine, WfpTransaction};
88    ///
89    /// let engine = WfpEngine::new()?;
90    /// let mut txn = WfpTransaction::begin(&engine)?;
91    /// // Perform operations...
92    /// txn.commit()?; // Make changes permanent
93    /// # Ok::<(), windows_wfp::WfpError>(())
94    /// ```
95    pub fn commit(mut self) -> WfpResult<()> {
96        unsafe {
97            let result = FwpmTransactionCommit0(self.engine.handle());
98
99            if result != ERROR_SUCCESS.0 {
100                return Err(WfpError::TransactionCommitFailed);
101            }
102        }
103
104        self.committed = true;
105        Ok(())
106    }
107
108    /// Explicitly rollback the transaction
109    ///
110    /// This is optional - the transaction will rollback automatically on drop
111    /// if not committed. Use this for explicit error handling.
112    ///
113    /// # Errors
114    ///
115    /// Returns `WfpError::TransactionAbortFailed` if the abort operation fails.
116    pub fn rollback(mut self) -> WfpResult<()> {
117        unsafe {
118            let result = FwpmTransactionAbort0(self.engine.handle());
119
120            if result != ERROR_SUCCESS.0 {
121                return Err(WfpError::TransactionAbortFailed);
122            }
123        }
124
125        self.committed = true; // Prevent double-abort in drop
126        Ok(())
127    }
128
129    /// Check if transaction has been committed
130    pub fn is_committed(&self) -> bool {
131        self.committed
132    }
133}
134
135impl<'a> Drop for WfpTransaction<'a> {
136    /// Automatically rollback transaction if not committed
137    fn drop(&mut self) {
138        if !self.committed {
139            unsafe {
140                // Best effort rollback - ignore errors in drop
141                let _ = FwpmTransactionAbort0(self.engine.handle());
142            }
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    #[ignore] // Requires admin privileges
153    fn test_transaction_begin_commit() {
154        let engine = WfpEngine::new().expect("Failed to create engine");
155        let txn = WfpTransaction::begin(&engine).expect("Failed to begin transaction");
156
157        assert!(!txn.is_committed());
158
159        txn.commit().expect("Failed to commit transaction");
160    }
161
162    #[test]
163    #[ignore] // Requires admin privileges
164    fn test_transaction_auto_rollback() {
165        let engine = WfpEngine::new().expect("Failed to create engine");
166
167        {
168            let _txn = WfpTransaction::begin(&engine).expect("Failed to begin transaction");
169            // Transaction should rollback automatically when dropped
170        }
171
172        // Should be able to begin a new transaction after rollback
173        let _txn2 = WfpTransaction::begin(&engine).expect("Failed to begin second transaction");
174    }
175
176    #[test]
177    #[ignore] // Requires admin privileges
178    fn test_transaction_explicit_rollback() {
179        let engine = WfpEngine::new().expect("Failed to create engine");
180        let txn = WfpTransaction::begin(&engine).expect("Failed to begin transaction");
181
182        txn.rollback().expect("Failed to rollback transaction");
183
184        // Should be able to begin a new transaction after rollback
185        let _txn2 = WfpTransaction::begin(&engine).expect("Failed to begin second transaction");
186    }
187}