Skip to main content

Engine

Struct Engine 

Source
pub struct Engine { /* private fields */ }
Expand description

The main rule evaluation engine.

Holds a set of compiled rules and provides methods to evaluate events against them. Supports optional logsource routing for performance.

§Example

use rsigma_parser::parse_sigma_yaml;
use rsigma_eval::{Engine, Event};
use rsigma_eval::event::JsonEvent;
use serde_json::json;

let yaml = r#"
title: Detect Whoami
logsource:
    product: windows
    category: process_creation
detection:
    selection:
        CommandLine|contains: 'whoami'
    condition: selection
level: medium
"#;

let collection = parse_sigma_yaml(yaml).unwrap();
let mut engine = Engine::new();
engine.add_collection(&collection).unwrap();

let event_val = json!({"CommandLine": "cmd /c whoami"});
let event = JsonEvent::borrow(&event_val);
let matches = engine.evaluate(&event);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].header.rule_title, "Detect Whoami");

Implementations§

Source§

impl Engine

Source

pub fn new() -> Self

Create a new empty engine.

Source

pub fn new_with_pipeline(pipeline: Pipeline) -> Self

Create a new engine with a pipeline.

Source

pub fn set_bloom_prefilter(&mut self, enabled: bool)

Enable or disable bloom-filter pre-filtering of positive substring detection items.

When enabled, evaluate* short-circuits any positive substring matcher (Contains / StartsWith / EndsWith / AhoCorasickSet, alone or wrapped in CaseInsensitiveGroup) whose field cannot possibly contain a needle trigram.

Disabled by default. The per-event probe (trigram extraction + double hashing) costs ~1 µs on a typical CommandLine field, which outweighs the savings on rule sets where most events overlap with at least one needle. Enable for workloads that pair many substring rules with mostly-non-matching events; benchmark with eval_bloom_rejection before flipping it on in production.

Source

pub fn bloom_prefilter_enabled(&self) -> bool

Returns whether bloom pre-filtering is currently enabled.

Source

pub fn set_bloom_max_bytes(&mut self, max_bytes: usize)

Set the memory budget for the per-field bloom index.

Must be called before add_collection / add_rule for the new budget to take effect on the existing rule set; otherwise it is applied at the next index rebuild. The default budget is 1 MB, shared across all per-field filters. Lower the cap on memory- constrained deployments; raise it for large rule sets where the default starts evicting useful filters.

Source

pub fn bloom_max_bytes(&self) -> Option<usize>

Returns the configured bloom memory budget, if one has been set explicitly. None means the crate default (1 MB) is in use.

Source

pub fn set_logsource_extractor(&mut self, extractor: Option<LogSourceExtractor>)

Enable or disable opt-in, conflict-based logsource pruning.

When set to Some, evaluate extracts each event’s logsource once via the LogSourceExtractor and skips any candidate rule whose logsource conflicts with the event’s (a dimension set on both sides that disagrees). A dimension unset on either side is a wildcard, so an event tagged only product: windows skips product: linux rules while still evaluating Windows-category and logsource-less rules.

Disabled by default (None), leaving the hot path unchanged. Pruning fails open: an event with no extractable logsource evaluates every rule. The extractor is read on every evaluate call, so it can be swapped at runtime (e.g. carried across a hot-reload).

Source

pub fn logsource_extractor(&self) -> Option<&LogSourceExtractor>

Returns the configured logsource extractor, if any. None means logsource pruning is disabled.

Source

pub fn logsource_pruned_total(&self) -> u64

Total rules skipped because their logsource conflicted with an event’s, since engine creation. Zero unless an extractor is set.

Counts both sources of conflict pruning: always-evaluated rules the candidate index never returns because their product bucket conflicts, and candidate rules the residual logsource_compatible check drops in the evaluation loop.

Source

pub fn logsource_absent_total(&self) -> u64

