• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 23350122333

20 Mar 2026 03:33PM UTC coverage: 76.492% (-2.8%) from 79.315%
23350122333

Pull #15053

github

web-flow
Merge f5bf69f97 into 6587e363a
Pull Request #15053: Flow queue/v3

113 of 129 new or added lines in 9 files covered. (87.6%)

9534 existing lines in 453 files now uncovered.

256601 of 335461 relevant lines covered (76.49%)

4680806.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.88
/rust/src/detect/byte_math.rs
1
/* Copyright (C) 2022 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
// Author: Jeff Lucovsky <jlucovsky@oisf.net>
19

20
use crate::detect::error::RuleParseError;
21
use crate::detect::parser::{parse_var, take_until_whitespace, ResultValue};
22
use crate::detect::{get_endian_value, get_string_value, ByteBase, ByteEndian};
23
use std::ffi::{CStr, CString};
24
use std::os::raw::c_char;
25

26
use nom8::bytes::complete::tag;
27
use nom8::character::complete::multispace0;
28
use nom8::sequence::preceded;
29
use nom8::{Err, IResult, Parser};
30
use std::str;
31

32
pub const DETECT_BYTEMATH_FLAG_RELATIVE: u8 = 0x01;
33
pub const DETECT_BYTEMATH_FLAG_STRING: u8 = 0x02;
34
pub const DETECT_BYTEMATH_FLAG_BITMASK: u8 = 0x04;
35
pub const DETECT_BYTEMATH_FLAG_ENDIAN: u8 = 0x08;
36
pub const DETECT_BYTEMATH_FLAG_RVALUE_VAR: u8 = 0x10;
37
pub const DETECT_BYTEMATH_FLAG_NBYTES_VAR: u8 = 0x20;
38

39
// Ensure required values are provided
40
const DETECT_BYTEMATH_FLAG_NBYTES: u8 = 0x1;
41
const DETECT_BYTEMATH_FLAG_OFFSET: u8 = 0x2;
42
const DETECT_BYTEMATH_FLAG_OPER: u8 = 0x4;
43
const DETECT_BYTEMATH_FLAG_RVALUE: u8 = 0x8;
44
const DETECT_BYTEMATH_FLAG_RESULT: u8 = 0x10;
45
const DETECT_BYTEMATH_FLAG_REQUIRED: u8 = DETECT_BYTEMATH_FLAG_RESULT
46
    | DETECT_BYTEMATH_FLAG_RVALUE
47
    | DETECT_BYTEMATH_FLAG_NBYTES
48
    | DETECT_BYTEMATH_FLAG_OFFSET
49
    | DETECT_BYTEMATH_FLAG_OPER;
50

51
#[repr(u8)]
52
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53
// operators: +, -, /, *, <<, >>
54
pub enum ByteMathOperator {
55
    OperatorNone = 1,
56
    Addition = 2,
57
    Subtraction = 3,
58
    Division = 4,
59
    Multiplication = 5,
60
    LeftShift = 6,
61
    RightShift = 7,
62
}
63

64
pub const DETECT_BYTEMATH_ENDIAN_DEFAULT: ByteEndian = ByteEndian::BigEndian;
65

66
const BASE_DEFAULT: ByteBase = ByteBase::BaseDec;
67

68
// Fixed position parameter count: bytes, offset, oper, rvalue, result
69
// result is not parsed with the fixed position parameters as it's
70
// often swapped with optional parameters
71
pub const DETECT_BYTEMATH_FIXED_PARAM_COUNT: usize = 5;
72
// Optional parameters: endian, relative, string, dce, bitmask
73
pub const DETECT_BYTEMATH_MAX_PARAM_COUNT: usize = 10;
74

75
#[repr(C)]
76
#[derive(Debug)]
77
pub struct DetectByteMathData {
78
    rvalue_str: *const c_char,
79
    result: *const c_char,
80
    nbytes_str: *const c_char,
81
    rvalue: u32,
82
    offset: i32,
83
    bitmask_val: u32,
84
    bitmask_shift_count: u16,
85
    id: u16,
86
    flags: u8,
87
    local_id: u8,
88
    nbytes: u8,
89
    oper: ByteMathOperator,
90
    endian: ByteEndian, // big, little, dce
91
    base: ByteBase,     // From string or dce
92
}
93

94
impl Drop for DetectByteMathData {
95
    fn drop(&mut self) {
226✔
96
        unsafe {
226✔
97
            if !self.result.is_null() {
226✔
98
                let _ = CString::from_raw(self.result as *mut c_char);
92✔
99
            }
134✔
100
            if !self.rvalue_str.is_null() {
226✔
101
                let _ = CString::from_raw(self.rvalue_str as *mut c_char);
72✔
102
            }
154✔
103
            if !self.nbytes_str.is_null() {
226✔
104
                let _ = CString::from_raw(self.nbytes_str as *mut c_char);
4✔
105
            }
222✔
106
        }
107
    }
226✔
108
}
109

110
impl Default for DetectByteMathData {
111
    fn default() -> Self {
113✔
112
        DetectByteMathData {
113✔
113
            local_id: 0,
113✔
114
            flags: 0,
113✔
115
            nbytes: 0,
113✔
116
            offset: 0,
113✔
117
            oper: ByteMathOperator::OperatorNone,
113✔
118
            rvalue_str: std::ptr::null_mut(),
113✔
119
            nbytes_str: std::ptr::null_mut(),
113✔
120
            rvalue: 0,
113✔
121
            result: std::ptr::null_mut(),
113✔
122
            endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
113✔
123
            base: BASE_DEFAULT,
113✔
124
            bitmask_val: 0,
113✔
125
            bitmask_shift_count: 0,
113✔
126
            id: 0,
113✔
127
        }
113✔
128
    }
113✔
129
}
130

131
impl DetectByteMathData {
132
    pub fn new() -> Self {
102✔
133
        Self {
102✔
134
            ..Default::default()
102✔
135
        }
102✔
136
    }
102✔
137
}
138

139
fn get_oper_value(value: &str) -> Result<ByteMathOperator, ()> {
91✔
140
    let res = match value {
91✔
141
        "+" => ByteMathOperator::Addition,
91✔
142
        "-" => ByteMathOperator::Subtraction,
26✔
143
        "/" => ByteMathOperator::Division,
22✔
144
        "*" => ByteMathOperator::Multiplication,
20✔
145
        "<<" => ByteMathOperator::LeftShift,
12✔
146
        ">>" => ByteMathOperator::RightShift,
8✔
147
        _ => return Err(()),
6✔
148
    };
149

150
    Ok(res)
85✔
151
}
91✔
152

153
fn parse_bytemath(input: &str) -> IResult<&str, DetectByteMathData, RuleParseError<&str>> {
113✔
154
    // Inner utility function for easy error creation.
155
    fn make_error(reason: String) -> nom8::Err<RuleParseError<&'static str>> {
55✔
156
        Err::Error(RuleParseError::InvalidByteMath(reason))
55✔
157
    }
55✔
158
    let (_, values) = nom8::multi::separated_list1(
113✔
159
        tag(","),
113✔
160
        preceded(multispace0, nom8::bytes::complete::is_not(",")),
113✔
161
    ).parse(input)?;
113✔
162

163
    if values.len() < DETECT_BYTEMATH_FIXED_PARAM_COUNT
112✔
164
        || values.len() > DETECT_BYTEMATH_MAX_PARAM_COUNT
103✔
165
    {
166
        return Err(make_error(format!("Incorrect argument string; at least {} values must be specified but no more than {}: {:?}",
10✔
167
            DETECT_BYTEMATH_FIXED_PARAM_COUNT, DETECT_BYTEMATH_MAX_PARAM_COUNT, input)));
10✔
168
    }
102✔
169

102✔
170
    let mut required_flags: u8 = 0;
102✔
171
    let mut byte_math = DetectByteMathData::new();
102✔
172
    //for value in &values[0..] {
173
    for value in values {
578✔
174
        let (mut val, mut name) = take_until_whitespace(value)?;
514✔
175
        val = val.trim();
514✔
176
        name = name.trim();
514✔
177
        match name {
514✔
178
            "oper" => {
514✔
179
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OPER) {
92✔
180
                    return Err(make_error("operator already set".to_string()));
1✔
181
                }
91✔
182
                byte_math.oper = match get_oper_value(val) {
91✔
183
                    Ok(val) => val,
85✔
184
                    Err(_) => {
185
                        return Err(make_error(format!("unknown oper value {}", val)));
6✔
186
                    }
187
                };
188
                required_flags |= DETECT_BYTEMATH_FLAG_OPER;
85✔
189
            }
190
            "result" => {
422✔
191
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RESULT) {
82✔
192
                    return Err(make_error("result already set".to_string()));
1✔
193
                }
81✔
194
                let tmp: String = val
81✔
195
                    .parse()
81✔
196
                    .map_err(|_| make_error(format!("invalid result: {}", val)))?;
81✔
197
                match CString::new(tmp) {
81✔
198
                    Ok(strval) => {
81✔
199
                        byte_math.result = strval.into_raw();
81✔
200
                        required_flags |= DETECT_BYTEMATH_FLAG_RESULT;
81✔
201
                    }
81✔
202
                    _ => {
203
                        return Err(make_error(
×
204
                            "parse string not safely convertible to C".to_string(),
×
205
                        ));
×
206
                    }
207
                }
208
            }
209
            "rvalue" => {
340✔
210
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RVALUE) {
85✔
211
                    return Err(make_error("rvalue already set".to_string()));
1✔
212
                }
84✔
213
                let (_, res) = parse_var(val)?;
84✔
214
                match res {
84✔
215
                    ResultValue::Numeric(val) => {
20✔
216
                        if val >= u64::from(u32::MIN) && val <= u64::from(u32::MAX) {
20✔
217
                            byte_math.rvalue = val as u32
19✔
218
                        } else {
219
                            return Err(make_error(format!(
1✔
220
                                "invalid rvalue value: must be between {} and {}: {}",
1✔
221
                                1,
1✔
222
                                u32::MAX,
1✔
223
                                val
1✔
224
                            )));
1✔
225
                        }
226
                    }
227
                    ResultValue::String(val) => match CString::new(val) {
64✔
228
                        Ok(newval) => {
64✔
229
                            byte_math.rvalue_str = newval.into_raw();
64✔
230
                            byte_math.flags |= DETECT_BYTEMATH_FLAG_RVALUE_VAR;
64✔
231
                        }
64✔
232
                        _ => {
233
                            return Err(make_error(
×
234
                                "parse string not safely convertible to C".to_string(),
×
235
                            ))
×
236
                        }
237
                    },
238
                }
239
                required_flags |= DETECT_BYTEMATH_FLAG_RVALUE;
83✔
240
            }
241
            "endian" => {
255✔
242
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) {
17✔
243
                    return Err(make_error("endianess already set".to_string()));
1✔
244
                }
16✔
245
                if let Some(endian) = get_endian_value(val) {
16✔
246
                    byte_math.endian = endian;
11✔
247
                } else {
11✔
248
                    return Err(make_error(format!("invalid endian value: {}", val)));
5✔
249
                };
250
                byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
11✔
251
            }
252
            "dce" => {
238✔
253
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) {
5✔
254
                    return Err(make_error("endianess already set".to_string()));
1✔
255
                }
4✔
256
                byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
4✔
257
                byte_math.endian = ByteEndian::EndianDCE;
4✔
258
            }
259
            "string" => {
233✔
260
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_STRING) {
12✔
261
                    return Err(make_error("string already set".to_string()));
×
262
                }
12✔
263
                if let Some(base) = get_string_value(val) {
12✔
264
                    byte_math.base = base;
9✔
265
                } else {
9✔
266
                    return Err(make_error(format!("invalid string value: {}", val)));
3✔
267
                };
268
                byte_math.flags |= DETECT_BYTEMATH_FLAG_STRING;
9✔
269
            }
270
            "relative" => {
221✔
271
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
10✔
UNCOV
272
                    return Err(make_error("relative already set".to_string()));
×
273
                }
10✔
274
                byte_math.flags |= DETECT_BYTEMATH_FLAG_RELATIVE;
10✔
275
            }
276
            "bitmask" => {
211✔
277
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_BITMASK) {
13✔
UNCOV
278
                    return Err(make_error("bitmask already set".to_string()));
×
279
                }
13✔
280
                let trimmed = if val.starts_with("0x") || val.starts_with("0X") {
13✔
281
                    &val[2..]
7✔
282
                } else {
283
                    val
6✔
284
                };
285

286
                let val = u32::from_str_radix(trimmed, 16)
13✔
287
                    .map_err(|_| make_error(format!("invalid bitmask value: {}", value)))?;
13✔
288
                byte_math.bitmask_val = val;
5✔
289
                byte_math.flags |= DETECT_BYTEMATH_FLAG_BITMASK;
5✔
290
            }
291
            "offset" => {
198✔
292
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OFFSET) {
96✔
293
                    return Err(make_error("offset already set".to_string()));
1✔
294
                }
95✔
295
                byte_math.offset = val
95✔
296
                    .parse::<i32>()
95✔
297
                    .map_err(|_| make_error(format!("invalid offset value: {}", val)))?;
95✔
298
                if byte_math.offset > 65535 || byte_math.offset < -65535 {
95✔
299
                    return Err(make_error(format!(
3✔
300
                        "invalid offset value: must be between -65535 and 65535: {}",
3✔
301
                        val
3✔
302
                    )));
3✔
303
                }
92✔
304
                required_flags |= DETECT_BYTEMATH_FLAG_OFFSET;
92✔
305
            }
306
            "bytes" => {
102✔
307
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_NBYTES) {
102✔
308
                    return Err(make_error("nbytes already set".to_string()));
1✔
309
                }
101✔
310
                let (_, res) = parse_var(val)?;
101✔
311
                match res {
101✔
312
                    ResultValue::Numeric(val) => {
98✔
313
                        if (1..=10).contains(&val) {
98✔
314
                            byte_math.nbytes = val as u8
93✔
315
                        } else {
316
                            return Err(make_error(format!(
5✔
317
                                "invalid nbytes value: must be between 1 and 10: {}",
5✔
318
                                val
5✔
319
                            )));
5✔
320
                        }
321
                    }
322
                    ResultValue::String(val) => match CString::new(val) {
3✔
323
                        Ok(newval) => {
3✔
324
                            byte_math.nbytes_str = newval.into_raw();
3✔
325
                            byte_math.flags |= DETECT_BYTEMATH_FLAG_NBYTES_VAR;
3✔
326
                        }
3✔
327
                        _ => {
328
                            return Err(make_error(
×
329
                                "parse string not safely convertible to C".to_string(),
×
330
                            ))
×
331
                        }
332
                    },
333
                }
334
                required_flags |= DETECT_BYTEMATH_FLAG_NBYTES;
96✔
335
            }
336
            _ => {
UNCOV
337
                return Err(make_error(format!("unknown byte_math keyword: {}", name)));
×
338
            }
339
        };
340
    }
341

342
    // Ensure required values are present
343
    if (required_flags & DETECT_BYTEMATH_FLAG_REQUIRED) != DETECT_BYTEMATH_FLAG_REQUIRED {
64✔
344
        return Err(make_error(format!(
5✔
345
            "required byte_math parameters missing: \"{:?}\"",
5✔
346
            input
5✔
347
        )));
5✔
348
    }
59✔
349

59✔
350
    // Using left/right shift further restricts the value of nbytes. Note that
59✔
351
    // validation has already ensured nbytes is in [1..10]
59✔
352
    match byte_math.oper {
59✔
353
        ByteMathOperator::LeftShift | ByteMathOperator::RightShift => {
354
            if byte_math.nbytes > 4 {
6✔
355
                return Err(make_error(format!("nbytes must be 1 through 4 (inclusive) when used with \"<<\" or \">>\"; {} is not valid", byte_math.nbytes)));
2✔
356
            }
4✔
357
        }
358
        _ => {}
53✔
359
    };
360

361
    Ok((input, byte_math))
57✔
362
}
113✔
363

364
/// Intermediary function between the C code and the parsing functions.
365
#[no_mangle]
366
pub unsafe extern "C" fn SCByteMathParse(c_arg: *const c_char) -> *mut DetectByteMathData {
36✔
367
    if c_arg.is_null() {
36✔
368
        return std::ptr::null_mut();
×
369
    }
36✔
370

371
    let arg = match CStr::from_ptr(c_arg).to_str() {
36✔
372
        Ok(arg) => arg,
36✔
373
        Err(_) => {
374
            return std::ptr::null_mut();
×
375
        }
376
    };
377
    match parse_bytemath(arg) {
36✔
378
        Ok((_, detect)) => return Box::into_raw(Box::new(detect)),
30✔
379
        Err(_) => return std::ptr::null_mut(),
6✔
380
    }
381
}
36✔
382

383
#[no_mangle]
384
pub unsafe extern "C" fn SCByteMathFree(ptr: *mut DetectByteMathData) {
30✔
385
    if !ptr.is_null() {
30✔
386
        let _ = Box::from_raw(ptr);
30✔
387
    }
30✔
388
}
30✔
389

390
#[cfg(test)]
391
mod tests {
392
    use super::*;
393
    // structure equality only used by test cases
394
    impl PartialEq for DetectByteMathData {
395
        fn eq(&self, other: &Self) -> bool {
27✔
396
            let mut res: bool = false;
27✔
397

27✔
398
            if !self.rvalue_str.is_null() && !other.rvalue_str.is_null() {
27✔
399
                let s_val = unsafe { CStr::from_ptr(self.rvalue_str) };
22✔
400
                let o_val = unsafe { CStr::from_ptr(other.rvalue_str) };
22✔
401
                res = s_val == o_val;
22✔
402
            } else if !self.rvalue_str.is_null() || !other.rvalue_str.is_null() {
22✔
403
                return false;
404
            }
5✔
405

406
            if !self.nbytes_str.is_null() && !other.nbytes_str.is_null() {
27✔
407
                let s_val = unsafe { CStr::from_ptr(self.nbytes_str) };
1✔
408
                let o_val = unsafe { CStr::from_ptr(other.nbytes_str) };
1✔
409
                res = s_val == o_val;
1✔
410
            } else if !self.nbytes_str.is_null() || !other.nbytes_str.is_null() {
26✔
411
                return false;
412
            }
26✔
413

414
            if !self.result.is_null() && !self.result.is_null() {
27✔
415
                let s_val = unsafe { CStr::from_ptr(self.result) };
27✔
416
                let o_val = unsafe { CStr::from_ptr(other.result) };
27✔
417
                res = s_val == o_val;
27✔
418
            } else if !self.result.is_null() || !self.result.is_null() {
27✔
419
                return false;
420
            }
421

422
            res && self.local_id == other.local_id
27✔
423
                && self.flags == other.flags
27✔
424
                && self.nbytes == other.nbytes
27✔
425
                && self.offset == other.offset
27✔
426
                && self.oper == other.oper
27✔
427
                && self.rvalue == other.rvalue
27✔
428
                && self.endian == other.endian
27✔
429
                && self.base == other.base
27✔
430
                && self.bitmask_val == other.bitmask_val
27✔
431
                && self.bitmask_shift_count == other.bitmask_shift_count
27✔
432
                && self.id == other.id
27✔
433
        }
27✔
434
    }
435

436
    fn valid_test(
5✔
437
        args: &str, nbytes: u8, offset: i32, oper: ByteMathOperator, rvalue_str: &str,
5✔
438
        nbytes_str: &str, rvalue: u32, result: &str, base: ByteBase, endian: ByteEndian,
5✔
439
        bitmask_val: u32, flags: u8,
5✔
440
    ) {
5✔
441
        let bmd = DetectByteMathData {
5✔
442
            nbytes,
5✔
443
            offset,
5✔
444
            oper,
5✔
445
            rvalue_str: if !rvalue_str.is_empty() {
5✔
446
                CString::new(rvalue_str).unwrap().into_raw()
3✔
447
            } else {
448
                std::ptr::null_mut()
2✔
449
            },
450
            nbytes_str: if !nbytes_str.is_empty() {
5✔
451
                CString::new(nbytes_str).unwrap().into_raw()
1✔
452
            } else {
453
                std::ptr::null_mut()
4✔
454
            },
455
            rvalue,
5✔
456
            result: CString::new(result).unwrap().into_raw(),
5✔
457
            base,
5✔
458
            endian,
5✔
459
            bitmask_val,
5✔
460
            flags,
5✔
461
            ..Default::default()
5✔
462
        };
5✔
463

5✔
464
        let (_, val) = parse_bytemath(args).unwrap();
5✔
465
        assert_eq!(val, bmd);
5✔
466
    }
5✔
467

468
    #[test]
469
    fn test_parser_valid() {
1✔
470
        valid_test(
1✔
471
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result myresult, dce, string dec",
1✔
472
            4,
1✔
473
            3933,
1✔
474
            ByteMathOperator::Addition,
1✔
475
            "myrvalue",
1✔
476
            "",
1✔
477
            0,
1✔
478
            "myresult",
1✔
479
            ByteBase::BaseDec,
1✔
480
            ByteEndian::EndianDCE,
1✔
481
            0,
1✔
482
            DETECT_BYTEMATH_FLAG_RVALUE_VAR
1✔
483
                | DETECT_BYTEMATH_FLAG_STRING
1✔
484
                | DETECT_BYTEMATH_FLAG_ENDIAN,
1✔
485
        );
1✔
486

1✔
487
        valid_test(
1✔
488
            "bytes 4, offset 3933, oper +, rvalue 99, result other, dce, string dec",
1✔
489
            4,
1✔
490
            3933,
1✔
491
            ByteMathOperator::Addition,
1✔
492
            "",
1✔
493
            "",
1✔
494
            99,
1✔
495
            "other",
1✔
496
            ByteBase::BaseDec,
1✔
497
            ByteEndian::EndianDCE,
1✔
498
            0,
1✔
499
            DETECT_BYTEMATH_FLAG_STRING | DETECT_BYTEMATH_FLAG_ENDIAN,
1✔
500
        );
1✔
501

1✔
502
        valid_test(
1✔
503
            "bytes 4, offset -3933, oper +, rvalue myrvalue, result foo",
1✔
504
            4,
1✔
505
            -3933,
1✔
506
            ByteMathOperator::Addition,
1✔
507
            "rvalue",
1✔
508
            "",
1✔
509
            0,
1✔
510
            "foo",
1✔
511
            BASE_DEFAULT,
1✔
512
            ByteEndian::BigEndian,
1✔
513
            0,
1✔
514
            DETECT_BYTEMATH_FLAG_RVALUE_VAR,
1✔
515
        );
1✔
516

1✔
517
        valid_test(
1✔
518
            "bytes nbytes_var, offset -3933, oper +, rvalue myrvalue, result foo",
1✔
519
            0,
1✔
520
            -3933,
1✔
521
            ByteMathOperator::Addition,
1✔
522
            "rvalue",
1✔
523
            "nbytes_var",
1✔
524
            0,
1✔
525
            "foo",
1✔
526
            BASE_DEFAULT,
1✔
527
            ByteEndian::BigEndian,
1✔
528
            0,
1✔
529
            DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_NBYTES_VAR,
1✔
530
        );
1✔
531

1✔
532
        // Out of order
1✔
533
        valid_test(
1✔
534
            "string dec, endian big, result other, rvalue 99, oper +, offset 3933, bytes 4",
1✔
535
            4,
1✔
536
            3933,
1✔
537
            ByteMathOperator::Addition,
1✔
538
            "",
1✔
539
            "",
1✔
540
            99,
1✔
541
            "other",
1✔
542
            ByteBase::BaseDec,
1✔
543
            ByteEndian::BigEndian,
1✔
544
            0,
1✔
545
            DETECT_BYTEMATH_FLAG_STRING | DETECT_BYTEMATH_FLAG_ENDIAN,
1✔
546
        );
1✔
547
    }
1✔
548

549
    #[test]
550
    fn test_parser_string_valid() {
1✔
551
        let mut bmd = DetectByteMathData {
1✔
552
            nbytes: 4,
1✔
553
            offset: 3933,
1✔
554
            oper: ByteMathOperator::Addition,
1✔
555
            rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
1✔
556
            rvalue: 0,
1✔
557
            result: CString::new("foo").unwrap().into_raw(),
1✔
558
            endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
1✔
559
            base: ByteBase::BaseDec,
1✔
560
            flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_STRING,
1✔
561
            ..Default::default()
1✔
562
        };
1✔
563

1✔
564
        let (_, val) =
1✔
565
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string dec")
1✔
566
                .unwrap();
1✔
567
        assert_eq!(val, bmd);
1✔
568

569
        bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR;
1✔
570
        bmd.base = BASE_DEFAULT;
1✔
571
        let (_, val) =
1✔
572
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo").unwrap();
1✔
573
        assert_eq!(val, bmd);
1✔
574

575
        bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_STRING;
1✔
576
        bmd.base = ByteBase::BaseHex;
1✔
577
        let (_, val) =
1✔
578
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hex")
1✔
579
                .unwrap();
1✔
580
        assert_eq!(val, bmd);
1✔
581

582
        bmd.base = ByteBase::BaseOct;
1✔
583
        let (_, val) =
1✔
584
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string oct")
1✔
585
                .unwrap();
1✔
586
        assert_eq!(val, bmd);
1✔
587
    }
1✔
588

589
    #[test]
590
    fn test_parser_string_invalid() {
1✔
591
        assert!(parse_bytemath(
1✔
592
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string decimal"
1✔
593
        )
1✔
594
        .is_err());
1✔
595
        assert!(parse_bytemath(
1✔
596
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hexadecimal"
1✔
597
        )
1✔
598
        .is_err());
1✔
599
        assert!(parse_bytemath(
1✔
600
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string octal"
1✔
601
        )
1✔
602
        .is_err());
1✔
603
    }
1✔
604

605
    #[test]
606
    // bytes must be between 1 and 10; when combined with rshift/lshift, must be 4 or less
607
    fn test_parser_bytes_invalid() {
1✔
608
        assert!(
1✔
609
            parse_bytemath("bytes 0, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
1✔
610
        );
1✔
611
        assert!(
1✔
612
            parse_bytemath("bytes 11, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
1✔
613
        );
1✔
614
        assert!(
1✔
615
            parse_bytemath("bytes 5, offset 3933, oper >>, rvalue myrvalue, result foo").is_err()
1✔
616
        );
1✔
617
        assert!(
1✔
618
            parse_bytemath("bytes 5, offset 3933, oper <<, rvalue myrvalue, result foo").is_err()
1✔
619
        );
1✔
620
    }
1✔
621

622
    #[test]
623
    fn test_parser_bitmask_invalid() {
1✔
624
        assert!(parse_bytemath(
1✔
625
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x"
1✔
626
        )
1✔
627
        .is_err());
1✔
628
        assert!(parse_bytemath(
1✔
629
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask x12345678"
1✔
630
        )
1✔
631
        .is_err());
1✔
632
        assert!(parse_bytemath(
1✔
633
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask X12345678"
1✔
634
        )
1✔
635
        .is_err());
1✔
636
        assert!(parse_bytemath(
1✔
637
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x123456789012"
1✔
638
        )
1✔
639
        .is_err());
1✔
640
        assert!(parse_bytemath(
1✔
641
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0q"
1✔
642
        )
1✔
643
        .is_err());
1✔
644
        assert!(parse_bytemath(
1✔
645
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask maple"
1✔
646
        )
1✔
647
        .is_err());
1✔
648
        assert!(parse_bytemath(
1✔
649
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0xGHIJKLMN"
1✔
650
        )
1✔
651
        .is_err());
1✔
652
        assert!(parse_bytemath(
1✔
653
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask #*#*@-"
1✔
654
        )
1✔
655
        .is_err());
1✔
656
    }
1✔
657

658
    #[test]
659
    fn test_parser_bitmask_valid() {
1✔
660
        let mut bmd = DetectByteMathData {
1✔
661
            nbytes: 4,
1✔
662
            offset: 3933,
1✔
663
            oper: ByteMathOperator::Addition,
1✔
664
            rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
1✔
665
            rvalue: 0,
1✔
666
            result: CString::new("foo").unwrap().into_raw(),
1✔
667
            endian: ByteEndian::BigEndian,
1✔
668
            base: BASE_DEFAULT,
1✔
669
            flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_BITMASK,
1✔
670
            ..Default::default()
1✔
671
        };
1✔
672

1✔
673
        bmd.bitmask_val = 0x12345678;
1✔
674
        let (_, val) = parse_bytemath(
1✔
675
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x12345678",
1✔
676
        )
1✔
677
        .unwrap();
1✔
678
        assert_eq!(val, bmd);
1✔
679

680
        bmd.bitmask_val = 0xffff1234;
1✔
681
        let (_, val) = parse_bytemath(
1✔
682
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask ffff1234",
1✔
683
        )
1✔
684
        .unwrap();
1✔
685
        assert_eq!(val, bmd);
1✔
686

687
        bmd.bitmask_val = 0xffff1234;
1✔
688
        let (_, val) = parse_bytemath(
1✔
689
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0Xffff1234",
1✔
690
        )
1✔
691
        .unwrap();
1✔
692
        assert_eq!(val, bmd);
1✔
693
    }
1✔
694
    #[test]
695
    fn test_parser_endian_valid() {
1✔
696
        let mut bmd = DetectByteMathData {
1✔
697
            nbytes: 4,
1✔
698
            offset: 3933,
1✔
699
            oper: ByteMathOperator::Addition,
1✔
700
            rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
1✔
701
            rvalue: 0,
1✔
702
            result: CString::new("foo").unwrap().into_raw(),
1✔
703
            endian: ByteEndian::BigEndian,
1✔
704
            base: BASE_DEFAULT,
1✔
705
            flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_ENDIAN,
1✔
706
            ..Default::default()
1✔
707
        };
1✔
708

1✔
709
        let (_, val) =
1✔
710
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian big")
1✔
711
                .unwrap();
1✔
712
        assert_eq!(val, bmd);
1✔
713

714
        bmd.endian = ByteEndian::LittleEndian;
1✔
715
        let (_, val) = parse_bytemath(
1✔
716
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian little",
1✔
717
        )
1✔
718
        .unwrap();
1✔
719
        assert_eq!(val, bmd);
1✔
720

721
        bmd.endian = ByteEndian::EndianDCE;
1✔
722
        let (_, val) =
1✔
723
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, dce")
1✔
724
                .unwrap();
1✔
725
        assert_eq!(val, bmd);
1✔
726

727
        bmd.endian = DETECT_BYTEMATH_ENDIAN_DEFAULT;
1✔
728
        bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR;
1✔
729
        let (_, val) =
1✔
730
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo").unwrap();
1✔
731
        assert_eq!(val, bmd);
1✔
732
    }
1✔
733

734
    #[test]
735
    fn test_parser_endian_invalid() {
1✔
736
        assert!(parse_bytemath(
1✔
737
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian bigger"
1✔
738
        )
1✔
739
        .is_err());
1✔
740
        assert!(parse_bytemath(
1✔
741
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian smaller"
1✔
742
        )
1✔
743
        .is_err());
1✔
744

745
        // endianess can only be specified once
746
        assert!(parse_bytemath(
1✔
747
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian big, dce"
1✔
748
        )
1✔
749
        .is_err());
1✔
750
        assert!(parse_bytemath(
1✔
751
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian small, endian big"
1✔
752
        )
1✔
753
        .is_err());
1✔
754
        assert!(parse_bytemath(
1✔
755
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian small, dce"
1✔
756
        )
1✔
757
        .is_err());
1✔
758
    }
1✔
759

760
    #[test]
761
    fn test_parser_oper_valid() {
1✔
762
        let mut bmd = DetectByteMathData {
1✔
763
            nbytes: 4,
1✔
764
            offset: 3933,
1✔
765
            oper: ByteMathOperator::Addition,
1✔
766
            rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
1✔
767
            rvalue: 0,
1✔
768
            result: CString::new("foo").unwrap().into_raw(),
1✔
769
            endian: ByteEndian::BigEndian,
1✔
770
            base: BASE_DEFAULT,
1✔
771
            flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR,
1✔
772
            ..Default::default()
1✔
773
        };
1✔
774

1✔
775
        let (_, val) =
1✔
776
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo").unwrap();
1✔
777
        assert_eq!(val, bmd);
1✔
778

779
        bmd.oper = ByteMathOperator::Subtraction;
1✔
780
        let (_, val) =
1✔
781
            parse_bytemath("bytes 4, offset 3933, oper -, rvalue myrvalue, result foo").unwrap();
1✔
782
        assert_eq!(val, bmd);
1✔
783

784
        bmd.oper = ByteMathOperator::Multiplication;
1✔
785
        let (_, val) =
1✔
786
            parse_bytemath("bytes 4, offset 3933, oper *, rvalue myrvalue, result foo").unwrap();
1✔
787
        assert_eq!(val, bmd);
1✔
788

789
        bmd.oper = ByteMathOperator::Division;
1✔
790
        let (_, val) =
1✔
791
            parse_bytemath("bytes 4, offset 3933, oper /, rvalue myrvalue, result foo").unwrap();
1✔
792
        assert_eq!(val, bmd);
1✔
793

794
        bmd.oper = ByteMathOperator::RightShift;
1✔
795
        let (_, val) =
1✔
796
            parse_bytemath("bytes 4, offset 3933, oper >>, rvalue myrvalue, result foo").unwrap();
1✔
797
        assert_eq!(val, bmd);
1✔
798

799
        bmd.oper = ByteMathOperator::LeftShift;
1✔
800
        let (_, val) =
1✔
801
            parse_bytemath("bytes 4, offset 3933, oper <<, rvalue myrvalue, result foo").unwrap();
1✔
802
        assert_eq!(val, bmd);
1✔
803
    }
1✔
804

805
    #[test]
806
    fn test_parser_oper_invalid() {
1✔
807
        assert!(parse_bytemath("bytes 4, offset 0, oper !, rvalue myvalue, result foo").is_err());
1✔
808
        assert!(parse_bytemath("bytes 4, offset 0, oper ^, rvalue myvalue, result foo").is_err());
1✔
809
        assert!(parse_bytemath("bytes 4, offset 0, oper <>, rvalue myvalue, result foo").is_err());
1✔
810
        assert!(parse_bytemath("bytes 4, offset 0, oper ><, rvalue myvalue, result foo").is_err());
1✔
811
        assert!(parse_bytemath("bytes 4, offset 0, oper <, rvalue myvalue, result foo").is_err());
1✔
812
        assert!(parse_bytemath("bytes 4, offset 0, oper >, rvalue myvalue, result foo").is_err());
1✔
813
    }
1✔
814

815
    #[test]
816
    fn test_parser_rvalue_valid() {
1✔
817
        let mut bmd = DetectByteMathData {
1✔
818
            nbytes: 4,
1✔
819
            offset: 47303,
1✔
820
            oper: ByteMathOperator::Multiplication,
1✔
821
            rvalue_str: std::ptr::null_mut(),
1✔
822
            rvalue: 4294967295,
1✔
823
            result: CString::new("foo").unwrap().into_raw(),
1✔
824
            endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
1✔
825
            base: BASE_DEFAULT,
1✔
826
            ..Default::default()
1✔
827
        };
1✔
828

1✔
829
        let (_, val) =
1✔
830
            parse_bytemath("bytes 4, offset 47303, oper *, rvalue 4294967295      , result foo")
1✔
831
                .unwrap();
1✔
832
        assert_eq!(val, bmd);
1✔
833

834
        bmd.rvalue = 1;
1✔
835
        let (_, val) =
1✔
836
            parse_bytemath("bytes 4, offset 47303, oper *, rvalue 1, result foo").unwrap();
1✔
837
        assert_eq!(val, bmd);
1✔
838

839
        bmd.rvalue = 0;
1✔
840
        let (_, val) =
1✔
841
            parse_bytemath("bytes 4, offset 47303, oper *, rvalue 0, result foo").unwrap();
1✔
842
        assert_eq!(val, bmd);
1✔
843
    }
1✔
844

845
    #[test]
846
    fn test_parser_rvalue_invalid() {
1✔
847
        assert!(
1✔
848
            parse_bytemath("bytes 4, offset 47303, oper *, rvalue 4294967296, result foo").is_err()
1✔
849
        );
1✔
850
    }
1✔
851

852
    #[test]
853
    fn test_parser_offset_valid() {
1✔
854
        let mut bmd = DetectByteMathData {
1✔
855
            nbytes: 4,
1✔
856
            offset: -65535,
1✔
857
            oper: ByteMathOperator::Multiplication,
1✔
858
            rvalue_str: CString::new("myrvalue").unwrap().into_raw(),
1✔
859
            rvalue: 0,
1✔
860
            result: CString::new("foo").unwrap().into_raw(),
1✔
861
            endian: DETECT_BYTEMATH_ENDIAN_DEFAULT,
1✔
862
            base: BASE_DEFAULT,
1✔
863
            flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR,
1✔
864
            ..Default::default()
1✔
865
        };
1✔
866

1✔
867
        let (_, val) =
1✔
868
            parse_bytemath("bytes 4, offset -65535, oper *, rvalue myrvalue, result foo").unwrap();
1✔
869
        assert_eq!(val, bmd);
1✔
870

871
        bmd.offset = 65535;
1✔
872
        let (_, val) =
1✔
873
            parse_bytemath("bytes 4, offset 65535, oper *, rvalue myrvalue, result foo").unwrap();
1✔
874
        assert_eq!(val, bmd);
1✔
875
    }
1✔
876

877
    #[test]
878
    // offset: numeric values must be between -65535 and 65535
879
    fn test_parser_offset_invalid() {
1✔
880
        assert!(
1✔
881
            parse_bytemath("bytes 4, offset -70000, oper *, rvalue myvalue, result foo").is_err()
1✔
882
        );
1✔
883
        assert!(
1✔
884
            parse_bytemath("bytes 4, offset 70000, oper +, rvalue myvalue, result foo").is_err()
1✔
885
        );
1✔
886
    }
1✔
887

888
    #[test]
889
    fn test_parser_incomplete_args() {
1✔
890
        assert!(parse_bytemath("").is_err());
1✔
891
        assert!(parse_bytemath("bytes 4").is_err());
1✔
892
        assert!(parse_bytemath("bytes 4, offset 0").is_err());
1✔
893
        assert!(parse_bytemath("bytes 4, offset 0, oper <<").is_err());
1✔
894
    }
1✔
895

896
    #[test]
897
    fn test_parser_missing_required() {
1✔
898
        assert!(
1✔
899
            parse_bytemath("endian big, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
1✔
900
        );
1✔
901
        assert!(
1✔
902
            parse_bytemath("bytes 4, endian big, oper +, rvalue myrvalue, result foo,").is_err()
1✔
903
        );
1✔
904
        assert!(
1✔
905
            parse_bytemath("bytes 4, offset 3933, endian big, rvalue myrvalue, result foo")
1✔
906
                .is_err()
1✔
907
        );
1✔
908
        assert!(parse_bytemath("bytes 4, offset 3933, oper +, endian big, result foo").is_err());
1✔
909
        assert!(
1✔
910
            parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, endian big").is_err()
1✔
911
        );
1✔
912
    }
1✔
913

914
    #[test]
915
    fn test_parser_invalid_args() {
1✔
916
        assert!(parse_bytemath("monkey banana").is_err());
1✔
917
        assert!(parse_bytemath("bytes nan").is_err());
1✔
918
        assert!(parse_bytemath("bytes 4, offset nan").is_err());
1✔
919
        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());
1✔
920
        assert!(parse_bytemath("bytes 4, offset 0, oper ><, rvalue myrvalue").is_err());
1✔
921
        assert!(
1✔
922
            parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, endian endian").is_err()
1✔
923
        );
1✔
924
    }
1✔
925
    #[test]
926
    fn test_parser_multiple() {
1✔
927
        assert!(parse_bytemath(
1✔
928
            "bytes 4, bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big"
1✔
929
        )
1✔
930
        .is_err());
1✔
931
        assert!(parse_bytemath(
1✔
932
            "bytes 4, offset 0, offset 0, oper +, rvalue myrvalue, result myresult, endian big"
1✔
933
        )
1✔
934
        .is_err());
1✔
935
        assert!(parse_bytemath(
1✔
936
            "bytes 4, offset 0, oper +, oper +, rvalue myrvalue, result myresult, endian big"
1✔
937
        )
1✔
938
        .is_err());
1✔
939
        assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, rvalue myrvalue, result myresult, endian big").is_err());
1✔
940
        assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, result myresult, endian big").is_err());
1✔
941
        assert!(parse_bytemath(
1✔
942
            "bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big, endian big"
1✔
943
        )
1✔
944
        .is_err());
1✔
945
    }
1✔
946
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc