libddwaf/
context.rs

1use std::error;
2use std::fmt;
3use std::ptr::null_mut;
4use std::time::Duration;
5
6use crate::object::{AsRawMutObject, Keyed, WafArray, WafMap, WafObject, WafOwned};
7
8/// A WAF Context that can be used to evaluate the configured ruleset against address data.
9///
10/// This is obtained by calling [`Handle::new_context`][crate::Handle::new_context], and a given [`Context`] should only
11/// be used to handle data for a single request.
12pub struct Context {
13    pub(crate) raw: libddwaf_sys::ddwaf_context,
14    pub(crate) keepalive: Vec<WafMap>,
15}
16impl Context {
17    /// Evaluates the configured ruleset against the provided address data, and returns the result
18    /// of this evaluation.
19    ///
20    /// # Errors
21    /// Returns an error if the WAF encountered an internal error, invalid object, or invalid argument while processing
22    /// the request.
23    pub fn run(
24        &mut self,
25        mut persistent_data: Option<WafMap>,
26        ephemeral_data: Option<&WafMap>,
27        timeout: Duration,
28    ) -> Result<RunResult, RunError> {
29        let mut res = std::mem::MaybeUninit::<RunOutput>::uninit();
30        let persistent_ref = persistent_data
31            .as_mut()
32            // The bindings take non-const pointers to the data, but actually does not change it.
33            .map_or(null_mut(), |f| unsafe {
34                std::ptr::from_mut(f.as_raw_mut()).cast()
35            });
36        let ephemeral_ref = ephemeral_data
37            .map(AsRef::<libddwaf_sys::ddwaf_object>::as_ref)
38            // The bindings take non-const pointers to the data, but actually does not change it.
39            .map_or(null_mut(), |r| std::ptr::from_ref(r).cast_mut());
40
41        let status = unsafe {
42            libddwaf_sys::ddwaf_run(
43                self.raw,
44                persistent_ref,
45                ephemeral_ref,
46                res.as_mut_ptr().cast(),
47                timeout.as_micros().try_into().unwrap_or(u64::MAX),
48            )
49        };
50        match status {
51            libddwaf_sys::DDWAF_ERR_INTERNAL => {
52                // It's unclear whether the persistent data needs to be kept alive or not, so we
53                // keep it alive to be on the safe side.
54                if let Some(obj) = persistent_data {
55                    self.keepalive.push(obj);
56                }
57                Err(RunError::InternalError)
58            }
59            libddwaf_sys::DDWAF_ERR_INVALID_OBJECT => Err(RunError::InvalidObject),
60            libddwaf_sys::DDWAF_ERR_INVALID_ARGUMENT => Err(RunError::InvalidArgument),
61            libddwaf_sys::DDWAF_OK => {
62                // We need to keep the persistent data alive as the WAF may hold references to it.
63                if let Some(obj) = persistent_data {
64                    self.keepalive.push(obj);
65                }
66                Ok(RunResult::NoMatch(unsafe { res.assume_init() }))
67            }
68            libddwaf_sys::DDWAF_MATCH => {
69                // We need to keep the persistent data alive as the WAF may hold references to it.
70                if let Some(obj) = persistent_data {
71                    self.keepalive.push(obj);
72                }
73                Ok(RunResult::Match(unsafe { res.assume_init() }))
74            }
75            unknown => unreachable!(
76                "Unexpected value returned by {}: 0x{:02X}",
77                stringify!(libddwaf_sys::ddwaf_run),
78                unknown
79            ),
80        }
81    }
82}
83impl Drop for Context {
84    fn drop(&mut self) {
85        unsafe { libddwaf_sys::ddwaf_context_destroy(self.raw) }
86    }
87}
88// Safety: Operations that mutate the internal state are made safe by requiring a mutable borrow on
89// the [Context] instance; and none of the internal state is exposed in any way.
90unsafe impl Send for Context {}
91// Safety: [Context] is trivially [Sync] because it contains no methods allowing shared references.
92unsafe impl Sync for Context {}
93
94/// The result of the [`Context::run`] operation.
95#[derive(Debug)]
96pub enum RunResult {
97    /// The WAF successfully processed the request, and produced no match.
98    NoMatch(RunOutput),
99    /// The WAF successfully processed the request and some event rules matched
100    /// some of the supplied address data.
101    Match(RunOutput),
102}
103
104/// The error that can occur during a [`Context::run`] operation.
105#[non_exhaustive]
106#[derive(Debug)]
107pub enum RunError {
108    /// The WAF encountered an internal error while processing the request.
109    InternalError,
110    /// The WAF encountered an invalid object while processing the request.
111    InvalidObject,
112    /// The WAF encountered an invalid argument while processing the request.
113    InvalidArgument,
114}
115impl fmt::Display for RunError {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            RunError::InternalError => write!(f, "The WAF encountered an internal error"),
119            RunError::InvalidObject => write!(f, "The WAF encountered an invalid object"),
120            RunError::InvalidArgument => write!(f, "The WAF encountered an invalid argument"),
121        }
122    }
123}
124impl error::Error for RunError {}
125
126/// The data produced by a [`Context::run`] operation.
127#[repr(transparent)]
128pub struct RunOutput {
129    data: WafOwned<WafMap>,
130}
131impl RunOutput {
132    /// Returns true if the WAF did not have enough time to process all the address data that was
133    /// being evaluated.
134    #[must_use]
135    pub fn timeout(&self) -> bool {
136        debug_assert!(self.data.is_valid());
137        self.data
138            .get(b"timeout")
139            .and_then(|o| o.to_bool())
140            .unwrap_or_default()
141    }
142
143    /// Returns true if the WAF determined the trace for this request should have its priority
144    /// overridden to ensure it is not dropped by the sampler.
145    #[must_use]
146    pub fn keep(&self) -> bool {
147        debug_assert!(self.data.is_valid());
148        self.data
149            .get(b"keep")
150            .and_then(|o| o.to_bool())
151            .unwrap_or_default()
152    }
153
154    /// Returns the total time spent processing the request; excluding bindings overhead (which
155    /// ought to be trivial).
156    pub fn duration(&self) -> Duration {
157        debug_assert!(self.data.is_valid());
158        self.data
159            .get(b"duration")
160            .and_then(|o| o.to_u64())
161            .map(Duration::from_nanos)
162            .unwrap_or_default()
163    }
164
165    /// Returns the list of events that were produced by this WAF run.
166    ///
167    /// This is only expected to be populated when [`Context::run`] returns [`RunResult::Match`].
168    pub fn events(&self) -> Option<&Keyed<WafArray>> {
169        debug_assert!(self.data.is_valid());
170        self.data
171            .get(b"events")
172            .and_then(Keyed::<WafObject>::as_type)
173    }
174
175    /// Returns the list of actions that were produced by this WAF run.
176    ///
177    /// This is only expected to be populated when [`Context::run`] returns [`RunResult::Match`].
178    pub fn actions(&self) -> Option<&Keyed<WafMap>> {
179        debug_assert!(self.data.is_valid());
180        self.data
181            .get(b"actions")
182            .and_then(Keyed::<WafObject>::as_type)
183    }
184
185    /// Returns the list of attributes that were produced by this WAF run, and which should be
186    /// attached to the surrounding trace.
187    pub fn attributes(&self) -> Option<&Keyed<WafMap>> {
188        debug_assert!(self.data.is_valid());
189        self.data
190            .get(b"attributes")
191            .and_then(Keyed::<WafObject>::as_type)
192    }
193}
194impl fmt::Debug for RunOutput {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        f.debug_struct("RunOutput")
197            .field("timeout", &self.timeout())
198            .field("keep", &self.keep())
199            .field("duration", &self.duration())
200            .field("events", &self.events())
201            .field("actions", &self.actions())
202            .field("attributes", &self.attributes())
203            .finish()
204    }
205}