summaryrefslogtreecommitdiffstats
path: root/rust/src/detect/byte_math.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rust/src/detect/byte_math.rs1163
1 files changed, 1163 insertions, 0 deletions
diff --git a/rust/src/detect/byte_math.rs b/rust/src/detect/byte_math.rs
new file mode 100644
index 0000000..80bd3d5
--- /dev/null
+++ b/rust/src/detect/byte_math.rs
@@ -0,0 +1,1163 @@
+/* Copyright (C) 2022 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// Author: Jeff Lucovsky <jlucovsky@oisf.net>
+
+use crate::detect::error::RuleParseError;
+use crate::detect::parser::{parse_token, take_until_whitespace};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+
+use nom7::bytes::complete::tag;
+use nom7::character::complete::multispace0;
+use nom7::sequence::preceded;
+use nom7::{Err, IResult};
+use std::str;
+
+pub const DETECT_BYTEMATH_FLAG_RELATIVE: u8 = 0x01;
+pub const DETECT_BYTEMATH_FLAG_STRING: u8 = 0x02;
+pub const DETECT_BYTEMATH_FLAG_BITMASK: u8 = 0x04;
+pub const DETECT_BYTEMATH_FLAG_ENDIAN: u8 = 0x08;
+pub const DETECT_BYTEMATH_FLAG_RVALUE_VAR: u8 = 0x10;
+pub const DETECT_BYTEMATH_FLAG_NBYTES_VAR: u8 = 0x20;
+
+// Ensure required values are provided
+const DETECT_BYTEMATH_FLAG_NBYTES: u8 = 0x1;
+const DETECT_BYTEMATH_FLAG_OFFSET: u8 = 0x2;
+const DETECT_BYTEMATH_FLAG_OPER: u8 = 0x4;
+const DETECT_BYTEMATH_FLAG_RVALUE: u8 = 0x8;
+const DETECT_BYTEMATH_FLAG_RESULT: u8 = 0x10;
+const DETECT_BYTEMATH_FLAG_REQUIRED: u8 = DETECT_BYTEMATH_FLAG_RESULT
+ | DETECT_BYTEMATH_FLAG_RVALUE
+ | DETECT_BYTEMATH_FLAG_NBYTES
+ | DETECT_BYTEMATH_FLAG_OFFSET
+ | DETECT_BYTEMATH_FLAG_OPER;
+
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+// operators: +, -, /, *, <<, >>
+pub enum ByteMathOperator {
+ OperatorNone = 1,
+ Addition = 2,
+ Subtraction = 3,
+ Division = 4,
+ Multiplication = 5,
+ LeftShift = 6,
+ RightShift = 7,
+}
+
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+// endian <big|little|dce>
+pub enum ByteMathEndian {
+ _EndianNone = 0,
+ BigEndian = 1,
+ LittleEndian = 2,
+ EndianDCE = 3,
+}
+pub const DETECT_BYTEMATH_ENDIAN_DEFAULT: ByteMathEndian = ByteMathEndian::BigEndian;
+
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ByteMathBase {
+ _BaseNone = 0,
+ BaseOct = 8,
+ BaseDec = 10,
+ BaseHex = 16,
+}
+const BASE_DEFAULT: ByteMathBase = ByteMathBase::BaseDec;
+
+// Fixed position parameter count: bytes, offset, oper, rvalue, result
+// result is not parsed with the fixed position parameters as it's
+// often swapped with optional parameters
+pub const DETECT_BYTEMATH_FIXED_PARAM_COUNT: usize = 5;
+// Optional parameters: endian, relative, string, dce, bitmask
+pub const DETECT_BYTEMATH_MAX_PARAM_COUNT: usize = 10;
+
+#[derive(Debug)]
+enum ResultValue {
+ Numeric(u64),
+ String(String),
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct DetectByteMathData {
+ rvalue_str: *const c_char,
+ result: *const c_char,
+ nbytes_str: *const c_char,
+ rvalue: u32,
+ offset: i32,
+ bitmask_val: u32,
+ bitmask_shift_count: u16,
+ id: u16,
+ flags: u8,
+ local_id: u8,
+ nbytes: u8,
+ oper: ByteMathOperator,
+ endian: ByteMathEndian, // big, little, dce
+ base: ByteMathBase, // From string or dce
+}
+
+impl Drop for DetectByteMathData {
+ fn drop(&mut self) {
+ unsafe {
+ if !self.result.is_null() {
+ let _ = CString::from_raw(self.result as *mut c_char);
+ }
+ if !self.rvalue_str.is_null() {
+ let _ = CString::from_raw(self.rvalue_str as *mut c_char);
+ }
+ if !self.nbytes_str.is_null() {
+ let _ = CString::from_raw(self.nbytes_str as *mut c_char);
+ }
+ }
+ }
+}
+
+impl Default for DetectByteMathData {
+ fn default() -> Self {
+ DetectByteMathData {
+ local_id: 0,
+ flags: 0,
+ nbytes: 0,
+ offset: 0,
+ oper: ByteMathOperator::OperatorNone,
+ rvalue_str: std::ptr::null_mut(),
+ nbytes_str: std::ptr::null_mut(),
+ rvalue: 0,
+ result: std::ptr::null_mut(),
+ endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
+ base: BASE_DEFAULT,
+ bitmask_val: 0,
+ bitmask_shift_count: 0,
+ id: 0,
+ }
+ }
+}
+
+impl DetectByteMathData {
+ pub fn new() -> Self {
+ Self {
+ ..Default::default()
+ }
+ }
+}
+
+fn get_string_value(value: &str) -> Result<ByteMathBase, ()> {
+ let res = match value {
+ "hex" => ByteMathBase::BaseHex,
+ "oct" => ByteMathBase::BaseOct,
+ "dec" => ByteMathBase::BaseDec,
+ _ => return Err(()),
+ };
+
+ Ok(res)
+}
+
+fn get_oper_value(value: &str) -> Result<ByteMathOperator, ()> {
+ let res = match value {
+ "+" => ByteMathOperator::Addition,
+ "-" => ByteMathOperator::Subtraction,
+ "/" => ByteMathOperator::Division,
+ "*" => ByteMathOperator::Multiplication,
+ "<<" => ByteMathOperator::LeftShift,
+ ">>" => ByteMathOperator::RightShift,
+ _ => return Err(()),
+ };
+
+ Ok(res)
+}
+
+fn get_endian_value(value: &str) -> Result<ByteMathEndian, ()> {
+ let res = match value {
+ "big" => ByteMathEndian::BigEndian,
+ "little" => ByteMathEndian::LittleEndian,
+ "dce" => ByteMathEndian::EndianDCE,
+ _ => return Err(()),
+ };
+
+ Ok(res)
+}
+
+// Parsed as a u64 for validation with u32 {min,max} so values greater than uint32
+// are not treated as a string value.
+fn parse_var(input: &str) -> IResult<&str, ResultValue, RuleParseError<&str>> {
+ let (input, value) = parse_token(input)?;
+ if let Ok(val) = value.parse::<u64>() {
+ Ok((input, ResultValue::Numeric(val)))
+ } else {
+ Ok((input, ResultValue::String(value.to_string())))
+ }
+}
+
+fn parse_bytemath(input: &str) -> IResult<&str, DetectByteMathData, RuleParseError<&str>> {
+ // Inner utility function for easy error creation.
+ fn make_error(reason: String) -> nom7::Err<RuleParseError<&'static str>> {
+ Err::Error(RuleParseError::InvalidByteMath(reason))
+ }
+ let (_, values) = nom7::multi::separated_list1(
+ tag(","),
+ preceded(multispace0, nom7::bytes::complete::is_not(",")),
+ )(input)?;
+
+ if values.len() < DETECT_BYTEMATH_FIXED_PARAM_COUNT
+ || values.len() > DETECT_BYTEMATH_MAX_PARAM_COUNT
+ {
+ return Err(make_error(format!("Incorrect argument string; at least {} values must be specified but no more than {}: {:?}",
+ DETECT_BYTEMATH_FIXED_PARAM_COUNT, DETECT_BYTEMATH_MAX_PARAM_COUNT, input)));
+ }
+
+ let mut required_flags: u8 = 0;
+ let mut byte_math = DetectByteMathData::new();
+ //for value in &values[0..] {
+ for value in values {
+ let (mut val, mut name) = take_until_whitespace(value)?;
+ val = val.trim();
+ name = name.trim();
+ match name {
+ "oper" => {
+ if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OPER) {
+ return Err(make_error("operator already set".to_string()));
+ }
+ byte_math.oper = match get_oper_value(val) {
+ Ok(val) => val,
+ Err(_) => {
+ return Err(make_error(format!("unknown oper value {}", val)));
+ }
+ };
+ required_flags |= DETECT_BYTEMATH_FLAG_OPER;
+ }
+ "result" => {
+ if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RESULT) {
+ return Err(make_error("result already set".to_string()));
+ }
+ let tmp: String = val
+ .parse()
+ .map_err(|_| make_error(format!("invalid result: {}", val)))?;
+ match CString::new(tmp) {
+ Ok(strval) => {
+ byte_math.result = strval.into_raw();
+ required_flags |= DETECT_BYTEMATH_FLAG_RESULT;
+ }
+ _ => {
+ return Err(make_error(
+ "parse string not safely convertible to C".to_string(),
+ ));
+ }
+ }
+ }
+ "rvalue" => {
+ if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RVALUE) {
+ return Err(make_error("rvalue already set".to_string()));
+ }
+ let (_, res) = parse_var(val)?;
+ match res {
+ ResultValue::Numeric(val) => {
+ if val >= u32::MIN.into() && val <= u32::MAX.into() {
+ byte_math.rvalue = val as u32
+ } else {
+ return Err(make_error(format!(
+ "invalid rvalue value: must be between {} and {}: {}",
+ 1,
+ u32::MAX,
+ val
+ )));
+ }
+ }
+ ResultValue::String(val) => match CString::new(val) {
+ Ok(newval) => {
+ byte_math.rvalue_str = newval.into_raw();
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_RVALUE_VAR;
+ }
+ _ => {
+ return Err(make_error(
+ "parse string not safely convertible to C".to_string(),
+ ))
+ }
+ },
+ }
+ required_flags |= DETECT_BYTEMATH_FLAG_RVALUE;
+ }
+ "endian" => {
+ if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) {
+ return Err(make_error("endianess already set".to_string()));
+ }
+ byte_math.endian = match get_endian_value(val) {
+ Ok(val) => val,
+ Err(_) => {
+ return Err(make_error(format!("invalid endian value: {}", val)));
+ }
+ };
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
+ }
+ "dce" => {
+ if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) {
+ return Err(make_error("endianess already set".to_string()));
+ }
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
+ byte_math.endian = ByteMathEndian::EndianDCE;
+ }
+ "string" => {
+ if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_STRING) {
+ return Err(make_error("string already set".to_string()));
+ }
+ byte_math.base = match get_string_value(val) {
+ Ok(val) => val,
+ Err(_) => {
+ return Err(make_error(format!("invalid string value: {}", val)));
+ }
+ };
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_STRING;
+ }
+ "relative" => {
+ if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
+ return Err(make_error("relative already set".to_string()));
+ }
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_RELATIVE;
+ }
+ "bitmask" => {
+ if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_BITMASK) {
+ return Err(make_error("bitmask already set".to_string()));
+ }
+ let trimmed = if val.starts_with("0x") || val.starts_with("0X") {
+ &val[2..]
+ } else {
+ val
+ };
+
+ let val = u32::from_str_radix(trimmed, 16)
+ .map_err(|_| make_error(format!("invalid bitmask value: {}", value)))?;
+ byte_math.bitmask_val = val;
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_BITMASK;
+ }
+ "offset" => {
+ if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OFFSET) {
+ return Err(make_error("offset already set".to_string()));
+ }
+ byte_math.offset = val
+ .parse::<i32>()
+ .map_err(|_| make_error(format!("invalid offset value: {}", val)))?;
+ if byte_math.offset > 65535 || byte_math.offset < -65535 {
+ return Err(make_error(format!(
+ "invalid offset value: must be between -65535 and 65535: {}",
+ val
+ )));
+ }
+ required_flags |= DETECT_BYTEMATH_FLAG_OFFSET;
+ }
+ "bytes" => {
+ if 0 != (required_flags & DETECT_BYTEMATH_FLAG_NBYTES) {
+ return Err(make_error("nbytes already set".to_string()));
+ }
+ let (_, res) = parse_var(val)?;
+ match res {
+ ResultValue::Numeric(val) => {
+ if (1..=10).contains(&val) {
+ byte_math.nbytes = val as u8
+ } else {
+ return Err(make_error(format!(
+ "invalid nbytes value: must be between 1 and 10: {}",
+ val
+ )));
+ }
+ }
+ ResultValue::String(val) => match CString::new(val) {
+ Ok(newval) => {
+ byte_math.nbytes_str = newval.into_raw();
+ byte_math.flags |= DETECT_BYTEMATH_FLAG_NBYTES_VAR;
+ }
+ _ => {
+ return Err(make_error(
+ "parse string not safely convertible to C".to_string(),
+ ))
+ }
+ },
+ }
+ required_flags |= DETECT_BYTEMATH_FLAG_NBYTES;
+ }
+ _ => {
+ return Err(make_error(format!("unknown byte_math keyword: {}", name)));
+ }
+ };
+ }
+
+ // Ensure required values are present
+ if (required_flags & DETECT_BYTEMATH_FLAG_REQUIRED) != DETECT_BYTEMATH_FLAG_REQUIRED {
+ return Err(make_error(format!(
+ "required byte_math parameters missing: \"{:?}\"",
+ input
+ )));
+ }
+
+ // Using left/right shift further restricts the value of nbytes. Note that
+ // validation has already ensured nbytes is in [1..10]
+ match byte_math.oper {
+ ByteMathOperator::LeftShift | ByteMathOperator::RightShift => {
+ if byte_math.nbytes > 4 {
+ return Err(make_error(format!("nbytes must be 1 through 4 (inclusive) when used with \"<<\" or \">>\"; {} is not valid", byte_math.nbytes)));
+ }
+ }
+ _ => {}
+ };
+
+ Ok((input, byte_math))
+}
+
+/// Intermediary function between the C code and the parsing functions.
+#[no_mangle]
+pub unsafe extern "C" fn ScByteMathParse(c_arg: *const c_char) -> *mut DetectByteMathData {
+ if c_arg.is_null() {
+ return std::ptr::null_mut();
+ }
+
+ let arg = match CStr::from_ptr(c_arg).to_str() {
+ Ok(arg) => arg,
+ Err(_) => {
+ return std::ptr::null_mut();
+ }
+ };
+ match parse_bytemath(arg) {
+ Ok((_, detect)) => return Box::into_raw(Box::new(detect)),
+ Err(_) => return std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn ScByteMathFree(ptr: *mut DetectByteMathData) {
+ if !ptr.is_null() {
+ let _ = Box::from_raw(ptr);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ // structure equality only used by test cases
+ impl PartialEq for DetectByteMathData {
+ fn eq(&self, other: &Self) -> bool {
+ let mut res: bool = false;
+
+ if !self.rvalue_str.is_null() && !other.rvalue_str.is_null() {
+ let s_val = unsafe { CStr::from_ptr(self.rvalue_str) };
+ let o_val = unsafe { CStr::from_ptr(other.rvalue_str) };
+ res = s_val == o_val;
+ } else if !self.rvalue_str.is_null() || !other.rvalue_str.is_null() {
+ return false;
+ }
+
+ if !self.nbytes_str.is_null() && !other.nbytes_str.is_null() {
+ let s_val = unsafe { CStr::from_ptr(self.nbytes_str) };
+ let o_val = unsafe { CStr::from_ptr(other.nbytes_str) };
+ res = s_val == o_val;
+ } else if !self.nbytes_str.is_null() || !other.nbytes_str.is_null() {
+ return false;
+ }
+
+ if !self.result.is_null() && !self.result.is_null() {
+ let s_val = unsafe { CStr::from_ptr(self.result) };
+ let o_val = unsafe { CStr::from_ptr(other.result) };
+ res = s_val == o_val;
+ } else if !self.result.is_null() || !self.result.is_null() {
+ return false;
+ }
+
+ res && self.local_id == other.local_id
+ && self.flags == other.flags
+ && self.nbytes == other.nbytes
+ && self.offset == other.offset
+ && self.oper == other.oper
+ && self.rvalue == other.rvalue
+ && self.endian == other.endian
+ && self.base == other.base
+ && self.bitmask_val == other.bitmask_val
+ && self.bitmask_shift_count == other.bitmask_shift_count
+ && self.id == other.id
+ }
+ }
+
+ fn valid_test(
+ args: &str, nbytes: u8, offset: i32, oper: ByteMathOperator, rvalue_str: &str, nbytes_str: &str, rvalue: u32,
+ result: &str, base: ByteMathBase, endian: ByteMathEndian, bitmask_val: u32, flags: u8,
+ ) {
+ let bmd = DetectByteMathData {
+ nbytes,
+ offset,
+ oper,
+ rvalue_str: if !rvalue_str.is_empty() {
+ CString::new(rvalue_str).unwrap().into_raw()
+ } else {
+ std::ptr::null_mut()
+ },
+ nbytes_str: if !nbytes_str.is_empty() {
+ CString::new(nbytes_str).unwrap().into_raw()
+ } else {
+ std::ptr::null_mut()
+ },
+ rvalue,
+ result: CString::new(result).unwrap().into_raw(),
+ base,
+ endian,
+ bitmask_val,
+ flags,
+ ..Default::default()
+ };
+
+ match parse_bytemath(args) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+
+ #[test]
+ fn test_parser_valid() {
+ valid_test(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result myresult, dce, string dec",
+ 4,
+ 3933,
+ ByteMathOperator::Addition,
+ "myrvalue",
+ "",
+ 0,
+ "myresult",
+ ByteMathBase::BaseDec,
+ ByteMathEndian::EndianDCE,
+ 0,
+ DETECT_BYTEMATH_FLAG_RVALUE_VAR
+ | DETECT_BYTEMATH_FLAG_STRING
+ | DETECT_BYTEMATH_FLAG_ENDIAN,
+ );
+
+ valid_test(
+ "bytes 4, offset 3933, oper +, rvalue 99, result other, dce, string dec",
+ 4,
+ 3933,
+ ByteMathOperator::Addition,
+ "",
+ "",
+ 99,
+ "other",
+ ByteMathBase::BaseDec,
+ ByteMathEndian::EndianDCE,
+ 0,
+ DETECT_BYTEMATH_FLAG_STRING | DETECT_BYTEMATH_FLAG_ENDIAN,
+ );
+
+ valid_test(
+ "bytes 4, offset -3933, oper +, rvalue myrvalue, result foo",
+ 4,
+ -3933,
+ ByteMathOperator::Addition,
+ "rvalue",
+ "",
+ 0,
+ "foo",
+ BASE_DEFAULT,
+ ByteMathEndian::BigEndian,
+ 0,
+ DETECT_BYTEMATH_FLAG_RVALUE_VAR,
+ );
+
+ valid_test(
+ "bytes nbytes_var, offset -3933, oper +, rvalue myrvalue, result foo",
+ 0,
+ -3933,
+ ByteMathOperator::Addition,
+ "rvalue",
+ "nbytes_var",
+ 0,
+ "foo",
+ BASE_DEFAULT,
+ ByteMathEndian::BigEndian,
+ 0,
+ DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_NBYTES_VAR,
+ );
+
+ // Out of order
+ valid_test(
+ "string dec, endian big, result other, rvalue 99, oper +, offset 3933, bytes 4",
+ 4,
+ 3933,
+ ByteMathOperator::Addition,
+ "",
+ "",
+ 99,
+ "other",
+ ByteMathBase::BaseDec,
+ ByteMathEndian::BigEndian,
+ 0,
+ DETECT_BYTEMATH_FLAG_STRING | DETECT_BYTEMATH_FLAG_ENDIAN,
+ );
+ }
+
+ #[test]
+ fn test_parser_string_valid() {
+ let mut bmd = DetectByteMathData {
+ nbytes: 4,
+ offset: 3933,
+ oper: ByteMathOperator::Addition,
+ rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
+ rvalue: 0,
+ result: CString::new("foo").unwrap().into_raw(),
+ endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
+ base: ByteMathBase::BaseDec,
+ flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_STRING,
+ ..Default::default()
+ };
+
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string dec",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR;
+ bmd.base = BASE_DEFAULT;
+ match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_STRING;
+ bmd.base = ByteMathBase::BaseHex;
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hex",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.base = ByteMathBase::BaseOct;
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string oct",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+
+ #[test]
+ fn test_parser_string_invalid() {
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string decimal"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hexadecimal"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string octal"
+ )
+ .is_err()
+ );
+ }
+
+ #[test]
+ // bytes must be between 1 and 10; when combined with rshift/lshift, must be 4 or less
+ fn test_parser_bytes_invalid() {
+ assert!(
+ parse_bytemath("bytes 0, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 11, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 5, offset 3933, oper >>, rvalue myrvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 5, offset 3933, oper <<, rvalue myrvalue, result foo").is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_bitmask_invalid() {
+ assert!(
+ parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x")
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask x12345678"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask X12345678"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x123456789012"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0q")
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask maple"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0xGHIJKLMN"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask #*#*@-"
+ )
+ .is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_bitmask_valid() {
+ let mut bmd = DetectByteMathData {
+ nbytes: 4,
+ offset: 3933,
+ oper: ByteMathOperator::Addition,
+ rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
+ rvalue: 0,
+ result: CString::new("foo").unwrap().into_raw(),
+ endian: ByteMathEndian::BigEndian,
+ base: BASE_DEFAULT,
+ flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_BITMASK,
+ ..Default::default()
+ };
+
+ bmd.bitmask_val = 0x12345678;
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x12345678",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.bitmask_val = 0xffff1234;
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask ffff1234",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.bitmask_val = 0xffff1234;
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0Xffff1234",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+ #[test]
+ fn test_parser_endian_valid() {
+ let mut bmd = DetectByteMathData {
+ nbytes: 4,
+ offset: 3933,
+ oper: ByteMathOperator::Addition,
+ rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
+ rvalue: 0,
+ result: CString::new("foo").unwrap().into_raw(),
+ endian: ByteMathEndian::BigEndian,
+ base: BASE_DEFAULT,
+ flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_ENDIAN,
+ ..Default::default()
+ };
+
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian big",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.endian = ByteMathEndian::LittleEndian;
+ match parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian little",
+ ) {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.endian = ByteMathEndian::EndianDCE;
+ match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, dce") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.endian = DETECT_BYTEMATH_ENDIAN_DEFAULT;
+ bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR;
+ match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+
+ #[test]
+ fn test_parser_endian_invalid() {
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian bigger"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian smaller"
+ )
+ .is_err()
+ );
+
+ // endianess can only be specified once
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian big, dce"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian small, endian big"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian small, dce"
+ )
+ .is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_oper_valid() {
+ let mut bmd = DetectByteMathData {
+ nbytes: 4,
+ offset: 3933,
+ oper: ByteMathOperator::Addition,
+ rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
+ rvalue: 0,
+ result: CString::new("foo").unwrap().into_raw(),
+ endian: ByteMathEndian::BigEndian,
+ base: BASE_DEFAULT,
+ flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR,
+ ..Default::default()
+ };
+
+ match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.oper = ByteMathOperator::Subtraction;
+ match parse_bytemath("bytes 4, offset 3933, oper -, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.oper = ByteMathOperator::Multiplication;
+ match parse_bytemath("bytes 4, offset 3933, oper *, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ bmd.oper = ByteMathOperator::Division;
+ match parse_bytemath("bytes 4, offset 3933, oper /, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ bmd.oper = ByteMathOperator::RightShift;
+ match parse_bytemath("bytes 4, offset 3933, oper >>, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ bmd.oper = ByteMathOperator::LeftShift;
+ match parse_bytemath("bytes 4, offset 3933, oper <<, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+
+ #[test]
+ fn test_parser_oper_invalid() {
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper !, rvalue myvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper ^, rvalue myvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper <>, rvalue myvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper ><, rvalue myvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper <, rvalue myvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper >, rvalue myvalue, result foo").is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_rvalue_valid() {
+ let mut bmd = DetectByteMathData {
+ nbytes: 4,
+ offset: 47303,
+ oper: ByteMathOperator::Multiplication,
+ rvalue_str: std::ptr::null_mut(),
+ rvalue: 4294967295,
+ result: CString::new("foo").unwrap().into_raw(),
+ endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
+ base: BASE_DEFAULT,
+ ..Default::default()
+ };
+
+ match parse_bytemath("bytes 4, offset 47303, oper *, rvalue 4294967295 , result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.rvalue = 1;
+ match parse_bytemath("bytes 4, offset 47303, oper *, rvalue 1, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ bmd.rvalue = 0;
+ match parse_bytemath("bytes 4, offset 47303, oper *, rvalue 0, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+
+ #[test]
+ fn test_parser_rvalue_invalid() {
+ assert!(
+ parse_bytemath("bytes 4, offset 47303, oper *, rvalue 4294967296, result foo").is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_offset_valid() {
+ let mut bmd = DetectByteMathData {
+ nbytes: 4,
+ offset: -65535,
+ oper: ByteMathOperator::Multiplication,
+ rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
+ rvalue: 0,
+ result: CString::new("foo").unwrap().into_raw(),
+ endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
+ base: BASE_DEFAULT,
+ flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR,
+ ..Default::default()
+ };
+
+ match parse_bytemath("bytes 4, offset -65535, oper *, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+
+ bmd.offset = 65535;
+ match parse_bytemath("bytes 4, offset 65535, oper *, rvalue myrvalue, result foo") {
+ Ok((_, val)) => {
+ assert_eq!(val, bmd);
+ }
+ Err(_) => {
+ assert!(false);
+ }
+ }
+ }
+
+ #[test]
+ // offset: numeric values must be between -65535 and 65535
+ fn test_parser_offset_invalid() {
+ assert!(
+ parse_bytemath("bytes 4, offset -70000, oper *, rvalue myvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 70000, oper +, rvalue myvalue, result foo").is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_incomplete_args() {
+ assert!(parse_bytemath("").is_err());
+ assert!(parse_bytemath("bytes 4").is_err());
+ assert!(parse_bytemath("bytes 4, offset 0").is_err());
+ assert!(parse_bytemath("bytes 4, offset 0, oper <<").is_err());
+ }
+
+ #[test]
+ fn test_parser_missing_required() {
+ assert!(
+ parse_bytemath("endian big, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, endian big, oper +, rvalue myrvalue, result foo,").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 3933, endian big, rvalue myrvalue, result foo")
+ .is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 3933, oper +, endian big, result foo").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, endian big").is_err()
+ );
+ }
+
+ #[test]
+ fn test_parser_invalid_args() {
+ assert!(parse_bytemath("monkey banana").is_err());
+ assert!(parse_bytemath("bytes nan").is_err());
+ assert!(parse_bytemath("bytes 4, offset nan").is_err());
+ assert!(parse_bytemath("bytes 4, offset 0, three 3, four 4, five 5, six 6, seven 7, eight 8, nine 9, ten 10, eleven 11").is_err());
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper ><, rvalue myrvalue").is_err()
+ );
+ assert!(
+ parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, endian endian").is_err()
+ );
+ }
+ #[test]
+ fn test_parser_multiple() {
+ assert!(
+ parse_bytemath(
+ "bytes 4, bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 0, offset 0, oper +, rvalue myrvalue, result myresult, endian big"
+ )
+ .is_err()
+ );
+ assert!(
+ parse_bytemath(
+ "bytes 4, offset 0, oper +, oper +, rvalue myrvalue, result myresult, endian big"
+ )
+ .is_err()
+ );
+ assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, rvalue myrvalue, result myresult, endian big").is_err());
+ assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, result myresult, endian big").is_err());
+ assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big, endian big").is_err());
+ }
+}