Total evaluate calls where the extractor produced no logsource and pruning failed open (every rule evaluated). Zero unless an extractor is set.

Source

pub fn logsource_eligibility( &self, event_logsource: &LogSource, ) -> (usize, usize)

Static view of how many loaded rules are eligible (logsource-compatible) versus pruned (conflicting) for event_logsource, returned as (eligible, pruned). Used to report how much of a ruleset a given logsource (for example a schema’s implied logsource) actually evaluates, independent of any specific event’s field values.

Source

pub fn set_include_event(&mut self, include: bool)

Set global include_event — when true, all match results include the full event JSON regardless of per-rule custom attributes.

Source

pub fn set_match_detail(&mut self, level: MatchDetailLevel)

Set the match-detail verbosity for detection results.

Off (default) records each match as { field, value }, identical to pre-enrichment releases. Summary adds the originating selection, the matcher kind, and case sensitivity, and reports keyword and absence matches that Off omits. Full additionally records the pattern that fired. The extra work runs only when a rule matches and only above Off, so the default hot path is unchanged.

Source

pub fn match_detail(&self) -> MatchDetailLevel

Returns the configured match-detail verbosity.

Source

pub fn add_pipeline(&mut self, pipeline: Pipeline)

Add a pipeline to the engine.

Pipelines are applied to rules during add_rule / add_collection. Only affects rules added after this call.

Source

pub fn add_rule(&mut self, rule: &SigmaRule) -> Result<()>

Add a single parsed Sigma rule.

If pipelines are set, the rule is cloned and transformed before compilation. The rule index folds the new rule incrementally; the bloom index also folds it incrementally and only triggers a full rebuild when its doubling watermark is reached, so this call is amortized O(1) per rule. With the daachorse-index feature enabled and the cross-rule AC index turned on at runtime, the call falls back to a full rebuild because the daachorse automaton has no incremental update path.

Source

pub fn add_rules<'a, I>(&mut self, rules: I) -> Vec<(usize, EvalError)>
where I: IntoIterator<Item = &'a SigmaRule>,

Add many parsed Sigma rules in a single batch.

Each rule is compiled (with the engine’s pipelines applied, if any) and pushed onto the rule set. Compilation errors are collected and returned as (rule_index_in_input, error) pairs without aborting the batch; rules that did compile remain loaded. The inverted index and per-field bloom filter are rebuilt once at the end of the batch.

Prefer this over a loop of Engine::add_rule when loading large rule sets: the per-call rebuild is O(N) in the total rule count, so per-rule adds turn a 3K-rule corpus into O(N²) work.

Source

pub fn add_collection(&mut self, collection: &SigmaCollection) -> Result<()>

Add all detection rules from a parsed collection, then apply filters.

Filter rules modify referenced detection rules by appending exclusion conditions. Correlation rules are handled by CorrelationEngine. The inverted index is rebuilt once after all rules and filters are loaded.

Source

pub fn save_hir(&self) -> Result<Vec<u8>>

Serialize the retained rule HIR to a versioned cache blob (see rsigma_ir::encode_rules).

The blob captures rules added via the parsed-rule paths (add_rule, add_rules, add_collection, and the pipeline variants) in post-pipeline, pre-filter form. It does not capture filter injections (applied to the compiled rules) or rules added via Engine::add_compiled_rule / Engine::extend_compiled_rules. Re-apply any filters after Engine::load_hir if the engine used them.

Source

pub fn load_hir(&mut self, bytes: &[u8]) -> Result<()>

Load rules from a HIR cache blob produced by Engine::save_hir, compiling each into the engine and rebuilding the indexes once.

Rules are appended to any already loaded, so a warm start loads into a fresh Engine::new. A blob whose schema version differs from this build’s is rejected (see rsigma_ir::decode_rules).

Source

pub fn transform_rule(&self, rule: &SigmaRule) -> Result<TransformedRule>

Apply this engine’s pipelines to rule and return the rewritten rule, without compiling or loading anything.

