use crate::LibmagicError;
use crate::evaluator::{operators, types};
use crate::parser::ast::{MagicRule, Operator, TypeKind, Value};
use log::debug;
pub(crate) fn evaluate_pattern_rule(
rule: &MagicRule,
buffer: &[u8],
absolute_offset: usize,
max_string_length: usize,
) -> Result<(bool, crate::parser::ast::Value), LibmagicError> {
let match_outcome = types::read_pattern_match(
buffer,
absolute_offset,
&rule.typ,
Some(&rule.value),
max_string_length,
)
.map_err(|e| LibmagicError::EvaluationError(e.into()))?;
let pattern_found = match_outcome.is_some();
let matched = match &rule.op {
crate::parser::ast::Operator::Equal => pattern_found,
crate::parser::ast::Operator::NotEqual => !pattern_found,
other => {
return Err(LibmagicError::EvaluationError(
types::TypeReadError::UnsupportedType {
type_name: format!(
"operator {other:?} is not supported for pattern-bearing type {:?}; only Equal (=) and NotEqual (!=) are allowed",
rule.typ
),
}
.into(),
));
}
};
let value = match_outcome.unwrap_or_else(|| crate::parser::ast::Value::String(String::new()));
Ok((matched, value))
}
pub(crate) fn evaluate_value_rule(
rule: &MagicRule,
buffer: &[u8],
absolute_offset: usize,
max_string_length: usize,
flip_endian: bool,
) -> Result<(bool, crate::parser::ast::Value), LibmagicError> {
let read_typ: std::borrow::Cow<'_, crate::parser::ast::TypeKind> = if flip_endian {
std::borrow::Cow::Owned(types::flip_type_endian(&rule.typ))
} else {
std::borrow::Cow::Borrowed(&rule.typ)
};
let read_value = types::read_typed_value_with_pattern(
buffer,
absolute_offset,
read_typ.as_ref(),
Some(&rule.value),
max_string_length,
)
.map_err(|e| LibmagicError::EvaluationError(e.into()))?;
let transformed_value = match rule.value_transform {
None => read_value,
Some(t) => {
let transformed = operators::apply_value_transform(&read_value, t)
.map_err(LibmagicError::EvaluationError)?;
types::narrow_transformed_to_type_width(transformed, &rule.typ)
}
};
let expected_value = types::coerce_value_to_type(&rule.value, &rule.typ);
let expected_ref: &crate::parser::ast::Value = expected_value.as_ref();
let matched = match &rule.op {
crate::parser::ast::Operator::BitwiseNot => operators::apply_bitwise_not_with_width(
&transformed_value,
expected_ref,
rule.typ.bit_width(),
),
crate::parser::ast::Operator::BitwiseAndMask(mask) => {
operators::apply_bitwise_and_mask_with_width(
*mask,
&transformed_value,
expected_ref,
rule.typ.bit_width(),
)
}
op => operators::apply_operator(op, &transformed_value, expected_ref),
};
let display_value = string_ordering_display_value(
rule,
buffer,
absolute_offset,
max_string_length,
transformed_value,
);
Ok((matched, display_value))
}
pub(crate) fn string_ordering_display_value(
rule: &MagicRule,
buffer: &[u8],
absolute_offset: usize,
max_string_length: usize,
compared: crate::parser::ast::Value,
) -> crate::parser::ast::Value {
use crate::parser::ast::Operator::{GreaterEqual, GreaterThan, LessEqual, LessThan};
let is_ordering = matches!(rule.op, LessThan | GreaterThan | LessEqual | GreaterEqual);
let string16_endian = match rule.typ {
TypeKind::String16 { endian } => Some(endian),
_ => None,
};
let is_string_like = string16_endian.is_some() || matches!(rule.typ, TypeKind::String { .. });
if is_ordering && is_string_like {
let gated = newline_stop_gate(rule);
let read_cap = if gated {
types::any_value_string_bound(buffer, absolute_offset, max_string_length)
} else {
max_string_length
};
let full_read = match string16_endian {
Some(endian) if gated => {
types::read_string16_any_value(buffer, absolute_offset, endian, max_string_length)
}
Some(endian) => types::read_string16(buffer, absolute_offset, endian, read_cap),
None => types::read_string(buffer, absolute_offset, Some(read_cap)),
};
match full_read {
Ok(full_field) => full_field,
Err(e) => {
debug!(
"string_ordering_display_value: full-field read failed at offset {absolute_offset} for rule '{}': {e}; rendering compared prefix",
rule.message
);
compared
}
}
} else {
compared
}
}
pub(crate) fn newline_stop_gate(rule: &MagicRule) -> bool {
if matches!(rule.op, Operator::AnyValue) {
return true;
}
let is_ordering = matches!(
rule.op,
Operator::LessThan | Operator::GreaterThan | Operator::LessEqual | Operator::GreaterEqual
);
is_ordering && pattern_first_byte_is_null(&rule.value)
}
fn pattern_first_byte_is_null(pattern: &Value) -> bool {
match pattern {
Value::String(s) => s.as_bytes().first() == Some(&0),
Value::Bytes(b) => b.first() == Some(&0),
Value::Uint(_) | Value::Int(_) | Value::Float(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::ast::{OffsetSpec, StringFlags};
fn rule_with(op: Operator, value: Value) -> MagicRule {
MagicRule::new(
OffsetSpec::Absolute(0),
TypeKind::String {
max_length: None,
flags: StringFlags::default(),
},
op,
value,
"msg".to_string(),
)
}
#[test]
fn test_any_value_rule_gates() {
let rule = rule_with(Operator::AnyValue, Value::String(String::new()));
assert!(newline_stop_gate(&rule));
}
#[test]
fn test_ordering_rule_with_null_first_byte_pattern_gates() {
let rule = rule_with(
Operator::GreaterThan,
Value::Bytes(vec![0, b'A', b'B', b'C']),
);
assert!(newline_stop_gate(&rule));
}
#[test]
fn test_ordering_rule_with_non_null_first_byte_pattern_does_not_gate() {
let rule = rule_with(Operator::GreaterThan, Value::String("0".to_string()));
assert!(!newline_stop_gate(&rule));
}
#[test]
fn test_equality_rule_does_not_gate_even_with_null_first_byte_pattern() {
let rule = rule_with(Operator::Equal, Value::Bytes(vec![0, b'A']));
assert!(!newline_stop_gate(&rule));
}
#[test]
fn test_equality_rule_with_ordinary_pattern_does_not_gate() {
let rule = rule_with(Operator::Equal, Value::String("hello".to_string()));
assert!(!newline_stop_gate(&rule));
}
#[test]
fn test_ordering_rule_with_empty_pattern_does_not_panic_or_gate() {
let rule = rule_with(Operator::LessThan, Value::String(String::new()));
assert!(!newline_stop_gate(&rule));
}
#[test]
fn test_all_four_ordering_operators_check_first_byte() {
for op in [
Operator::LessThan,
Operator::GreaterThan,
Operator::LessEqual,
Operator::GreaterEqual,
] {
let op_dbg = format!("{op:?}");
let gated = rule_with(op.clone(), Value::Bytes(vec![0, b'X']));
assert!(
newline_stop_gate(&gated),
"operator {op_dbg} with null-first-byte pattern must gate"
);
let ungated = rule_with(op, Value::String("X".to_string()));
assert!(
!newline_stop_gate(&ungated),
"operator {op_dbg} with non-null-first-byte pattern must not gate"
);
}
}
#[test]
fn test_ordering_display_value_gated_by_null_first_byte_stops_at_newline() {
let rule = rule_with(
Operator::GreaterThan,
Value::Bytes(vec![0, b'A', b'B', b'C']),
);
let buffer = b"ZZZZABC\nSECOND\n";
let display = string_ordering_display_value(
&rule,
buffer,
0,
8192,
Value::String(String::new()), );
assert_eq!(display, Value::String("ZZZZABC".to_string()));
}
#[test]
fn test_ordering_display_value_ungated_keeps_full_field_with_embedded_newline() {
let rule = rule_with(Operator::GreaterThan, Value::String("0.6.1".to_string()));
let buffer = b"0.6.2\nrelease notes";
let display =
string_ordering_display_value(&rule, buffer, 0, 8192, Value::String(String::new()));
assert_eq!(display, Value::String("0.6.2\nrelease notes".to_string()));
}
#[test]
fn test_ordering_display_value_equality_op_is_never_gated_or_reached() {
let rule = rule_with(Operator::Equal, Value::Bytes(vec![0, b'A']));
let buffer = b"whatever\ncontent";
let display = string_ordering_display_value(
&rule,
buffer,
0,
8192,
Value::String("compared".to_string()),
);
assert_eq!(display, Value::String("compared".to_string()));
}
}