use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::mem;
use std::mem::transmute;
use std::pin::Pin;
use std::time::Duration;
use crate::errors::VariableError;
use crate::scanner::context::{ScanState, create_wasm_store_and_ctx};
use crate::scanner::{DataSnippets, ScanContext};
use crate::wasm::runtime::Store;
use crate::{Rules, ScanError, ScanResults, Variable};
pub struct Scanner<'r> {
_rules: &'r Rules,
wasm_store: Pin<Box<Store<ScanContext<'static, 'static>>>>,
needs_reset: bool,
snippets: BTreeMap<usize, Vec<u8>>,
}
impl<'r> Scanner<'r> {
pub fn new(rules: &'r Rules) -> Scanner<'r> {
Scanner {
_rules: rules,
wasm_store: create_wasm_store_and_ctx(rules),
needs_reset: true,
snippets: BTreeMap::new(),
}
}
}
impl<'r> Scanner<'r> {
pub fn scan(
&mut self,
base: usize,
data: &[u8],
) -> Result<&mut Self, ScanError> {
if self.needs_reset {
self.scan_context_mut().reset();
self.needs_reset = false;
}
else {
self.scan_context_mut().unconfirmed_matches.clear();
}
let ctx = self.scan_context_mut();
ctx.scan_state = ScanState::ScanningBlock((base, data));
ctx.set_pattern_search_done(false);
ctx.search_for_patterns()?;
ctx.scan_state = ScanState::Idle;
for (_, match_list) in ctx.pattern_matches.matches_per_pattern() {
for match_ in
match_list.iter().filter(|match_| match_.base == base)
{
if let Some(match_data) = data.get(match_.block_range()) {
match self.snippets.entry(match_.range.start) {
Entry::Occupied(mut entry) => {
let snippet = entry.get_mut();
if match_data.len() > snippet.len() {
debug_assert!(match_data.starts_with(snippet));
entry.insert(match_data.to_vec());
} else {
debug_assert!(snippet.starts_with(match_data));
}
}
Entry::Vacant(entry) => {
entry.insert(match_data.to_vec());
}
}
} else {
debug_assert!(false)
}
}
}
Ok(self)
}
pub fn finish(&mut self) -> Result<ScanResults<'_, 'r>, ScanError> {
if self.needs_reset {
self.scan_context_mut().reset();
}
self.needs_reset = true;
let ctx = self.scan_context_mut();
ctx.eval_conditions()?;
ctx.scan_state = ScanState::Finished(DataSnippets::MultiBlock(
mem::take(&mut self.snippets),
));
Ok(ScanResults::new(ctx))
}
pub fn set_global<T: TryInto<Variable>>(
&mut self,
ident: &str,
value: T,
) -> Result<&mut Self, VariableError>
where
VariableError: From<<T as TryInto<Variable>>::Error>,
{
self.scan_context_mut().set_global(ident, value)?;
Ok(self)
}
pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
self.scan_context_mut().set_timeout(timeout);
self
}
pub fn max_matches_per_pattern(&mut self, n: usize) -> &mut Self {
self.scan_context_mut().pattern_matches.max_matches_per_pattern(n);
self
}
pub fn console_log<F>(&mut self, callback: F) -> &mut Self
where
F: FnMut(String) + 'r,
{
self.scan_context_mut().console_log = Some(Box::new(callback));
self
}
#[cfg(feature = "rules-profiling")]
pub fn slowest_rules(
&self,
n: usize,
) -> Vec<crate::scanner::ProfilingData<'_>> {
self.scan_context().slowest_rules(n)
}
#[cfg(feature = "rules-profiling")]
pub fn clear_profiling_data(&mut self) {
self.scan_context_mut().clear_profiling_data()
}
}
impl<'r> Scanner<'r> {
#[cfg(feature = "rules-profiling")]
#[inline]
fn scan_context<'a>(&self) -> &ScanContext<'r, 'a> {
unsafe {
transmute::<&ScanContext<'static, 'static>, &ScanContext<'r, '_>>(
self.wasm_store.data(),
)
}
}
#[inline]
fn scan_context_mut<'a>(&mut self) -> &'a mut ScanContext<'r, 'a> {
unsafe {
transmute::<
&mut ScanContext<'static, 'static>,
&mut ScanContext<'r, 'a>,
>(self.wasm_store.data_mut())
}
}
}
impl<'r> From<crate::scanner::Scanner<'r>> for Scanner<'r> {
fn from(scanner: crate::scanner::Scanner<'r>) -> Self {
Self {
_rules: scanner._rules,
wasm_store: scanner.wasm_store,
needs_reset: true,
snippets: Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use crate::scanner::blocks::Scanner;
use crate::{Compiler, compile};
use std::time::Duration;
#[test]
fn block_scanner_1() {
let rules = compile(
r#"
rule test { strings: $a = "ipsum" condition: $a }"#,
)
.unwrap();
let mut scanner = Scanner::new(&rules);
let results = scanner
.scan(0, b"Lorem ipsum")
.unwrap()
.scan(1000, b"dolor ipsum sit amet")
.unwrap()
.finish()
.unwrap();
assert_eq!(results.matching_rules().len(), 1);
let rule = results.matching_rules().next().unwrap();
let pattern = rule.patterns().next().unwrap();
let mut matches = pattern.matches();
let match1 = matches.next().unwrap();
assert_eq!(match1.data(), b"ipsum".as_slice());
assert_eq!(match1.range(), 6..11);
let match2 = matches.next().unwrap();
assert_eq!(match2.data(), b"ipsum".as_slice());
assert_eq!(match2.range(), 1006..1011);
}
#[test]
fn block_scanner_2() {
let rules = compile(
r#"
rule test { strings: $a = /ipsum.*amet/s condition: $a }"#,
)
.unwrap();
let mut scanner = Scanner::new(&rules);
let results = scanner
.scan(0, b"Lorem ipsum")
.unwrap()
.scan(1000, b"dolor ipsum sit amet")
.unwrap()
.finish()
.unwrap();
let rule = results.matching_rules().next().unwrap();
let pattern = rule.patterns().next().unwrap();
let mut matches = pattern.matches();
let match_ = matches.next().unwrap();
assert_eq!(match_.data(), b"ipsum sit amet".as_slice());
assert_eq!(match_.range(), 1006..1020);
}
#[test]
fn block_scanner_match_in_range() {
let rules = compile(
r#"
rule test { strings: $a = "ipsum" condition: $a in (1003..1008) }"#,
)
.unwrap();
let mut scanner = Scanner::new(&rules);
let results = scanner
.scan(0, b"Lorem ipsum")
.unwrap()
.scan(1000, b"dolor ipsum sit amet")
.unwrap()
.finish()
.unwrap();
assert_eq!(results.matching_rules().len(), 1);
let rule = results.matching_rules().next().unwrap();
let pattern = rule.patterns().next().unwrap();
let mut matches = pattern.matches();
let match1 = matches.next().unwrap();
assert_eq!(match1.data(), b"ipsum".as_slice());
assert_eq!(match1.range(), 6..11);
let match2 = matches.next().unwrap();
assert_eq!(match2.data(), b"ipsum".as_slice());
assert_eq!(match2.range(), 1006..1011);
}
#[test]
fn block_scanner_match_at_offset() {
let rules = compile(
r#"
rule test { strings: $a = "ipsum" condition: $a at 1006 }"#,
)
.unwrap();
let mut scanner = Scanner::new(&rules);
let results = scanner
.scan(1000, b"dolor ipsum sit amet")
.unwrap()
.finish()
.unwrap();
assert_eq!(results.matching_rules().len(), 1);
}
#[test]
fn block_scanner_global() {
let mut compiler = Compiler::new();
compiler
.define_global("foo", "")
.unwrap()
.add_source(
r#"
rule test { condition: foo == "foo" }"#,
)
.unwrap();
let rules = compiler.build();
let mut scanner = Scanner::new(&rules);
scanner.set_global("foo", "foo").unwrap();
let results = scanner.finish().unwrap();
assert_eq!(results.matching_rules().len(), 1);
}
#[test]
fn block_scanner_timeout() {
let rules = compile(
r#"
rule slow {
condition:
for any i in (0..1000000000) : (
uint8(i) == 0xCC
)
}"#,
)
.unwrap();
let mut scanner = Scanner::new(&rules);
scanner.set_timeout(Duration::from_secs(1));
let err = scanner.finish().unwrap_err();
assert_eq!(err.to_string(), "timeout");
}
#[test]
fn block_scanner_filesize() {
let rules = compile(
r#"
rule filesize_undefined {
condition:
not defined filesize
}"#,
)
.unwrap();
let mut scanner = Scanner::new(&rules);
let results = scanner.finish().unwrap();
assert_eq!(results.matching_rules().len(), 1);
}
}