Use this to read what the configured pipelines do to a rule: the rewritten logsource, the conditions a pipeline injected, or which transformation ids fired. Loading a rule discards the rewritten Sigma AST and keeps only the compiled form, so this is the way to see it.

The engine is not modified and no rule is added. Cost is one clone plus one pipeline pass, the same as the load path pays, so call it once per rule set load rather than per event. If only the rewritten logsource matters, read it from Engine::rules after loading instead: the compiled rules already carry it.

Source

pub fn transform_collection( &self, collection: &SigmaCollection, ) -> Result<Vec<TransformedRule>>

Apply this engine’s pipelines to every detection rule in collection.

Per-rule equivalent of Engine::transform_rule, in collection order.

Source

pub fn add_collection_with_pipelines( &mut self, collection: &SigmaCollection, pipelines: &[Pipeline], ) -> Result<()>

Add all detection rules from a collection, applying the given pipelines.

This is a convenience method that temporarily sets pipelines, adds the collection, then clears them. The inverted index is rebuilt once after all rules and filters are loaded.

Source

pub fn apply_filter(&mut self, filter: &FilterRule) -> Result<()>

Apply a filter rule to all referenced detection rules and rebuild the index.

Source

pub fn add_compiled_rule(&mut self, rule: CompiledRule)

Add a pre-compiled rule directly. The rule index folds the new rule incrementally; the bloom index also folds it incrementally and only triggers a full rebuild when its doubling watermark is reached, so this call is amortized O(1) per rule. With the cross-rule AC index enabled (daachorse-index feature, runtime toggle), this falls back to a full rebuild because daachorse has no incremental update path.

Source

pub fn extend_compiled_rules<I>(&mut self, rules: I)
where I: IntoIterator<Item = CompiledRule>,

Add many pre-compiled rules in a single batch. The inverted index and bloom filter are rebuilt exactly once at the end, regardless of how many rules are appended.

Source

pub fn evaluate<E: Event>(&self, event: &E) -> Vec<EvaluationResult>

Evaluate an event against candidate rules using the inverted index.

When a logsource extractor is configured (see Engine::set_logsource_extractor) the event’s logsource is derived from it and used for conflict-based pruning.

Source

pub fn evaluate_pruned<E: Event>( &self, event: &E, event_logsource: &LogSource, ) -> Vec<EvaluationResult>

Evaluate an event with a caller-resolved event logsource for conflict-based pruning, bypassing the engine’s own extractor.

The schema router uses this to feed a per-event logsource resolved from the event’s explicit fields plus the recognized schema’s implied logsource, so cross-product rules are pruned even when the event carries no explicit product/service/category field. Pruning is conflict-based: a rule is skipped only when a dimension is set on both the rule and event_logsource and the values differ.

Source

pub fn evaluate_with_logsource<E: Event>( &self, event: &E, event_logsource: &LogSource, ) -> Vec<EvaluationResult>

Evaluate an event against candidate rules matching the given logsource.

Uses the inverted index for candidate pre-filtering, then applies the logsource constraint. Only rules whose logsource is compatible with event_logsource are evaluated.

Source

pub fn evaluate_batch<E: Event + Sync>( &self, events: &[&E], ) -> Vec<Vec<EvaluationResult>>

Evaluate a batch of events, returning per-event match results.

When the parallel feature is enabled, events are evaluated concurrently using rayon’s work-stealing thread pool. Otherwise, falls back to sequential evaluation.

Source

pub fn rule_count(&self) -> usize

Number of rules loaded in the engine.

Source

pub fn rules(&self) -> &[CompiledRule]

Access the compiled rules.

Source

pub fn rule_metadata(&self, key: &str) -> RuleMetadataLookup

Resolve a rule key (an id, or a title for a rule without one) to the documentation of every loaded rule that carries it.

Trait Implementations§

Source§

impl Default for Engine

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.