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

OISF / suricata / 23374838686

21 Mar 2026 07:29AM UTC coverage: 59.341% (-20.0%) from 79.315%
23374838686

Pull #15075

github

web-flow
Merge 90b4e834f into 6587e363a
Pull Request #15075: Stack 8001 v16.4

38 of 70 new or added lines in 10 files covered. (54.29%)

34165 existing lines in 563 files now uncovered.

119621 of 201584 relevant lines covered (59.34%)

650666.92 hits per line

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

92.83
/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) {
27,506✔
96
        unsafe {
27,506✔
97
            if !self.result.is_null() {
27,506✔
98
                let _ = CString::from_raw(self.result as *mut c_char);
10,057✔
99
            }
17,449✔
100
            if !self.rvalue_str.is_null() {
27,506✔
101
                let _ = CString::from_raw(self.rvalue_str as *mut c_char);
7,803✔
102
            }
19,703✔
103
            if !self.nbytes_str.is_null() {
27,506✔
104
                let _ = CString::from_raw(self.nbytes_str as *mut c_char);
614✔
105
            }
26,892✔
106
        }
107
    }
27,506✔
108
}
109

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

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

139
fn get_oper_value(value: &str) -> Result<ByteMathOperator, ()> {
12,346✔
140
    let res = match value {
12,346✔
141
        "+" => ByteMathOperator::Addition,
12,346✔
142
        "-" => ByteMathOperator::Subtraction,
11,165✔
143
        "/" => ByteMathOperator::Division,
3,723✔
144
        "*" => ByteMathOperator::Multiplication,
3,700✔
145
        "<<" => ByteMathOperator::LeftShift,
3,700✔
146
        ">>" => ByteMathOperator::RightShift,
347✔
147
        _ => return Err(()),
28✔
148
    };
149

150
    Ok(res)
12,318✔
151
}
12,346✔
152

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

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

13,753✔
170
    let mut required_flags: u8 = 0;
13,753✔
171
    let mut byte_math = DetectByteMathData::new();
13,753✔
172
    //for value in &values[0..] {
173
    for value in values {
76,282✔
174
        let (mut val, mut name) = take_until_whitespace(value)?;
66,452✔
175
        val = val.trim();
66,452✔
176
        name = name.trim();
66,452✔
177
        match name {
66,452✔
178
            "oper" => {
66,452✔
179
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OPER) {
12,400✔
180
                    return Err(make_error("operator already set".to_string()));
54✔
181
                }
12,346✔
182
                byte_math.oper = match get_oper_value(val) {
12,346✔
183
                    Ok(val) => val,
12,318✔
184
                    Err(_) => {
185
                        return Err(make_error(format!("unknown oper value {}", val)));
28✔
186
                    }
187
                };
188
                required_flags |= DETECT_BYTEMATH_FLAG_OPER;
12,318✔
189
            }
190
            "result" => {
54,052✔
191
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RESULT) {
10,064✔
192
                    return Err(make_error("result already set".to_string()));
7✔
193
                }
10,057✔
194
                let tmp: String = val
10,057✔
195
                    .parse()
10,057✔
196
                    .map_err(|_| make_error(format!("invalid result: {}", val)))?;
10,057✔
197
                match CString::new(tmp) {
10,057✔
198
                    Ok(strval) => {
10,057✔
199
                        byte_math.result = strval.into_raw();
10,057✔
200
                        required_flags |= DETECT_BYTEMATH_FLAG_RESULT;
10,057✔
201
                    }
10,057✔
202
                    _ => {
203
                        return Err(make_error(
×
204
                            "parse string not safely convertible to C".to_string(),
×
205
                        ));
×
206
                    }
207
                }
208
            }
209
            "rvalue" => {
43,988✔
210
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RVALUE) {
12,113✔
211
                    return Err(make_error("rvalue already set".to_string()));
2✔
212
                }
12,111✔
213
                let (_, res) = parse_var(val)?;
12,111✔
214
                match res {
11,991✔
215
                    ResultValue::Numeric(val) => {
4,188✔
216
                        if val >= u64::from(u32::MIN) && val <= u64::from(u32::MAX) {
4,188✔
217
                            byte_math.rvalue = val as u32
4,173✔
218
                        } else {
219
                            return Err(make_error(format!(
15✔
220
                                "invalid rvalue value: must be between {} and {}: {}",
15✔
221
                                1,
15✔
222
                                u32::MAX,
15✔
223
                                val
15✔
224
                            )));
15✔
225
                        }
226
                    }
227
                    ResultValue::String(val) => match CString::new(val) {
7,803✔
228
                        Ok(newval) => {
7,803✔
229
                            byte_math.rvalue_str = newval.into_raw();
7,803✔
230
                            byte_math.flags |= DETECT_BYTEMATH_FLAG_RVALUE_VAR;
7,803✔
231
                        }
7,803✔
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;
11,976✔
240
            }
241
            "endian" => {
31,875✔
242
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) {
3✔
UNCOV
243
                    return Err(make_error("endianess already set".to_string()));
×
244
                }
3✔
245
                if let Some(endian) = get_endian_value(val) {
3✔
UNCOV
246
                    byte_math.endian = endian;
×
UNCOV
247
                } else {
×
248
                    return Err(make_error(format!("invalid endian value: {}", val)));
3✔
249
                };
UNCOV
250
                byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
×
251
            }
252
            "dce" => {
31,872✔
253
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) {
89✔
254
                    return Err(make_error("endianess already set".to_string()));
1✔
255
                }
88✔
256
                byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
88✔
257
                byte_math.endian = ByteEndian::EndianDCE;
88✔
258
            }
259
            "string" => {
31,783✔
260
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_STRING) {
7✔
261
                    return Err(make_error("string already set".to_string()));
×
262
                }
7✔
263
                if let Some(base) = get_string_value(val) {
7✔
264
                    byte_math.base = base;
2✔
265
                } else {
2✔
266
                    return Err(make_error(format!("invalid string value: {}", val)));
5✔
267
                };
268
                byte_math.flags |= DETECT_BYTEMATH_FLAG_STRING;
2✔
269
            }
270
            "relative" => {
31,776✔
271
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
1,719✔
272
                    return Err(make_error("relative already set".to_string()));
1✔
273
                }
1,718✔
274
                byte_math.flags |= DETECT_BYTEMATH_FLAG_RELATIVE;
1,718✔
275
            }
276
            "bitmask" => {
30,057✔
277
                if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_BITMASK) {
19✔
278
                    return Err(make_error("bitmask already set".to_string()));
1✔
279
                }
18✔
280
                let trimmed = if val.starts_with("0x") || val.starts_with("0X") {
18✔
281
                    &val[2..]
3✔
282
                } else {
283
                    val
15✔
284
                };
285

286
                let val = u32::from_str_radix(trimmed, 16)
18✔
287
                    .map_err(|_| make_error(format!("invalid bitmask value: {}", value)))?;
18✔
288
                byte_math.bitmask_val = val;
2✔
289
                byte_math.flags |= DETECT_BYTEMATH_FLAG_BITMASK;
2✔
290
            }
291
            "offset" => {
30,038✔
292
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OFFSET) {
14,567✔
293
                    return Err(make_error("offset already set".to_string()));
1,155✔
294
                }
13,412✔
295
                byte_math.offset = val
13,412✔
296
                    .parse::<i32>()
13,412✔
297
                    .map_err(|_| make_error(format!("invalid offset value: {}", val)))?;
13,412✔
298
                if byte_math.offset > 65535 || byte_math.offset < -65535 {
13,093✔
299
                    return Err(make_error(format!(
211✔
300
                        "invalid offset value: must be between -65535 and 65535: {}",
211✔
301
                        val
211✔
302
                    )));
211✔
303
                }
12,882✔
304
                required_flags |= DETECT_BYTEMATH_FLAG_OFFSET;
12,882✔
305
            }
