Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/rust/src/detect/float.rs
Line
Count
Source
1
/* Copyright (C) 2025 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
use nom7::{
19
    branch::alt,
20
    bytes::complete::{is_a, tag, tag_no_case, take_while},
21
    character::complete::{char, digit1},
22
    combinator::{all_consuming, map, map_opt, opt, recognize, value, verify},
23
    error::{make_error, ErrorKind},
24
    sequence::tuple,
25
    Err, IResult,
26
};
27
28
use num::traits::float::FloatCore;
29
use num::traits::{FromPrimitive, ToPrimitive};
30
use num::Bounded;
31
32
use std::ffi::CStr;
33
34
#[derive(PartialEq, Eq, Clone, Debug)]
35
#[repr(u8)]
36
pub enum DetectFloatMode {
37
    DetectFloatModeEqual,
38
    DetectFloatModeLt,
39
    DetectFloatModeLte,
40
    DetectFloatModeGt,
41
    DetectFloatModeGte,
42
    DetectFloatModeRange,
43
    DetectFloatModeNe,
44
    DetectFloatModeNegRg,
45
}
46
47
#[derive(Debug, PartialEq)]
48
#[repr(C)]
49
pub struct DetectFloatData<T> {
50
    pub arg1: T,
51
    pub arg2: T,
52
    pub mode: DetectFloatMode,
53
}
54
55
impl<T: Default> Default for DetectFloatData<T> {
56
224
    fn default() -> Self {
57
224
        Self {
58
224
            arg1: T::default(),
59
224
            arg2: T::default(),
60
224
            mode: DetectFloatMode::DetectFloatModeEqual,
61
224
        }
62
224
    }
63
}
64
65
pub trait DetectFloatType:
66
    FromPrimitive + ToPrimitive + std::str::FromStr + Bounded + PartialOrd + FloatCore + Sized
67
{
68
    fn from_str(s: &str) -> Option<Self>;
69
}
70
71
impl<T> DetectFloatType for T
72
where
73
    T: FromPrimitive + ToPrimitive + std::str::FromStr + Bounded + PartialOrd + FloatCore,
74
{
75
356
    fn from_str(s: &str) -> Option<Self> {
76
356
        s.parse().ok()
77
356
    }
78
}
79
80
457
pub fn parse_float_value<T: DetectFloatType>(input: &str) -> IResult<&str, T> {
81
457
    alt((
82
        // Handle special cases first
83
457
        map(tag_no_case("NaN"), |_| {
84
3
            <T as DetectFloatType>::from_str("NaN").unwrap()
85
3
        }),
86
457
        map(tag_no_case("+inf"), |_| {
87
2
            <T as DetectFloatType>::from_str("inf").unwrap()
88
2
        }),
89
457
        map(tag_no_case("inf"), |_| {
90
0
            <T as DetectFloatType>::from_str("inf").unwrap()
91
0
        }),
92
457
        map(tag_no_case("-inf"), |_| {
93
0
            <T as DetectFloatType>::from_str("-inf").unwrap()
94
0
        }),
95
        // Handle numeric parsing, including scientific notation
96
457
        map_opt(
97
457
            recognize(tuple((
98
457
                opt(alt((tag("+"), tag("-")))), // Handle optional signs
99
457
                alt((digit1, recognize(tuple((tag("."), digit1))))), // Handle integers & `.5`
100
457
                opt(tuple((tag("."), digit1))), // Handle decimals like `5.`
101
457
                opt(tuple((
102
457
                    tag_no_case("e"),
103
457
                    opt(alt((tag("+"), tag("-")))),
104
457
                    digit1,
105
457
                ))), // Handle `1e10`, `-1e-5`
106
457
            ))),
107
351
            |float_str: &str| <T as DetectFloatType>::from_str(float_str),
108
        ),
109
457
    ))(input)
110
457
}
111
209
fn detect_parse_float_start_equal<T: DetectFloatType>(
112
209
    i: &str,
113
209
) -> IResult<&str, DetectFloatData<T>> {
114
209
    let (i, _) = opt(tag("="))(i)?;
115
209
    let (i, _) = opt(is_a(" "))(i)?;
116
209
    let (i, arg1) = parse_float_value::<T>(i)?;
117
162
    Ok((
118
162
        i,
119
162
        DetectFloatData {
120
162
            arg1,
121
162
            arg2: <T as FloatCore>::min_value(),
122
162
            mode: DetectFloatMode::DetectFloatModeEqual,
123
162
        },
124
162
    ))
125
209
}
126
127
210
pub fn detect_parse_float_start_interval<T: DetectFloatType>(
128
210
    i: &str,
129
210
) -> IResult<&str, DetectFloatData<T>> {
130
210
    let (i, neg) = opt(char('!'))(i)?;
131
210
    let (i, arg1) = parse_float_value::<T>(i)?;
132
163
    let (i, _) = opt(is_a(" "))(i)?;
133
163
    let (i, _) = alt((tag("-"), tag("<>")))(i)?;
134
4
    let (i, _) = opt(is_a(" "))(i)?;
135
4
    let (i, arg2) = verify(parse_float_value::<T>, |x| {
136
3
        *x > arg1 && *x - arg1 > <T as FloatCore>::epsilon()
137
4
    })(i)?;
138
1
    let mode = if neg.is_some() {
139
0
        DetectFloatMode::DetectFloatModeNegRg
140
    } else {
141
1
        DetectFloatMode::DetectFloatModeRange
142
    };
143
1
    Ok((i, DetectFloatData { arg1, arg2, mode }))
144
210
}
145
146
47
fn detect_parse_float_mode(i: &str) -> IResult<&str, DetectFloatMode> {
147
47
    let (i, mode) = alt((
148
47
        value(DetectFloatMode::DetectFloatModeGte, tag(">=")),
149
47
        value(DetectFloatMode::DetectFloatModeLte, tag("<=")),
150
47
        value(DetectFloatMode::DetectFloatModeGt, tag(">")),
151
47
        value(DetectFloatMode::DetectFloatModeLt, tag("<")),
152
47
        value(DetectFloatMode::DetectFloatModeNe, tag("!=")),
153
47
        value(DetectFloatMode::DetectFloatModeEqual, tag("=")),
154
47
    ))(i)?;
155
34
    Ok((i, mode))
156
47
}
157
158
47
fn detect_parse_float_start_symbol<T: DetectFloatType>(
159
47
    i: &str,
160
47
) -> IResult<&str, DetectFloatData<T>> {
161
47
    let (i, mode) = detect_parse_float_mode(i)?;
162
34
    let (i, _) = opt(is_a(" "))(i)?;
163
34
    let (i, arg1) = parse_float_value::<T>(i)?;
164
165
26
    match mode {
166
1
        DetectFloatMode::DetectFloatModeNe => {}
167
        DetectFloatMode::DetectFloatModeLt => {
168
1
            if arg1 == <T as FloatCore>::min_value() {
169
0
                return Err(Err::Error(make_error(i, ErrorKind::Verify)));
170
1
            }
171
        }
172
        DetectFloatMode::DetectFloatModeLte => {
173
5
            if arg1 == <T as FloatCore>::max_value() {
174
0
                return Err(Err::Error(make_error(i, ErrorKind::Verify)));
175
5
            }
176
        }
177
        DetectFloatMode::DetectFloatModeGt => {
178
1
            if arg1 == <T as FloatCore>::max_value() {
179
0
                return Err(Err::Error(make_error(i, ErrorKind::Verify)));
180
1
            }
181
        }
182
        DetectFloatMode::DetectFloatModeGte => {
183
18
            if arg1 == <T as FloatCore>::min_value() {
184
0
                return Err(Err::Error(make_error(i, ErrorKind::Verify)));
185
18
            }
186
        }
187
        _ => {
188
0
            return Err(Err::Error(make_error(i, ErrorKind::MapOpt)));
189
        }
190
    }
191
192
26
    Ok((
193
26
        i,
194
26
        DetectFloatData {
195
26
            arg1,
196
26
            arg2: <T as FloatCore>::min_value(),
197
26
            mode,
198
26
        },
199
26
    ))
200
47
}
201
202
68
pub fn detect_match_float<T: DetectFloatType>(x: &DetectFloatData<T>, val: T) -> bool {
203
68
    match x.mode {
204
0
        DetectFloatMode::DetectFloatModeEqual => val == x.arg1,
205
0
        DetectFloatMode::DetectFloatModeNe => val != x.arg1,
206
0
        DetectFloatMode::DetectFloatModeLt => val < x.arg1,
207
0
        DetectFloatMode::DetectFloatModeLte => val <= x.arg1,
208
0
        DetectFloatMode::DetectFloatModeGt => val > x.arg1,
209
68
        DetectFloatMode::DetectFloatModeGte => val >= x.arg1,
210
0
        DetectFloatMode::DetectFloatModeRange => val > x.arg1 && val < x.arg2,
211
0
        DetectFloatMode::DetectFloatModeNegRg => val <= x.arg1 || val >= x.arg2,
212
    }
213
68
}
214
215
210
pub fn detect_parse_float<T: DetectFloatType>(i: &str) -> IResult<&str, DetectFloatData<T>> {
216
210
    let (i, float) = detect_parse_float_notending(i)?;
217
189
    let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?;
218
149
    Ok((i, float))
219
210
}
220
221
210
fn detect_parse_float_notending<T: DetectFloatType>(i: &str) -> IResult<&str, DetectFloatData<T>> {
222
210
    let (i, _) = opt(is_a(" "))(i)?;
223
210
    let (i, float) = alt((
224
210
        detect_parse_float_start_interval,
225
210
        detect_parse_float_start_equal,
226
210
        detect_parse_float_start_symbol,
227
210
    ))(i)?;
228
189
    Ok((i, float))
229
210
}
230
231
#[no_mangle]
232
0
pub unsafe extern "C" fn SCDetectF64Parse(
233
0
    ustr: *const std::os::raw::c_char,
234
0
) -> *mut DetectFloatData<f64> {
235
0
    let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
236
0
    if let Ok(s) = ft_name.to_str() {
237
0
        if let Ok((_, ctx)) = detect_parse_float::<f64>(s) {
238
0
            let boxed = Box::new(ctx);
239
0
            return Box::into_raw(boxed) as *mut _;
240
0
        }
241
0
    }
242
0
    return std::ptr::null_mut();
243
0
}
244
245
#[no_mangle]
246
0
pub unsafe extern "C" fn SCDetectF64Match(
247
0
    arg: f64, ctx: &DetectFloatData<f64>,
248
0
) -> std::os::raw::c_int {
249
0
    if detect_match_float::<f64>(ctx, arg) {
250
0
        return 1;
251
0
    }
252
0
    return 0;
253
0
}
254
255
#[no_mangle]
256
0
pub unsafe extern "C" fn SCDetectF64Free(ctx: &mut DetectFloatData<f64>) {
257
    // Just unbox...
258
0
    std::mem::drop(Box::from_raw(ctx));
259
0
}
260
261
#[no_mangle]
262
0
pub unsafe extern "C" fn SCDetectParseF64(
263
0
    ustr: *const std::os::raw::c_char,
264
0
) -> *mut DetectFloatData<f64> {
265
0
    let ft_name: &CStr = CStr::from_ptr(ustr);
266
0
    if let Ok(s) = ft_name.to_str() {
267
0
        if let Ok((_, ctx)) = detect_parse_float::<f64>(s) {
268
0
            let boxed = Box::new(ctx);
269
0
            return Box::into_raw(boxed);
270
0
        }
271
0
    }
272
0
    std::ptr::null_mut()
273
0
}
274
275
#[no_mangle]
276
0
pub unsafe extern "C" fn SCDetectMatchF64(
277
0
    arg: f64, ctx: &DetectFloatData<f64>,
278
0
) -> std::os::raw::c_int {
279
0
    if detect_match_float(ctx, arg) {
280
0
        1
281
    } else {
282
0
        0
283
    }
284
0
}
285
286
#[no_mangle]
287
0
pub unsafe extern "C" fn SCDetectFreeF64(ctx: *mut DetectFloatData<f64>) {
288
0
    std::mem::drop(Box::from_raw(ctx));
289
0
}
290
291
#[cfg(test)]
292
mod tests {
293
    use super::*;
294
    use std::ffi::CString;
295
296
    #[test]
297
    fn test_parse_float_value() {
298
        assert!(parse_float_value::<f64>("NaN").is_ok());
299
        assert!(parse_float_value::<f64>("-inf").is_ok());
300
        assert!(parse_float_value::<f64>("inf").is_ok());
301
        assert!(parse_float_value::<f64>("+inf").is_ok());
302
        assert!(parse_float_value::<f64>("123.45").is_ok());
303
        assert!(parse_float_value::<f64>("-0.001").is_ok());
304
        assert!(parse_float_value::<f64>("1e10").is_ok());
305
        assert!(parse_float_value::<f64>("-1e-10").is_ok());
306
        assert!(parse_float_value::<f64>("0.5").is_ok());
307
        assert!(parse_float_value::<f64>("5.").is_ok());
308
        assert!(parse_float_value::<f64>("0+").is_ok());
309
        assert!(parse_float_value::<f64>("0-").is_ok());
310
        assert!(parse_float_value::<f32>("NaN").is_ok());
311
        assert!(parse_float_value::<f32>("-inf").is_ok());
312
        assert!(parse_float_value::<f32>("inf").is_ok());
313
        assert!(parse_float_value::<f32>("+inf").is_ok());
314
        assert!(parse_float_value::<f32>("123.45").is_ok());
315
        assert!(parse_float_value::<f32>("-0.001").is_ok());
316
        assert!(parse_float_value::<f32>("1e10").is_ok());
317
        assert!(parse_float_value::<f32>("-1e-10").is_ok());
318
        assert!(parse_float_value::<f32>("0.5").is_ok());
319
        assert!(parse_float_value::<f32>("5.").is_ok());
320
        assert!(parse_float_value::<f32>("0+").is_ok());
321
        assert!(parse_float_value::<f32>("0-").is_ok());
322
323
        assert!(parse_float_value::<f32>(".e10").is_err());
324
    }
325
    #[test]
326
    fn test_detect_parse_valid() {
327
        let _ = do_parse("1.0", 1.0, DetectFloatMode::DetectFloatModeEqual);
328
        let _ = do_parse(">1.0", 1.0, DetectFloatMode::DetectFloatModeGt);
329
        let _ = do_parse(">=1.0", 1.0, DetectFloatMode::DetectFloatModeGte);
330
        let _ = do_parse("<1.0", 1.0, DetectFloatMode::DetectFloatModeLt);
331
        let _ = do_parse("<=1.0", 1.0, DetectFloatMode::DetectFloatModeLte);
332
        let _ = do_parse("=1.0", 1.0, DetectFloatMode::DetectFloatModeEqual);
333
        let _ = do_parse("!=1.0", 1.0, DetectFloatMode::DetectFloatModeNe);
334
        let _ = do_parse_mult_args(
335
            "37.0-42.0",
336
            37.0,
337
            42.0,
338
            DetectFloatMode::DetectFloatModeRange,
339
        );
340
    }
341
342
    #[test]
343
    fn test_detect_parse_invalid() {
344
        assert!(detect_parse_float::<f64>("suricata").is_err());
345
346
        // range should be <lower-val> - <higher-val>
347
        assert!(detect_parse_float::<f64>("42-37").is_err());
348
349
        assert!(detect_parse_float::<f64>("< suricata").is_err());
350
        assert!(detect_parse_float::<f64>("<= suricata").is_err());
351
        assert!(detect_parse_float::<f64>("= suricata").is_err());
352
        assert!(detect_parse_float::<f64>("> suricata").is_err());
353
        assert!(detect_parse_float::<f64>(">= suricata").is_err());
354
        assert!(detect_parse_float::<f64>("! suricata").is_err());
355
        assert!(detect_parse_float::<f64>("!= suricata").is_err());
356
    }
357
358
    fn do_parse<T: DetectFloatType + std::fmt::Display>(
359
        val: &str, fval: T, mode: DetectFloatMode,
360
    ) -> DetectFloatData<T> {
361
        let str_val = format!("{:.3}", fval);
362
        let (_, val) = detect_parse_float::<T>(val).unwrap();
363
        let str_arg1 = format!("{:.3}", val.arg1);
364
        assert_eq!(str_arg1, str_val);
365
        assert_eq!(val.mode, mode);
366
        val
367
    }
368
369
    fn do_parse_mult_args<T: DetectFloatType + std::fmt::Display>(
370
        val: &str, fval1: T, fval2: T, mode: DetectFloatMode,
371
    ) -> DetectFloatData<T> {
372
        let str_val = format!("{:.3}", fval1);
373
        let (_, val) = detect_parse_float::<T>(val).unwrap();
374
        let str_arg = format!("{:.3}", val.arg1);
375
        assert_eq!(str_arg, str_val);
376
        let str_val = format!("{:.3}", fval2);
377
        let str_arg = format!("{:.3}", val.arg2);
378
        assert_eq!(str_arg, str_val);
379
        assert_eq!(val.mode, mode);
380
        val
381
    }
382
383
    #[test]
384
    fn test_detect_match_valid() {
385
        let val = do_parse("= 1.264", 1.264, DetectFloatMode::DetectFloatModeEqual);
386
        assert!(detect_match_float(&val, 1.264));
387
388
        let val = do_parse("> 1.0", 1.0, DetectFloatMode::DetectFloatModeGt);
389
        assert!(detect_match_float(&val, 1.1));
390
        assert!(!detect_match_float(&val, 1.0));
391
392
        let val = do_parse(">= 1.0", 1.0, DetectFloatMode::DetectFloatModeGte);
393
        assert!(detect_match_float(&val, 1.0));
394
        assert!(detect_match_float(&val, 1.5));
395
        assert!(!detect_match_float(&val, 0.5));
396
397
        let val = do_parse("<= 1.0", 1.0, DetectFloatMode::DetectFloatModeLte);
398
        assert!(detect_match_float(&val, 1.0));
399
        assert!(detect_match_float(&val, 0.5));
400
        assert!(!detect_match_float(&val, 1.5));
401
402
        let val = do_parse("< 1.0", 1.0, DetectFloatMode::DetectFloatModeLt);
403
        assert!(detect_match_float(&val, 0.9));
404
        assert!(!detect_match_float(&val, 1.0));
405
406
        let val = do_parse("= 1.0", 1.0, DetectFloatMode::DetectFloatModeEqual);
407
        assert!(detect_match_float(&val, 1.0));
408
        assert!(!detect_match_float(&val, 0.9));
409
        assert!(!detect_match_float(&val, 1.1));
410
411
        let val = do_parse("!= 1.0", 1.0, DetectFloatMode::DetectFloatModeNe);
412
        assert!(detect_match_float(&val, 0.9));
413
        assert!(detect_match_float(&val, 1.1));
414
        assert!(!detect_match_float(&val, 1.0));
415
416
        let val = do_parse_mult_args(
417
            "37.0-42.0",
418
            37.0,
419
            42.0,
420
            DetectFloatMode::DetectFloatModeRange,
421
        );
422
        assert!(detect_match_float(&val, 37.1));
423
        assert!(detect_match_float(&val, 41.9));
424
        assert!(!detect_match_float(&val, 35.0));
425
        assert!(!detect_match_float(&val, 43.0));
426
427
        let val = do_parse_mult_args(
428
            "!37.0-42.0",
429
            37.0,
430
            42.0,
431
            DetectFloatMode::DetectFloatModeNegRg,
432
        );
433
        assert!(detect_match_float(&val, 37.0));
434
        assert!(detect_match_float(&val, 42.0));
435
        assert!(detect_match_float(&val, 35.0));
436
        assert!(detect_match_float(&val, 43.0));
437
        assert!(!detect_match_float(&val, 37.1));
438
        assert!(!detect_match_float(&val, 41.9));
439
    }
440
441
    fn do_match_test(val: &str, arg1: f64, arg1_cmp: f64, arg2: f64, mode: DetectFloatMode) {
442
        let c_string = CString::new(val).expect("CString::new failed");
443
        unsafe {
444
            let val = SCDetectF64Parse(c_string.as_ptr());
445
            let str_arg_a = format!("{:.3}", (*val).arg1);
446
            let str_arg_b = format!("{:.3}", arg1);
447
            assert_eq!(str_arg_a, str_arg_b);
448
            let str_arg_a = format!("{:.3}", (*val).arg2);
449
            let str_arg_b = format!("{:.3}", arg2);
450
            assert_eq!(str_arg_a, str_arg_b);
451
452
            assert_eq!((*val).mode, mode);
453
            assert_eq!(1, SCDetectF64Match(arg1_cmp, &*val));
454
        }
455
    }
456
457
    fn do_match_test_arg1(val: &str, arg1: f64, arg1_cmp: f64, mode: DetectFloatMode) {
458
        do_match_test(val, arg1, arg1_cmp, FloatCore::min_value(), mode);
459
    }
460
461
    fn do_parse_test(val: &str, arg1: f64, arg2: f64, mode: DetectFloatMode) {
462
        let c_string = CString::new(val).expect("CString::new failed");
463
        unsafe {
464
            let val = SCDetectF64Parse(c_string.as_ptr());
465
            let str_arg_a = format!("{:.3}", (*val).arg1);
466
            let str_arg_b = format!("{:.3}", arg1);
467
            assert_eq!(str_arg_a, str_arg_b);
468
            let str_arg_a = format!("{:.3}", (*val).arg2);
469
            let str_arg_b = format!("{:.3}", arg2);
470
            assert_eq!(str_arg_a, str_arg_b);
471
472
            assert_eq!((*val).mode, mode);
473
        }
474
    }
475
476
    fn do_parse_test_arg1(val: &str, arg1: f64, mode: DetectFloatMode) {
477
        do_parse_test(val, arg1, FloatCore::min_value(), mode);
478
    }
479
480
    #[test]
481
    fn test_ffi_detect_match_valid() {
482
        do_match_test_arg1("1.0", 1.0, 1.0, DetectFloatMode::DetectFloatModeEqual);
483
        do_match_test_arg1("> 1.0", 1.0, 1.1, DetectFloatMode::DetectFloatModeGt);
484
        do_match_test_arg1(">= 1.0", 1.0, 1.0, DetectFloatMode::DetectFloatModeGte);
485
        do_match_test_arg1("<= 1.0", 1.0, 1.0, DetectFloatMode::DetectFloatModeLte);
486
        do_match_test_arg1("< 1.0", 1.0, 0.9, DetectFloatMode::DetectFloatModeLt);
487
        do_match_test_arg1("= 1.0", 1.0, 1.0, DetectFloatMode::DetectFloatModeEqual);
488
        do_match_test_arg1("!= 1.0", 1.0, 1.1, DetectFloatMode::DetectFloatModeNe);
489
        do_match_test(
490
            "37.0-42.0",
491
            37.0,
492
            37.1,
493
            42.0,
494
            DetectFloatMode::DetectFloatModeRange,
495
        );
496
        do_match_test(
497
            "37.0-42.0",
498
            37.0,
499
            41.9,
500
            42.0,
501
            DetectFloatMode::DetectFloatModeRange,
502
        );
503
        do_match_test_arg1(
504
            ">= 4.15",
505
            4.15,
506
            4.150007324019584,
507
            DetectFloatMode::DetectFloatModeGte,
508
        );
509
        do_match_test_arg1(
510
            "> 4.15",
511
            4.15,
512
            4.150007324019584,
513
            DetectFloatMode::DetectFloatModeGt,
514
        );
515
    }
516
517
    #[test]
518
    fn test_ffi_detect_parse_valid() {
519
        do_parse_test_arg1("1.0", 1.0, DetectFloatMode::DetectFloatModeEqual);
520
        do_parse_test_arg1("> 1.0", 1.0, DetectFloatMode::DetectFloatModeGt);
521
        do_parse_test_arg1(">= 1.0", 1.0, DetectFloatMode::DetectFloatModeGte);
522
        do_parse_test_arg1("<= 1.0", 1.0, DetectFloatMode::DetectFloatModeLte);
523
        do_parse_test_arg1("< 1.0", 1.0, DetectFloatMode::DetectFloatModeLt);
524
        do_parse_test_arg1("= 1.0", 1.0, DetectFloatMode::DetectFloatModeEqual);
525
        do_parse_test_arg1("!= 1.0", 1.0, DetectFloatMode::DetectFloatModeNe);
526
        do_parse_test(
527
            "37.0-42.0",
528
            37.0,
529
            42.0,
530
            DetectFloatMode::DetectFloatModeRange,
531
        );
532
    }
533
}