use std::collections::HashMap;
use std::fmt;
use aho_corasick::AhoCorasick;
use tracing::debug;
use crate::document::Document;
use crate::parser::{Expression, Match, MatchType, Search};
use crate::rule::Detection;
use crate::tokeniser::{BoolSym, ModSym};
use crate::value::Value;
struct Cache<'a>(&'a Vec<Option<Value<'a>>>);
impl<'a> Document for Cache<'a> {
fn find(&self, key: &str) -> Option<Value> {
let i = key.chars().nth(0).expect("could not get key") as u32;
self.0[i as usize].clone()
}
}
struct Passthrough<'a>(Option<Value<'a>>);
impl<'a> Document for Passthrough<'a> {
fn find(&self, _: &str) -> Option<Value> {
self.0.clone()
}
}
#[derive(Debug, PartialEq)]
pub(crate) enum SolverResult {
True,
False,
Missing,
}
impl fmt::Display for SolverResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::True => write!(f, "true"),
Self::False => write!(f, "false"),
Self::Missing => write!(f, "missing"),
}
}
}
/// Evalutes a `Document` with a provided detection, returning true if the detection solves.
pub fn solve(detection: &Detection, document: &dyn Document) -> bool {
match solve_expression(&detection.expression, &detection.identifiers, document) {
SolverResult::True => true,
SolverResult::False | SolverResult::Missing => false,
}
}
pub(crate) fn solve_expression(
expression: &Expression,
identifiers: &HashMap<String, Expression>,
document: &dyn Document,
) -> SolverResult {
match *expression {
Expression::BooleanGroup(BoolSym::And, ref group) => {
for expression in group {
match solve_expression(expression, identifiers, document) {
SolverResult::True => {}
SolverResult::False => return SolverResult::False,
SolverResult::Missing => return SolverResult::Missing,
}
}
SolverResult::True
}
Expression::BooleanGroup(BoolSym::Or, ref group) => {
let mut res = SolverResult::Missing;
for expression in group {
match solve_expression(expression, identifiers, document) {
SolverResult::True => return SolverResult::True,
SolverResult::False => res = SolverResult::False,
SolverResult::Missing => {}
}
}
res
}
Expression::BooleanExpression(ref left, ref op, ref right) => {
// Edge cases
match (&**left, op, &**right) {
(
Expression::Cast(ref left, ModSym::Str),
BoolSym::Equal,
Expression::Cast(ref right, ModSym::Str),
) => {
let x = match document.find(left) {
Some(x) => x,
None => {
debug!("evaluating missing, no left hand side for {}", expression);
return SolverResult::Missing;
}
};
let x = match x.to_string() {
Some(v) => v,
None => {
debug!(
"evaluating false, could not cast left field to string for {}",
expression
);
return SolverResult::False;
}
};
let y = match document.find(right) {
Some(x) => x,
None => {
debug!("evaluating missing, no right hand side for {}", expression);
return SolverResult::Missing;
}
};
let y = match y.to_string() {
Some(v) => v,
None => {
debug!(
"evaluating false, could not cast right field to string for {}",
expression
);
return SolverResult::False;
}
};
if x == y {
return SolverResult::True;
} else {
return SolverResult::False;
}
}
(Expression::Field(ref left), BoolSym::Equal, Expression::Boolean(b)) => {
let x = match document.find(left) {
Some(x) => x,
None => {
debug!("evaluating missing, no left hand side for {}", expression);
return SolverResult::Missing;
}
};
let x = match x.as_bool() {
Some(v) => v,
None => {
debug!(
"evaluating false, could not cast left field to boolean for {}",
expression
);
return SolverResult::False;
}
};
if x == *b {
return SolverResult::True;
} else {
return SolverResult::False;
}
}
(Expression::Field(ref left), BoolSym::Equal, Expression::Null) => {
let x = match document.find(left) {
Some(x) => x,
None => {
debug!("evaluating missing, no left hand side for {}", expression);
return SolverResult::Missing;
}
};
if x.is_null() {
return SolverResult::True;
} else {
return SolverResult::False;
}
}
_ => {}
}
// Boolean expressions
match *op {
BoolSym::Equal
| BoolSym::GreaterThan
| BoolSym::GreaterThanOrEqual
| BoolSym::LessThan
| BoolSym::LessThanOrEqual => {
let x = match left.as_ref() {
Expression::Field(f) => {
let i = match document.find(f) {
Some(i) => i,
None => {
debug!(
"evaluating missing, no left hand side for {}",
expression
);
return SolverResult::Missing;
}
};
match i {
Value::Float(_) | Value::Int(_) | Value::UInt(_) => i,
_ => {
debug!(
"evaluating false, no left hand side for {}",
expression
);
return SolverResult::False;
}
}
}
Expression::Cast(field, ModSym::Flt) => {
let i = match document.find(field) {
Some(i) => i,
None => {
debug!(
"evaluating missing, no left hand side for {}",
expression
);
return SolverResult::Missing;
}
};
match i {
Value::Bool(x) => {
if x {
Value::Float(1.0)
} else {
Value::Float(1.0)
}
}
Value::Float(x) => Value::Float(x),
Value::Int(x) => {
if x <= f64::MAX as i64 {
Value::Float(x as f64)
} else {
debug!(
"evaluating false, could not cast left hand side for {} - {}",
expression, x
);
return SolverResult::False;
}
}
Value::String(x) => match x.parse::<f64>() {
Ok(i) => Value::Float(i),
Err(e) => {
debug!(
"evaluating false, could not cast left hand side for {} - {}",
expression, e
);
return SolverResult::False;
}
},
Value::UInt(x) => {
if x <= f64::MAX as u64 {
Value::Float(x as f64)
} else {
debug!(
"evaluating false, could not cast left hand side for {} - {}",
expression, x
);
return SolverResult::False;
}
}
_ => {
debug!(
"evaluating false, invalid type on left hand side for {}",
expression
);
return SolverResult::False;
}
}
}
Expression::Cast(field, ModSym::Int) => {
let i = match document.find(field) {
Some(i) => i,
None => {
debug!(
"evaluating missing, no left hand side for {}",
expression
);
return SolverResult::Missing;
}
};
match i {
Value::Bool(x) => {
if x {
Value::Int(1)
} else {
Value::Int(0)
}
}
Value::Float(x) => Value::Int(x.round() as i64),
Value::Int(x) => Value::Int(x),
Value::String(x) => match x.parse::<i64>() {
Ok(i) => Value::Int(i),
Err(e) => {
debug!(
"evaluating false, could not cast left hand side for {} - {}",
expression, e
);
return SolverResult::False;
}
},
Value::UInt(x) => {
if x <= i64::MAX as u64 {
Value::Int(x as i64)
} else {
debug!(
"evaluating false, could not cast left hand side for {} - {}",
expression, x
);
return SolverResult::False;
}
}
_ => {
debug!(
"evaluating false, invalid type on left hand side for {}",
expression
);
return SolverResult::False;
}
}
}
Expression::Boolean(i) => Value::Bool(*i),
Expression::Float(i) => Value::Float(*i),
Expression::Integer(i) => Value::Int(*i),
_ => {
debug!("encountered invalid left hand side for {}", expression);
return SolverResult::False;
}
};
let y = match right.as_ref() {
Expression::Field(f) => {
let i = match document.find(f) {
Some(i) => i,
None => {
debug!(
"evaluating missing, no right hand side for {}",
expression
);
return SolverResult::Missing;
}
};
match i {
Value::Float(_) | Value::Int(_) | Value::UInt(_) => i,
_ => {
debug!(
"evaluating false, no right hand side for {}",
expression
);
return SolverResult::False;
}
}
}
Expression::Cast(field, ModSym::Flt) => {
let i = match document.find(field) {
Some(i) => i,
None => {
debug!(
"evaluating missing, no right hand side for {}",
expression
);
return SolverResult::Missing;
}
};
match i {
Value::Bool(x) => {
if x {
Value::Float(1.0)
} else {
Value::Float(1.0)
}
}
Value::Float(x) => Value::Float(x),
Value::Int(x) => {
if x <= f64::MAX as i64 {
Value::Float(x as f64)
} else {
debug!(
"evaluating false, could not cast right hand side for {} - {}",
expression, x
);
return SolverResult::False;
}
}
Value::String(x) => match x.parse::<f64>() {
Ok(i) => Value::Float(i),
Err(e) => {
debug!(
"evaluating false, could not cast right hand side for {} - {}",
expression, e
);
return SolverResult::False;
}
},
Value::UInt(x) => {
if x <= f64::MAX as u64 {
Value::Float(x as f64)
} else {
debug!(
"evaluating false, could not cast right hand side for {} - {}",
expression, x
);
return SolverResult::False;
}
}
_ => {
debug!(
"evaluating false, invalid type on right hand side for {}",
expression
);
return SolverResult::False;
}
}
}
Expression::Cast(field, ModSym::Int) => {
let i = match document.find(field) {
Some(i) => i,
None => {
debug!(
"evaluating missing, no right hand side for {}",
expression
);
return SolverResult::Missing;
}
};
match i {
Value::Bool(x) => {
if x {
Value::Int(1)
} else {
Value::Int(0)
}
}
Value::Float(x) => Value::Int(x.round() as i64),
Value::Int(x) => Value::Int(x),
Value::String(x) => match x.parse::<i64>() {
Ok(i) => Value::Int(i),
Err(e) => {
debug!(
"evaluating false, could not cast right hand side for {} - {}",
expression, e
);
return SolverResult::False;
}
},
Value::UInt(x) => {
if x <= i64::MAX as u64 {
Value::Int(x as i64)
} else {
debug!(
"evaluating false, could not cast right hand side for {} - {}",
expression, x
);
return SolverResult::False;
}
}
_ => {
debug!(
"evaluating false, invalid type on right hand side for {}",
expression
);
return SolverResult::False;
}
}
}
Expression::Boolean(i) => Value::Bool(*i),
Expression::Float(i) => Value::Float(*i),
Expression::Integer(i) => Value::Int(*i),
_ => {
debug!("encountered invalid right hand side for {}", expression);
return SolverResult::False;
}
};
let res = match (x, *op, y) {
(Value::Bool(x), BoolSym::Equal, Value::Bool(y)) => x == y,
(Value::Float(x), BoolSym::Equal, Value::Float(y)) => x == y,
(Value::Int(x), BoolSym::Equal, Value::Int(y)) => x == y,
(Value::UInt(x), BoolSym::Equal, Value::UInt(y)) => x == y,
(Value::UInt(x), BoolSym::Equal, Value::Int(y)) if x <= i64::MAX as u64 => {
x as i64 == y
}
(Value::Int(x), BoolSym::Equal, Value::UInt(y)) if y <= i64::MAX as u64 => {
x == y as i64
}
(_, BoolSym::Equal, _) => false,
(Value::Float(x), BoolSym::GreaterThan, Value::Float(y)) => x > y,
(Value::Int(x), BoolSym::GreaterThan, Value::Int(y)) => x > y,
(Value::UInt(x), BoolSym::GreaterThan, Value::UInt(y)) => x > y,
(Value::UInt(x), BoolSym::GreaterThan, Value::Int(y))
if x <= i64::MAX as u64 =>
{
x as i64 > y
}
(Value::Int(x), BoolSym::GreaterThan, Value::UInt(y))
if y <= i64::MAX as u64 =>
{
x > y as i64
}
(_, BoolSym::GreaterThan, _) => false,
(Value::Float(x), BoolSym::GreaterThanOrEqual, Value::Float(y)) => x >= y,
(Value::Int(x), BoolSym::GreaterThanOrEqual, Value::Int(y)) => x >= y,
(Value::UInt(x), BoolSym::GreaterThanOrEqual, Value::UInt(y)) => x >= y,
(Value::UInt(x), BoolSym::GreaterThanOrEqual, Value::Int(y))
if x <= i64::MAX as u64 =>
{
x as i64 >= y
}
(Value::Int(x), BoolSym::GreaterThanOrEqual, Value::UInt(y))
if y <= i64::MAX as u64 =>
{
x >= y as i64
}
(_, BoolSym::GreaterThanOrEqual, _) => false,
(Value::Float(x), BoolSym::LessThan, Value::Float(y)) => x < y,
(Value::Int(x), BoolSym::LessThan, Value::Int(y)) => x < y,
(Value::UInt(x), BoolSym::LessThan, Value::UInt(y)) => x < y,
(Value::UInt(x), BoolSym::LessThan, Value::Int(y))
if x <= i64::MAX as u64 =>
{
(x as i64) < y
}
(Value::Int(x), BoolSym::LessThan, Value::UInt(y))
if y <= i64::MAX as u64 =>
{
x < y as i64
}
(_, BoolSym::LessThan, _) => false,
(Value::Float(x), BoolSym::LessThanOrEqual, Value::Float(y)) => x <= y,
(Value::Int(x), BoolSym::LessThanOrEqual, Value::Int(y)) => x <= y,
(Value::UInt(x), BoolSym::LessThanOrEqual, Value::UInt(y)) => x <= y,
(Value::UInt(x), BoolSym::LessThanOrEqual, Value::Int(y))
if x <= i64::MAX as u64 =>
{
x as i64 <= y
}
(Value::Int(x), BoolSym::LessThanOrEqual, Value::UInt(y))
if y <= i64::MAX as u64 =>
{
x <= y as i64
}
(_, BoolSym::LessThanOrEqual, _) => false,
_ => unreachable!(),
};
match res {
true => SolverResult::True,
_ => SolverResult::False,
}
}
BoolSym::And => {
let x = match solve_expression(&*left, identifiers, document) {
SolverResult::True => (true, false),
SolverResult::False => return SolverResult::False,
SolverResult::Missing => return SolverResult::Missing,
};
let y = match solve_expression(&*right, identifiers, document) {
SolverResult::True => (true, false),
SolverResult::False => (false, false),
SolverResult::Missing => (false, true),
};
debug!(
"evaluating {} ({}) for {}",
x.0 && y.0,
x.1 || y.1,
expression
);
if x.1 || y.1 {
SolverResult::Missing
} else if x.0 && y.0 {
SolverResult::True
} else {
SolverResult::False
}
}
BoolSym::Or => {
let x = match solve_expression(&*left, identifiers, document) {
SolverResult::True => return SolverResult::True,
SolverResult::False => (false, false),
SolverResult::Missing => (false, true),
};
let y = match solve_expression(&*right, identifiers, document) {
SolverResult::True => (true, false),
SolverResult::False => (false, false),
SolverResult::Missing => (false, true),
};
debug!(
"evaluating {} ({}) for {}",
x.0 || y.0,
x.1 || y.1,
expression
);
if x.0 || y.0 {
SolverResult::True
} else if x.1 && y.1 {
SolverResult::Missing
} else {
SolverResult::False
}
}
}
}
Expression::Identifier(ref i) => match identifiers.get(i) {
Some(e) => solve_expression(e, identifiers, document),
None => unreachable!(),
},
Expression::Match(Match::All, ref e) => {
let (_, group) = match **e {
Expression::Identifier(ref i) => match identifiers.get(i) {
Some(Expression::BooleanGroup(o, g)) => (o, g),
Some(e) => return match_all(e, identifiers, document),
_ => unreachable!(),
},
Expression::BooleanGroup(ref o, ref g) => (o, g),
_ => return match_all(e, identifiers, document),
};
for expression in group {
match solve_expression(expression, identifiers, document) {
SolverResult::True => {}
SolverResult::False => return SolverResult::False,
SolverResult::Missing => return SolverResult::Missing,
}
}
SolverResult::True
}
Expression::Match(Match::Of(c), ref e) => {
let (_, group) = match **e {
Expression::Identifier(ref identifier) => match identifiers.get(identifier) {
Some(Expression::BooleanGroup(o, g)) => (o, g),
Some(e) => return match_of(e, identifiers, document, c),
_ => {
unreachable!();
}
},
Expression::BooleanGroup(ref o, ref g) => (o, g),
_ => return match_of(e, identifiers, document, c),
};
let mut count = 0;
let mut res = SolverResult::Missing;
for expression in group {
if c == 0 {
match solve_expression(expression, identifiers, document) {
SolverResult::True => return SolverResult::False,
SolverResult::False => {
res = SolverResult::True;
}
SolverResult::Missing => {}
}
} else {
match solve_expression(expression, identifiers, document) {
SolverResult::True => {
count += 1;
if count >= c {
return SolverResult::True;
}
}
SolverResult::False => res = SolverResult::False,
SolverResult::Missing => {}
}
}
}
res
}
Expression::Matrix(ref columns, ref rows) => {
// NOTE: Field and search widths must be the same or tau will panic, for now this is
// fine as only the optimiser can write this expression, and for those using core it is
// on them to ensure they don't break this. There are ways to lock this down and it
// could be done in the future...
let size = columns.len();
let mut cache: Vec<Option<Value>> = Vec::with_capacity(size);
for _ in 0..size {
cache.push(None);
}
let mut res = SolverResult::Missing;
for row in rows {
let mut hit = SolverResult::True;
for (i, expression) in row.iter().enumerate() {
if let Some(expression) = expression {
if cache[i].is_none() {
let value = match document.find(&columns[i]) {
Some(v) => v,
None => {
debug!(
"evaluating missing, field not found for {}",
expression
);
hit = SolverResult::Missing;
break;
}
};
let _ = std::mem::replace(&mut cache[i], Some(value));
}
match solve_expression(expression, identifiers, &Cache(&cache)) {
SolverResult::True => {}
SolverResult::False => {
hit = SolverResult::False;
break;
}
SolverResult::Missing => {
hit = SolverResult::Missing;
break;
}
}
}
}
match hit {
SolverResult::True => return SolverResult::True,
SolverResult::False => res = SolverResult::False,
SolverResult::Missing => {}
}
}
res
}
Expression::Negate(ref e) => {
let res = match solve_expression(e.as_ref(), identifiers, document) {
SolverResult::True => SolverResult::False,
SolverResult::False => SolverResult::True,
SolverResult::Missing => SolverResult::False,
};
debug!("evaluating {} for {}", res, expression);
res
}
Expression::Nested(ref s, ref e) => {
let value = match document.find(s) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
return SolverResult::Missing;
}
};
match value {
Value::Object(o) => solve_expression(e, identifiers, &o),
Value::Array(a) => {
if let Expression::Match(Match::All, expression) = &**e {
if let Expression::BooleanGroup(BoolSym::Or, expressions) = &**expression {
for expression in expressions {
let mut res = SolverResult::Missing;
for v in a.iter() {
if let Some(x) = v.as_object() {
match solve_expression(expression, identifiers, &x) {
SolverResult::True => {
res = SolverResult::True;
break;
}
SolverResult::False => res = SolverResult::False,
SolverResult::Missing => {}
}
}
}
if res != SolverResult::True {
return res;
}
}
return SolverResult::True;
} else if let Expression::Matrix(columns, rows) = &**expression {
// NOTE: We can't really make use of the optimisations provided by a
// matrix here as we have to loop through the array! For that reason we
// basically null this optimisation...
for row in rows {
let mut res = SolverResult::Missing;
for v in a.iter() {
let mut hit = SolverResult::True;
for (i, expression) in row.iter().enumerate() {
if let Some(expression) = expression {
if let Some(x) = v.as_object() {
let value = x.find(&columns[i]);
match solve_expression(
expression,
identifiers,
&Passthrough(value),
) {
SolverResult::True => {}
SolverResult::False => {
hit = SolverResult::False;
break;
}
SolverResult::Missing => {
hit = SolverResult::Missing;
break;
}
}
}
}
}
if hit == SolverResult::True {
res = SolverResult::True;
break;
}
}
if res != SolverResult::True {
return res;
}
}
return SolverResult::True;
}
}
for v in a.iter() {
if let Some(x) = v.as_object() {
if solve_expression(e, identifiers, &x) == SolverResult::True {
return SolverResult::True;
}
}
}
SolverResult::False
}
_ => {
debug!(
"evaluating false, field is not an array of objects or object for {}",
expression
);
SolverResult::False
}
}
}
Expression::Search(ref s, ref f, ref c) => {
let value = match document.find(f) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
return SolverResult::Missing;
}
};
let res = match (value, c) {
(Value::String(ref x), _) => search(s, x),
(Value::Array(a), _) => {
let mut res = SolverResult::False;
for v in a.iter() {
if let Some(x) = v.as_str() {
if search(s, x) == SolverResult::True {
res = SolverResult::True;
break;
}
} else if *c {
let x = match v {
Value::Bool(x) => x.to_string(),
Value::Float(x) => x.to_string(),
Value::Int(x) => x.to_string(),
Value::UInt(x) => x.to_string(),
_ => continue,
};
if search(s, x.as_str()) == SolverResult::True {
res = SolverResult::True;
break;
}
}
}
res
}
(Value::Bool(x), true) => {
let x = x.to_string();
search(s, x.as_str())
}
(Value::Float(x), true) => {
let x = x.to_string();
search(s, x.as_str())
}
(Value::Int(x), true) => {
let x = x.to_string();
search(s, x.as_str())
}
(Value::UInt(x), true) => {
let x = x.to_string();
search(s, x.as_str())
}
_ => {
debug!(
"evaluating false, field is not an array of strings, or a string for {}",
expression
);
return SolverResult::Missing;
}
};
debug!("evaluating {} for {}", res, expression);
res
}
Expression::BooleanGroup(_, _)
| Expression::Boolean(_)
| Expression::Cast(_, _)
| Expression::Field(_)
| Expression::Float(_)
| Expression::Integer(_)
| Expression::Null => unreachable!(),
}
}
#[inline]
fn match_all(
expression: &Expression,
identifiers: &HashMap<String, Expression>,
document: &dyn Document,
) -> SolverResult {
if let Expression::Search(Search::AhoCorasick(a, m, _), i, c) = expression {
let value = match document.find(i) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
return SolverResult::Missing;
}
};
match (value, c) {
(Value::String(ref x), _) => {
if slow_aho(a, m, x) != m.len() as u64 {
return SolverResult::False;
}
}
(Value::Array(x), _) => {
let mut found = false;
for v in x.iter() {
if let Some(x) = v.as_str() {
if slow_aho(a, m, x) == m.len() as u64 {
found = true;
break;
}
} else if *c {
let x = match v {
Value::Bool(x) => x.to_string(),
Value::Float(x) => x.to_string(),
Value::Int(x) => x.to_string(),
Value::UInt(x) => x.to_string(),
_ => continue,
};
if slow_aho(a, m, x.as_str()) == m.len() as u64 {
found = true;
break;
}
}
}
if !found {
return SolverResult::False;
}
}
(Value::Bool(x), true) => {
let x = x.to_string();
if slow_aho(a, m, x.as_str()) != m.len() as u64 {
return SolverResult::False;
}
}
(Value::Float(x), true) => {
let x = x.to_string();
if slow_aho(a, m, x.as_str()) != m.len() as u64 {
return SolverResult::False;
}
}
(Value::Int(x), true) => {
let x = x.to_string();
if slow_aho(a, m, x.as_str()) != m.len() as u64 {
return SolverResult::False;
}
}
(Value::UInt(x), true) => {
let x = x.to_string();
if slow_aho(a, m, x.as_str()) != m.len() as u64 {
return SolverResult::False;
}
}
(_, _) => {
debug!(
"evaluating false, field is not an array of strings, or a string for {}",
expression
);
return SolverResult::Missing;
}
}
} else if let Expression::Search(Search::RegexSet(s, _), i, c) = expression {
let value = match document.find(i) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
return SolverResult::Missing;
}
};
match (value, c) {
(Value::String(ref x), _) => {
let mut hits = 0;
for _ in s.matches(x).iter() {
hits += 1;
}
if hits != s.patterns().len() {
return SolverResult::False;
}
}
(Value::Array(x), _) => {
let mut found = false;
for v in x.iter() {
if let Some(x) = v.as_str() {
let mut hits = 0;
for _ in s.matches(x).iter() {
hits += 1;
}
if hits == s.patterns().len() {
found = true;
break;
}
} else if *c {
let x = match v {
Value::Bool(x) => x.to_string(),
Value::Float(x) => x.to_string(),
Value::Int(x) => x.to_string(),
Value::UInt(x) => x.to_string(),
_ => continue,
};
let mut hits = 0;
for _ in s.matches(x.as_str()).iter() {
hits += 1;
}
if hits == s.patterns().len() {
found = true;
break;
}
}
}
if !found {
return SolverResult::False;
}
}
(Value::Bool(x), true) => {
let x = x.to_string();
let mut hits = 0;
for _ in s.matches(x.as_str()).iter() {
hits += 1;
}
if hits != s.patterns().len() {
return SolverResult::False;
}
}
(Value::Float(x), true) => {
let x = x.to_string();
let mut hits = 0;
for _ in s.matches(x.as_str()).iter() {
hits += 1;
}
if hits != s.patterns().len() {
return SolverResult::False;
}
}
(Value::Int(x), true) => {
let x = x.to_string();
let mut hits = 0;
for _ in s.matches(x.as_str()).iter() {
hits += 1;
}
if hits != s.patterns().len() {
return SolverResult::False;
}
}
(Value::UInt(x), true) => {
let x = x.to_string();
let mut hits = 0;
for _ in s.matches(x.as_str()).iter() {
hits += 1;
}
if hits != s.patterns().len() {
return SolverResult::False;
}
}
_ => {
debug!(
"evaluating false, field is not an array of strings, or a string for {}",
expression
);
return SolverResult::Missing;
}
}
} else if let Expression::Matrix(ref columns, ref rows) = expression {
// NOTE: Field and search widths must be the same or tau will panic, for now this is
// fine as only the optimiser can write this expression, and for those using core it is
// on them to ensure they don't break this. There are ways to lock this down and it
// could be done in the future...
let size = columns.len();
let mut cache: Vec<Option<Value>> = Vec::with_capacity(size);
for _ in 0..size {
cache.push(None);
}
for row in rows {
let mut hit = SolverResult::True;
for (i, expression) in row.iter().enumerate() {
if let Some(expression) = expression {
if cache[i].is_none() {
let value = match document.find(&columns[i]) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
hit = SolverResult::Missing;
break;
}
};
let _ = std::mem::replace(&mut cache[i], Some(value));
}
match solve_expression(expression, identifiers, &Cache(&cache)) {
SolverResult::True => {}
SolverResult::False => {
hit = SolverResult::False;
break;
}
SolverResult::Missing => {
hit = SolverResult::Missing;
break;
}
}
}
}
match hit {
SolverResult::True => {}
SolverResult::False => return SolverResult::False,
SolverResult::Missing => return SolverResult::Missing,
}
}
} else {
return solve_expression(expression, identifiers, document);
}
SolverResult::True
}
#[inline]
fn match_of(
expression: &Expression,
identifiers: &HashMap<String, Expression>,
document: &dyn Document,
count: u64,
) -> SolverResult {
if count == 0 {
return match solve_expression(expression, identifiers, document) {
SolverResult::True => SolverResult::False,
SolverResult::False => SolverResult::True,
SolverResult::Missing => return SolverResult::Missing,
};
} else if let Expression::Search(Search::AhoCorasick(a, m, _), i, cast) = expression {
let value = match document.find(i) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
return SolverResult::Missing;
}
};
match (value, cast) {
(Value::String(ref x), _) => {
let c = slow_aho(a, m, x);
if c >= count {
return SolverResult::True;
}
}
(Value::Array(x), _) => {
for v in x.iter() {
if let Some(x) = v.as_str() {
let hits = slow_aho(a, m, x);
if hits >= count {
return SolverResult::True;
}
} else if *cast {
let x = match v {
Value::Bool(x) => x.to_string(),
Value::Float(x) => x.to_string(),
Value::Int(x) => x.to_string(),
Value::UInt(x) => x.to_string(),
_ => continue,
};
let hits = slow_aho(a, m, x.as_str());
if hits >= count {
return SolverResult::True;
}
}
}
}
(Value::Bool(x), true) => {
let x = x.to_string();
let c = slow_aho(a, m, x.as_str());
if c >= count {
return SolverResult::True;
}
}
(Value::Float(x), true) => {
let x = x.to_string();
let c = slow_aho(a, m, x.as_str());
if c >= count {
return SolverResult::True;
}
}
(Value::Int(x), true) => {
let x = x.to_string();
let c = slow_aho(a, m, x.as_str());
if c >= count {
return SolverResult::True;
}
}
(Value::UInt(x), true) => {
let x = x.to_string();
let c = slow_aho(a, m, x.as_str());
if c >= count {
return SolverResult::True;
}
}
_ => {
debug!(
"evaluating false, field is not an array of strings, or a string for {}",
expression
);
return SolverResult::Missing;
}
}
} else if let Expression::Search(Search::RegexSet(s, _), i, cast) = expression {
let value = match document.find(i) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
return SolverResult::Missing;
}
};
match (value, cast) {
(Value::String(ref x), _) => {
let mut c = 0;
for _ in s.matches(x).iter() {
c += 1;
}
if c >= count {
return SolverResult::True;
}
}
(Value::Array(x), _) => {
for v in x.iter() {
if let Some(x) = v.as_str() {
let mut hits = 0;
for _ in s.matches(x).iter() {
hits += 1;
}
if hits >= count {
return SolverResult::True;
}
} else if *cast {
let x = match v {
Value::Bool(x) => x.to_string(),
Value::Float(x) => x.to_string(),
Value::Int(x) => x.to_string(),
Value::UInt(x) => x.to_string(),
_ => continue,
};
let mut hits = 0;
for _ in s.matches(x.as_str()).iter() {
hits += 1;
}
if hits >= count {
return SolverResult::True;
}
}
}
}
(Value::Bool(x), true) => {
let x = x.to_string();
let mut c = 0;
for _ in s.matches(x.as_str()).iter() {
c += 1;
}
if c >= count {
return SolverResult::True;
}
}
(Value::Float(x), true) => {
let x = x.to_string();
let mut c = 0;
for _ in s.matches(x.as_str()).iter() {
c += 1;
}
if c >= count {
return SolverResult::True;
}
}
(Value::Int(x), true) => {
let x = x.to_string();
let mut c = 0;
for _ in s.matches(x.as_str()).iter() {
c += 1;
}
if c >= count {
return SolverResult::True;
}
}
(Value::UInt(x), true) => {
let x = x.to_string();
let mut c = 0;
for _ in s.matches(x.as_str()).iter() {
c += 1;
}
if c >= count {
return SolverResult::True;
}
}
_ => {
debug!(
"evaluating false, field is not an array of strings, or a string for {}",
expression
);
return SolverResult::Missing;
}
}
} else if let Expression::Matrix(ref columns, ref rows) = expression {
// NOTE: Field and search widths must be the same or tau will panic, for now this is
// fine as only the optimiser can write this expression, and for those using core it is
// on them to ensure they don't break this. There are ways to lock this down and it
// could be done in the future...
let size = columns.len();
let mut cache: Vec<Option<Value>> = Vec::with_capacity(size);
for _ in 0..size {
cache.push(None);
}
let mut hits = 0;
let mut res = SolverResult::Missing;
for row in rows {
let mut hit = SolverResult::True;
for (i, expression) in row.iter().enumerate() {
if let Some(expression) = expression {
if cache[i].is_none() {
let value = match document.find(&columns[i]) {
Some(v) => v,
None => {
debug!("evaluating missing, field not found for {}", expression);
hit = SolverResult::Missing;
break;
}
};
let _ = std::mem::replace(&mut cache[i], Some(value));
}
match solve_expression(expression, identifiers, &Cache(&cache)) {
SolverResult::True => {}
SolverResult::False => {
hit = SolverResult::False;
break;
}
SolverResult::Missing => {
hit = SolverResult::Missing;
break;
}
}
}
}
match hit {
SolverResult::True => {
hits += 1;
if hits >= count {
return SolverResult::True;
}
}
SolverResult::False => res = SolverResult::False,
SolverResult::Missing => {}
}
}
return res;
} else {
return solve_expression(expression, identifiers, document);
}
SolverResult::False
}
#[inline]
fn search(kind: &Search, value: &str) -> SolverResult {
match kind {
Search::Any => {
return SolverResult::True;
}
Search::Exact(ref i) => {
if i == value {
return SolverResult::True;
}
}
Search::Contains(ref i) => {
if value.contains(i) {
return SolverResult::True;
}
}
Search::EndsWith(ref i) => {
if value.ends_with(i) {
return SolverResult::True;
}
}
Search::StartsWith(ref i) => {
if value.starts_with(i) {
return SolverResult::True;
}
}
Search::Regex(ref i, _) => {
if i.is_match(value) {
return SolverResult::True;
}
}
Search::RegexSet(ref i, _) => {
if i.is_match(value) {
return SolverResult::True;
}
}
Search::AhoCorasick(ref a, ref m, _) => {
for i in a.find_overlapping_iter(value) {
match m[i.pattern()] {
MatchType::Contains(_) => return SolverResult::True,
MatchType::EndsWith(_) => {
if i.end() == value.len() {
return SolverResult::True;
}
}
MatchType::Exact(_) => {
if i.start() == 0 && i.end() == value.len() {
return SolverResult::True;
}
}
MatchType::StartsWith(_) => {
if i.start() == 0 {
return SolverResult::True;
}
}
}
}
return SolverResult::False;
}
}
SolverResult::False
}
#[inline]
fn slow_aho(a: &AhoCorasick, m: &[MatchType], value: &str) -> u64 {
// TODO: Benchmark properly to work out whether the bitmap really is better on average
let len = m.len();
if len < 64 {
let mut map = 0;
for i in a.find_overlapping_iter(value) {
let p = i.pattern();
match m[p] {
MatchType::Contains(_) => {
map |= 1 << p.as_u64();
}
MatchType::EndsWith(_) => {
if i.end() == value.len() {
map |= 1 << p.as_u64();
}
}
MatchType::Exact(_) => {
if i.start() == 0 && i.end() == value.len() {
map |= 1 << p.as_u64();
}
}
MatchType::StartsWith(_) => {
if i.start() == 0 {
map |= 1 << p.as_u64();
}
}
}
}
let mut hits = 0;
for i in 0..len {
hits += (map >> i) & 0x1;
}
hits
} else {
let mut hits = std::collections::HashSet::with_capacity(len);
for i in a.find_overlapping_iter(value) {
let p = i.pattern();
match m[p] {
MatchType::Contains(_) => {
hits.insert(p);
}
MatchType::EndsWith(_) => {
if i.end() == value.len() {
hits.insert(p);
}
}
MatchType::Exact(_) => {
if i.start() == 0 && i.end() == value.len() {
hits.insert(p);
}
}
MatchType::StartsWith(_) => {
if i.start() == 0 {
hits.insert(p);
}
}
}
}
hits.len() as u64
}
}