306
            "bytes" => {
15,471✔
307
                if 0 != (required_flags & DETECT_BYTEMATH_FLAG_NBYTES) {
13,687✔
308
                    return Err(make_error("nbytes already set".to_string()));
11✔
309
                }
13,676✔
310
                let (_, res) = parse_var(val)?;
13,676✔
311
                match res {
13,670✔
312
                    ResultValue::Numeric(val) => {
13,056✔
313
                        if (1..=10).contains(&val) {
13,056✔
314
                            byte_math.nbytes = val as u8
12,872✔
315
                        } else {
316
                            return Err(make_error(format!(
184✔
317
                                "invalid nbytes value: must be between 1 and 10: {}",
184✔
318
                                val
184✔
319
                            )));
184✔
320
                        }
321
                    }
322
                    ResultValue::String(val) => match CString::new(val) {
614✔
323
                        Ok(newval) => {
614✔
324
                            byte_math.nbytes_str = newval.into_raw();
614✔
325
                            byte_math.flags |= DETECT_BYTEMATH_FLAG_NBYTES_VAR;
614✔
326
                        }
614✔
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;
13,486✔
335
            }
336
            _ => {
337
                return Err(make_error(format!("unknown byte_math keyword: {}", name)));
1,784✔
338
            }
339
        };
340
    }
341

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

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

361
    Ok((input, byte_math))
9,802✔
362
}
16,766✔
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 {
16,766✔
367
    if c_arg.is_null() {
16,766✔
368
        return std::ptr::null_mut();
×
369
    }
16,766✔
370

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

383
#[no_mangle]
384
pub unsafe extern "C" fn SCByteMathFree(ptr: *mut DetectByteMathData) {
16,766✔
385
    if !ptr.is_null() {
16,766✔
386
        let _ = Box::from_raw(ptr);
9,802✔
387
    }
9,802✔
388
}
16,766✔
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 {
396
            let mut res: bool = false;
397

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

589
    #[test]
590
    fn test_parser_string_invalid() {
591
        assert!(parse_bytemath(
592
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string decimal"
593
        )
594
        .is_err());
595
        assert!(parse_bytemath(
596
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hexadecimal"
597
        )
598
        .is_err());
599
        assert!(parse_bytemath(
600
            "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string octal"
601
        )
602
        .is_err());
603
    }
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() {
608
        assert!(
609
            parse_bytemath("bytes 0, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
610
        );
611
        assert!(
612
            parse_bytemath("bytes 11, offset 3933, oper +, rvalue myrvalue, result foo").is_err()
613
        );
614
        assert!(
615
            parse_bytemath("bytes 5, offset 3933, oper >>, rvalue myrvalue, result foo").is_err()
616
        );
617
        assert!(
618
            parse_bytemath("bytes 5, offset 3933, oper <<, rvalue myrvalue, result foo").is_err()
619
        );
620
    }
621

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

914
    #[test]
915
    fn test_parser_invalid_args() {
916
        assert!(parse_bytemath("monkey banana").is_err());
917
        assert!(parse_bytemath("bytes nan").is_err());
918
        assert!(parse_bytemath("bytes 4, offset nan").is_err());
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());
920
        assert!(parse_bytemath("bytes 4, offset 0, oper ><, rvalue myrvalue").is_err());
921
        assert!(
922
            parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, endian endian").is_err()
923
        );
924
    }
925
    #[test]
926
    fn test_parser_multiple() {
927
        assert!(parse_bytemath(
928
            "bytes 4, bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big"
929
        )
930
        .is_err());
931
        assert!(parse_bytemath(
932
            "bytes 4, offset 0, offset 0, oper +, rvalue myrvalue, result myresult, endian big"
933
        )
934
        .is_err());
935
        assert!(parse_bytemath(
936
            "bytes 4, offset 0, oper +, oper +, rvalue myrvalue, result myresult, endian big"
937
        )
938
        .is_err());
939
        assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, rvalue myrvalue, result myresult, endian big").is_err());
940
        assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, result myresult, endian big").is_err());
941
        assert!(parse_bytemath(
942
            "bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big, endian big"
943
        )
944
        .is_err());
945
    }
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