Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/rust/src/modbus/detect.rs
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2021 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 super::modbus::ModbusTransaction;
19
use crate::debug_validate_bug_on;
20
use lazy_static::lazy_static;
21
use regex::Regex;
22
use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message};
23
use std::ffi::CStr;
24
use std::ops::{Range, RangeInclusive};
25
use std::os::raw::{c_char, c_void};
26
use std::str::FromStr;
27
28
lazy_static! {
29
    static ref ACCESS_RE: Regex = Regex::new(
30
        "^[[:space:]]*\"?[[:space:]]*access[[:space:]]*(read|write)\
31
        [[:space:]]*(discretes|coils|input|holding)?\
32
        (?:,[[:space:]]*address[[:space:]]+([<>]?[[:digit:]]+)(?:<>([[:digit:]]+))?\
33
        (?:,[[:space:]]*value[[:space:]]+([<>]?[[:digit:]]+)(?:<>([[:digit:]]+))?)?)?\
34
        [[:space:]]*\"?[[:space:]]*$"
35
    )
36
    .unwrap();
37
    static ref FUNC_RE: Regex = Regex::new(
38
        "^[[:space:]]*\"?[[:space:]]*function[[:space:]]*(!?[A-z0-9]+)\
39
        (?:,[[:space:]]*subfunction[[:space:]]+([[:digit:]]+))?[[:space:]]*\"?[[:space:]]*$"
40
    )
41
    .unwrap();
42
    static ref UNIT_RE: Regex = Regex::new(
43
        "^[[:space:]]*\"?[[:space:]]*unit[[:space:]]+([<>]?[[:digit:]]+)\
44
        (?:<>([[:digit:]]+))?(?:,[[:space:]]*(.*))?[[:space:]]*\"?[[:space:]]*$"
45
    )
46
    .unwrap();
47
}
48
49
#[derive(Debug, PartialEq, Default)]
50
pub struct DetectModbusRust {
51
    category: Option<Flags<CodeCategory>>,
52
    function: Option<FunctionCode>,
53
    subfunction: Option<u16>,
54
    access_type: Option<Flags<AccessType>>,
55
    unit_id: Option<Range<u16>>,
56
    address: Option<Range<u16>>,
57
    value: Option<Range<u16>>,
58
}
59
60
/// Compares a range from the alert signature to the transaction's unit_id/address/value
61
/// range. If the signature's range intersects with the transaction, it is a match and true is
62
/// returned.
63
0
fn check_match_range(sig_range: &Range<u16>, trans_range: RangeInclusive<u16>) -> bool {
64
0
    if sig_range.start == sig_range.end {
65
0
        sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end()
66
0
    } else if sig_range.start == u16::MIN {
67
0
        sig_range.end > *trans_range.start()
68
0
    } else if sig_range.end == u16::MAX {
69
0
        sig_range.start < *trans_range.end()
70
    } else {
71
0
        sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end
72
    }
73
0
}
74
75
/// Compares a range from the alert signature to the transaction's unit_id/address/value.
76
/// If the signature's range intersects with the transaction, it is a match and true is
77
/// returned.
78
0
fn check_match(sig_range: &Range<u16>, value: u16) -> bool {
79
0
    if sig_range.start == sig_range.end {
80
0
        sig_range.start == value
81
0
    } else if sig_range.start == u16::MIN {
82
0
        sig_range.end > value
83
0
    } else if sig_range.end == u16::MAX {
84
0
        sig_range.start < value
85
    } else {
86
0
        sig_range.start < value && value < sig_range.end
87
    }
88
0
}
89
90
/// Gets the min/max range of an alert signature from the respective capture groups.
91
/// In the case where the max is not given, it is set based on the first char of the min str
92
/// which indicates what range we are looking for:
93
///     '<' = u16::MIN..min
94
///     '>' = min..u16::MAX
95
///     _ = min..min
96
/// If the max is given, the range returned is min..max
97
950
fn parse_range(min_str: &str, max_str: &str) -> Result<Range<u16>, ()> {
98
950
    if max_str.is_empty() {
99
536
        if let Some(sign) = min_str.chars().next() {
100
536
            debug_validate_bug_on!(!sign.is_ascii_digit() && sign != '<' && sign != '>');
101
536
            match min_str[!sign.is_ascii_digit() as usize..].parse::<u16>() {
102
515
                Ok(num) => match sign {
103
399
                    '>' => Ok(num..u16::MAX),
104
4
                    '<' => Ok(u16::MIN..num),
105
112
                    _ => Ok(num..num),
106
                },
107
                Err(_) => {
108
21
                    SCLogError!("Invalid min number: {}", min_str);
109
21
                    Err(())
110
                }
111
            }
112
        } else {
113
0
            Err(())
114
        }
115
    } else {
116
414
        let min = match min_str.parse::<u16>() {
117
407
            Ok(num) => num,
118
            Err(_) => {
119
7
                SCLogError!("Invalid min number: {}", min_str);
120
7
                return Err(());
121
            }
122
        };
123
124
407
        let max = match max_str.parse::<u16>() {
125
407
            Ok(num) => num,
126
            Err(_) => {
127
0
                SCLogError!("Invalid max number: {}", max_str);
128
0
                return Err(());
129
            }
130
        };
131
132
407
        Ok(min..max)
133
    }
134
950
}
135
136
/// Intermediary function between the C code and the parsing functions.
137
#[no_mangle]
138
1.18k
pub unsafe extern "C" fn rs_modbus_parse(c_arg: *const c_char) -> *mut c_void {
139
1.18k
    if c_arg.is_null() {
140
0
        return std::ptr::null_mut();
141
1.18k
    }
142
1.18k
    if let Ok(arg) = CStr::from_ptr(c_arg).to_str() {
143
1.18k
        match parse_unit_id(arg)
144
1.18k
            .or_else(|_| parse_function(arg))
145
1.18k
            .or_else(|_| parse_access(arg))
146
        {
147
977
            Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void,
148
208
            Err(()) => return std::ptr::null_mut(),
149
        }
150
0
    }
151
0
    std::ptr::null_mut()
152
1.18k
}
153
154
#[no_mangle]
155
977
pub unsafe extern "C" fn rs_modbus_free(ptr: *mut c_void) {
156
977
    if !ptr.is_null() {
157
977
        let _ = Box::from_raw(ptr as *mut DetectModbusRust);
158
977
    }
159
977
}
160
161
/// Compares a transaction to a signature to determine whether the transaction
162
/// matches the signature. If it does, 1 is returned; otherwise 0 is returned.
163
#[no_mangle]
164
0
pub extern "C" fn rs_modbus_inspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 {
165
    // All necessary information can be found in the request (value inspection currently
166
    // only supports write functions, which hold the value in the request).
167
    // Only inspect the response in the case where there is no request.
168
0
    let msg = match &tx.request {
169
0
        Some(r) => r,
170
0
        None => match &tx.response {
171
0
            Some(r) => r,
172
0
            None => return 0,
173
        },
174
    };
175
176
0
    if let Some(unit_id) = &modbus.unit_id {
177
0
        if !check_match(unit_id, msg.unit_id.into()) {
178
0
            return 0;
179
0
        }
180
0
    }
181
182
0
    if let Some(access_type) = &modbus.access_type {
183
0
        let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE);
184
0
        let access_func = *access_type & AccessType::FUNC_MASK;
185
0
186
0
        if rd_wr_access.is_empty()
187
0
            || !msg.access_type.intersects(rd_wr_access)
188
0
            || (!access_func.is_empty() && !msg.access_type.intersects(access_func))
189
        {
190
0
            return 0;
191
0
        }
192
0
193
0
        return inspect_data(msg, modbus) as u8;
194
0
    }
195
196
0
    if let Some(category) = modbus.category {
197
0
        return u8::from(msg.category.intersects(category));
198
0
    }
199
200
0
    match &modbus.function {
201
0
        Some(func) if func == &msg.function.code => match modbus.subfunction {
202
0
            Some(subfunc) => {
203
0
                if let Data::Diagnostic { func, data: _ } = &msg.data {
204
0
                    u8::from(subfunc == func.raw)
205
                } else {
206
0
                    0
207
                }
208
            }
209
0
            None => 1,
210
        },
211
0
        None => 1,
212
0
        _ => 0,
213
    }
214
0
}
215
216
/// Compares the transaction's data with the signature to determine whether or
217
/// not it is a match
218
0
fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool {
219
0
    let sig_address = if let Some(sig_addr) = &modbus.address {
220
        // Compare the transaction's address with the signature to determine whether or
221
        // not it is a match
222
0
        if let Some(req_addr) = msg.get_address_range() {
223
0
            if !check_match_range(sig_addr, req_addr) {
224
0
                return false;
225
0
            }
226
        } else {
227
0
            return false;
228
        }
229
230
0
        sig_addr.start
231
    } else {
232
0
        return true;
233
    };
234
235
0
    let sig_value = if let Some(value) = &modbus.value {
236
0
        value
237
    } else {
238
0
        return true;
239
    };
240
241
0
    if let Some(value) = msg.get_write_value_at_address(sig_address) {
242
0
        check_match(sig_value, value)
243
    } else {
244
0
        false
245
    }
246
0
}
247
248
/// Parses the access type for the signature
249
847
fn parse_access(access_str: &str) -> Result<DetectModbusRust, ()> {
250
847
    let re = if let Some(re) = ACCESS_RE.captures(access_str) {
251
576
        re
252
    } else {
253
271
        return Err(());
254
    };
255
256
    // 1: Read | Write
257
576
    let mut access_type: Flags<AccessType> = match re.get(1) {
258
576
        Some(access) => match AccessType::from_str(access.as_str()) {
259
576
            Ok(access_type) => access_type.into(),
260
            Err(_) => {
261
0
                SCLogError!("Unknown access keyword {}", access.as_str());
262
0
                return Err(());
263
            }
264
        },
265
        None => {
266
0
            SCLogError!("No access keyword found");
267
0
            return Err(());
268
        }
269
    };
270
271
    // 2: Discretes | Coils | Input | Holding
272
576
    access_type = match re.get(2) {
273
405
        Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS,
274
0
        Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING,
275
0
        Some(x) if x.as_str() == "discretes" => {
276
0
            if access_type == AccessType::WRITE {
277
0
                SCLogError!("Discrete access is only read access");
278
0
                return Err(());
279
0
            }
280
0
            access_type | AccessType::DISCRETES
281
        }
282
0
        Some(x) if x.as_str() == "input" => {
283
0
            if access_type == AccessType::WRITE {
284
0
                SCLogError!("Input access is only read access");
285
0
                return Err(());
286
0
            }
287
0
            access_type | AccessType::INPUT
288
        }
289
0
        Some(unknown) => {
290
0
            SCLogError!("Unknown access keyword {}", unknown.as_str());
291
0
            return Err(());
292
        }
293
171
        None => access_type,
294
    };
295
296
    // 3: Address min
297
576
    let address = if let Some(min) = re.get(3) {
298
        // 4: Address max
299
401
        let max_str = if let Some(max) = re.get(4) {
300
398
            max.as_str()
301
        } else {
302
3
            ""
303
        };
304
401
        parse_range(min.as_str(), max_str)?
305
    } else {
306
175
        return Ok(DetectModbusRust {
307
175
            access_type: Some(access_type),
308
175
            ..Default::default()
309
175
        });
310
    };
311
312
    // 5: Value min
313
395
    let value = if let Some(min) = re.get(5) {
314
0
        if address.start != address.end {
315
0
            SCLogError!("rule contains conflicting keywords (address range and value).");
316
0
            return Err(());
317
0
        }
318
0
319
0
        if access_type == AccessType::READ {
320
0
            SCLogError!("Value keyword only works in write access");
321
0
            return Err(());
322
0
        }
323
324
        // 6: Value max
325
0
        let max_str = if let Some(max) = re.get(6) {
326
0
            max.as_str()
327
        } else {
328
0
            ""
329
        };
330
331
0
        parse_range(min.as_str(), max_str)?
332
    } else {
333
395
        return Ok(DetectModbusRust {
334
395
            access_type: Some(access_type),
335
395
            address: Some(address),
336
395
            ..Default::default()
337
395
        });
338
    };
339
340
0
    Ok(DetectModbusRust {
341
0
        access_type: Some(access_type),
342
0
        address: Some(address),
343
0
        value: Some(value),
344
0
        ..Default::default()
345
0
    })
346
847
}
347
348
1.12k
fn parse_function(func_str: &str) -> Result<DetectModbusRust, ()> {
349
1.12k
    let re = if let Some(re) = FUNC_RE.captures(func_str) {
350
310
        re
351
    } else {
352
816
        return Err(());
353
    };
354
355
310
    let mut modbus: DetectModbusRust = Default::default();
356
357
    // 1: Function
358
310
    if let Some(x) = re.get(1) {
359
310
        let word = x.as_str();
360
361
        // Digit
362
310
        if let Ok(num) = word.parse::<u8>() {
363
167
            if num == 0 {
364
6
                SCLogError!("Invalid modbus function value");
365
6
                return Err(());
366
161
            }
367
161
368
161
            modbus.function = Some(FunctionCode::from_raw(num));
369
161
370
161
            // 2: Subfunction (optional)
371
161
            match re.get(2) {
372
0
                Some(x) => {
373
0
                    let subfunc = x.as_str();
374
0
                    match subfunc.parse::<u16>() {
375
0
                        Ok(num) => {
376
0
                            modbus.subfunction = Some(num);
377
0
                        }
378
                        Err(_) => {
379
0
                            SCLogError!("Invalid subfunction value: {}", subfunc);
380
0
                            return Err(());
381
                        }
382
                    }
383
                }
384
161
                None => return Ok(modbus),
385
            }
386
        }
387
        // Non-digit
388
        else {
389
143
            let neg = word.starts_with('!');
390
391
143
            let category = match &word[neg as usize..] {
392
143
                "assigned" => CodeCategory::PUBLIC_ASSIGNED.into(),
393
25
                "unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(),
394
25
                "public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED,
395
25
                "user" => CodeCategory::USER_DEFINED.into(),
396
25
                "reserved" => CodeCategory::RESERVED.into(),
397
25
                "all" => {
398
0
                    CodeCategory::PUBLIC_ASSIGNED
399
0
                        | CodeCategory::PUBLIC_UNASSIGNED
400
0
                        | CodeCategory::USER_DEFINED
401
0
                        | CodeCategory::RESERVED
402
                }
403
                _ => {
404
25
                    SCLogError!("Keyword unknown: {}", word);
405
25
                    return Err(());
406
                }
407
            };
408
409
118
            if neg {
410
0
                modbus.category = Some(!category);
411
118
            } else {
412
118
                modbus.category = Some(category);
413
118
            }
414
        }
415
    } else {
416
0
        return Err(());
417
    }
418
419
118
    Ok(modbus)
420
1.12k
}
421
422
1.18k
fn parse_unit_id(unit_str: &str) -> Result<DetectModbusRust, ()> {
423
1.18k
    let re = if let Some(re) = UNIT_RE.captures(unit_str) {
424
618
        re
425
    } else {
426
567
        return Err(());
427
    };
428
429
    // 3: Either function or access string
430
618
    let mut modbus = if let Some(x) = re.get(3) {
431
468
        let extra = x.as_str();
432
468
        if let Ok(mbus) = parse_function(extra) {
433
0
            mbus
434
468
        } else if let Ok(mbus) = parse_access(extra) {
435
399
            mbus
436
        } else {
437
69
            SCLogError!("Invalid modbus option: {}", extra);
438
69
            return Err(());
439
        }
440
    } else {
441
150
        Default::default()
442
    };
443
444
    // 1: Unit ID min
445
549
    if let Some(min) = re.get(1) {
446
        // 2: Unit ID max
447
549
        let max_str = if let Some(max) = re.get(2) {
448
16
            max.as_str()
449
        } else {
450
533
            ""
451
        };
452
453
549
        modbus.unit_id = Some(parse_range(min.as_str(), max_str)?);
454
    } else {
455
0
        SCLogError!("Min modbus unit ID not found");
456
0
        return Err(());
457
    }
458
459
527
    Ok(modbus)
460
1.18k
}
461
462
#[cfg(test)]
463
mod test {
464
    use super::super::modbus::ModbusState;
465
    use super::*;
466
    use crate::applayer::*;
467
    use sawp::parser::Direction;
468
469
    #[test]
470
    fn test_parse() {
471
        assert_eq!(
472
            parse_function("function 1"),
473
            Ok(DetectModbusRust {
474
                function: Some(FunctionCode::RdCoils),
475
                ..Default::default()
476
            })
477
        );
478
        assert_eq!(
479
            parse_function("function 8, subfunction 4"),
480
            Ok(DetectModbusRust {
481
                function: Some(FunctionCode::Diagnostic),
482
                subfunction: Some(4),
483
                ..Default::default()
484
            })
485
        );
486
        assert_eq!(
487
            parse_function("function reserved"),
488
            Ok(DetectModbusRust {
489
                category: Some(Flags::from(CodeCategory::RESERVED)),
490
                ..Default::default()
491
            })
492
        );
493
        assert_eq!(
494
            parse_function("function !assigned"),
495
            Ok(DetectModbusRust {
496
                category: Some(!CodeCategory::PUBLIC_ASSIGNED),
497
                ..Default::default()
498
            })
499
        );
500
501
        assert_eq!(
502
            parse_access("access read"),
503
            Ok(DetectModbusRust {
504
                access_type: Some(Flags::from(AccessType::READ)),
505
                ..Default::default()
506
            })
507
        );
508
        assert_eq!(
509
            parse_access("access read discretes"),
510
            Ok(DetectModbusRust {
511
                access_type: Some(AccessType::READ | AccessType::DISCRETES),
512
                ..Default::default()
513
            })
514
        );
515
        assert_eq!(
516
            parse_access("access read, address 1000"),
517
            Ok(DetectModbusRust {
518
                access_type: Some(Flags::from(AccessType::READ)),
519
                address: Some(1000..1000),
520
                ..Default::default()
521
            })
522
        );
523
        assert_eq!(
524
            parse_access("access write coils, address <500"),
525
            Ok(DetectModbusRust {
526
                access_type: Some(AccessType::WRITE | AccessType::COILS),
527
                address: Some(u16::MIN..500),
528
                ..Default::default()
529
            })
530
        );
531
        assert_eq!(
532
            parse_access("access write coils, address >500"),
533
            Ok(DetectModbusRust {
534
                access_type: Some(AccessType::WRITE | AccessType::COILS),
535
                address: Some(500..u16::MAX),
536
                ..Default::default()
537
            })
538
        );
539
        assert_eq!(
540
            parse_access("access write holding, address 100, value <1000"),
541
            Ok(DetectModbusRust {
542
                access_type: Some(AccessType::WRITE | AccessType::HOLDING),
543
                address: Some(100..100),
544
                value: Some(u16::MIN..1000),
545
                ..Default::default()
546
            })
547
        );
548
        assert_eq!(
549
            parse_access("access write holding, address 100, value 500<>1000"),
550
            Ok(DetectModbusRust {
551
                access_type: Some(AccessType::WRITE | AccessType::HOLDING),
552
                address: Some(100..100),
553
                value: Some(500..1000),
554
                ..Default::default()
555
            })
556
        );
557
558
        assert_eq!(
559
            parse_unit_id("unit 10"),
560
            Ok(DetectModbusRust {
561
                unit_id: Some(10..10),
562
                ..Default::default()
563
            })
564
        );
565
        assert_eq!(
566
            parse_unit_id("unit 10, function 8, subfunction 4"),
567
            Ok(DetectModbusRust {
568
                function: Some(FunctionCode::Diagnostic),
569
                subfunction: Some(4),
570
                unit_id: Some(10..10),
571
                ..Default::default()
572
            })
573
        );
574
        assert_eq!(
575
            parse_unit_id("unit 10, access read, address 1000"),
576
            Ok(DetectModbusRust {
577
                access_type: Some(Flags::from(AccessType::READ)),
578
                unit_id: Some(10..10),
579
                address: Some(1000..1000),
580
                ..Default::default()
581
            })
582
        );
583
        assert_eq!(
584
            parse_unit_id("unit <11"),
585
            Ok(DetectModbusRust {
586
                unit_id: Some(u16::MIN..11),
587
                ..Default::default()
588
            })
589
        );
590
        assert_eq!(
591
            parse_unit_id("unit 10<>500"),
592
            Ok(DetectModbusRust {
593
                unit_id: Some(10..500),
594
                ..Default::default()
595
            })
596
        );
597
598
        assert_eq!(parse_unit_id("unit ๖"), Err(()));
599
600
        assert_eq!(parse_access("access write holdin"), Err(()));
601
        assert_eq!(parse_access("unt 10"), Err(()));
602
        assert_eq!(
603
            parse_access("access write holding, address 100, value 500<>"),
604
            Err(())
605
        );
606
    }
607
608
    #[test]
609
    fn test_match() {
610
        let mut modbus = ModbusState::new();
611
612
        // Read/Write Multiple Registers Request
613
        assert_eq!(
614
            modbus.parse(
615
                &[
616
                    0x12, 0x34, // Transaction ID
617
                    0x00, 0x00, // Protocol ID
618
                    0x00, 0x11, // Length
619
                    0x0a, // Unit ID
620
                    0x17, // Function code
621
                    0x00, 0x03, // Read Starting Address
622
                    0x00, 0x06, // Quantity to Read
623
                    0x00, 0x0E, // Write Starting Address
624
                    0x00, 0x03, // Quantity to Write
625
                    0x06, // Write Byte count
626
                    0x12, 0x34, // Write Registers Value
627
                    0x56, 0x78, 0x9A, 0xBC
628
                ],
629
                Direction::ToServer
630
            ),
631
            AppLayerResult::ok()
632
        );
633
        assert_eq!(modbus.transactions.len(), 1);
634
        // function 23
635
        assert_eq!(
636
            rs_modbus_inspect(
637
                &modbus.transactions[0],
638
                &DetectModbusRust {
639
                    function: Some(FunctionCode::RdWrMultRegs),
640
                    ..Default::default()
641
                }
642
            ),
643
            1
644
        );
645
        // access write holding, address 15, value <4660
646
        assert_ne!(
647
            rs_modbus_inspect(
648
                &modbus.transactions[0],
649
                &DetectModbusRust {
650
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
651
                    address: Some(15..15),
652
                    value: Some(u16::MIN..4660),
653
                    ..Default::default()
654
                }
655
            ),
656
            1
657
        );
658
        // access write holding, address 15, value 4661
659
        assert_ne!(
660
            rs_modbus_inspect(
661
                &modbus.transactions[0],
662
                &DetectModbusRust {
663
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
664
                    address: Some(15..15),
665
                    value: Some(4661..4661),
666
                    ..Default::default()
667
                }
668
            ),
669
            1
670
        );
671
        // access write holding, address 16, value 20000<>22136
672
        assert_ne!(
673
            rs_modbus_inspect(
674
                &modbus.transactions[0],
675
                &DetectModbusRust {
676
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
677
                    address: Some(16..16),
678
                    value: Some(20000..22136),
679
                    ..Default::default()
680
                }
681
            ),
682
            1
683
        );
684
        // access write holding, address 16, value 22136<>30000
685
        assert_ne!(
686
            rs_modbus_inspect(
687
                &modbus.transactions[0],
688
                &DetectModbusRust {
689
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
690
                    address: Some(16..16),
691
                    value: Some(22136..30000),
692
                    ..Default::default()
693
                }
694
            ),
695
            1
696
        );
697
        // access write holding, address 15, value >4660
698
        assert_ne!(
699
            rs_modbus_inspect(
700
                &modbus.transactions[0],
701
                &DetectModbusRust {
702
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
703
                    address: Some(15..15),
704
                    value: Some(4660..u16::MAX),
705
                    ..Default::default()
706
                }
707
            ),
708
            1
709
        );
710
        // access write holding, address 16, value <22137
711
        assert_eq!(
712
            rs_modbus_inspect(
713
                &modbus.transactions[0],
714
                &DetectModbusRust {
715
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
716
                    address: Some(16..16),
717
                    value: Some(u16::MIN..22137),
718
                    ..Default::default()
719
                }
720
            ),
721
            1
722
        );
723
        // access write holding, address 16, value <22137
724
        assert_eq!(
725
            rs_modbus_inspect(
726
                &modbus.transactions[0],
727
                &DetectModbusRust {
728
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
729
                    address: Some(16..16),
730
                    value: Some(u16::MIN..22137),
731
                    ..Default::default()
732
                }
733
            ),
734
            1
735
        );
736
        // access write holding, address 17, value 39612
737
        assert_eq!(
738
            rs_modbus_inspect(
739
                &modbus.transactions[0],
740
                &DetectModbusRust {
741
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
742
                    address: Some(17..17),
743
                    value: Some(39612..39612),
744
                    ..Default::default()
745
                }
746
            ),
747
            1
748
        );
749
        // access write holding, address 17, value 30000<>39613
750
        assert_eq!(
751
            rs_modbus_inspect(
752
                &modbus.transactions[0],
753
                &DetectModbusRust {
754
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
755
                    address: Some(17..17),
756
                    value: Some(30000..39613),
757
                    ..Default::default()
758
                }
759
            ),
760
            1
761
        );
762
        // access write holding, address 15, value 4659<>5000
763
        assert_eq!(
764
            rs_modbus_inspect(
765
                &modbus.transactions[0],
766
                &DetectModbusRust {
767
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
768
                    address: Some(15..15),
769
                    value: Some(4659..5000),
770
                    ..Default::default()
771
                }
772
            ),
773
            1
774
        );
775
        // access write holding, address 17, value >39611
776
        assert_eq!(
777
            rs_modbus_inspect(
778
                &modbus.transactions[0],
779
                &DetectModbusRust {
780
                    access_type: Some(AccessType::WRITE | AccessType::HOLDING),
781
                    address: Some(17..17),
782
                    value: Some(39611..u16::MAX),
783
                    ..Default::default()
784
                }
785
            ),
786
            1
787
        );
788
        // unit 12
789
        assert_ne!(
790
            rs_modbus_inspect(
791
                &modbus.transactions[0],
792
                &DetectModbusRust {
793
                    unit_id: Some(12..12),
794
                    ..Default::default()
795
                }
796
            ),
797
            1
798
        );
799
        // unit 5<>9
800
        assert_ne!(
801
            rs_modbus_inspect(
802
                &modbus.transactions[0],
803
                &DetectModbusRust {
804
                    unit_id: Some(5..9),
805
                    ..Default::default()
806
                }
807
            ),
808
            1
809
        );
810
        // unit 11<>15
811
        assert_ne!(
812
            rs_modbus_inspect(
813
                &modbus.transactions[0],
814
                &DetectModbusRust {
815
                    unit_id: Some(11..15),
816
                    ..Default::default()
817
                }
818
            ),
819
            1
820
        );
821
        // unit >11
822
        assert_ne!(
823
            rs_modbus_inspect(
824
                &modbus.transactions[0],
825
                &DetectModbusRust {
826
                    unit_id: Some(11..u16::MAX),
827
                    ..Default::default()
828
                }
829
            ),
830
            1
831
        );
832
        // unit <9
833
        assert_ne!(
834
            rs_modbus_inspect(
835
                &modbus.transactions[0],
836
                &DetectModbusRust {
837
                    unit_id: Some(u16::MIN..9),
838
                    ..Default::default()
839
                }
840
            ),
841
            1
842
        );
843
        // unit 10
844
        assert_eq!(
845
            rs_modbus_inspect(
846
                &modbus.transactions[0],
847
                &DetectModbusRust {
848
                    unit_id: Some(10..10),
849
                    ..Default::default()
850
                }
851
            ),
852
            1
853
        );
854
        // unit 5<>15
855
        assert_eq!(
856
            rs_modbus_inspect(
857
                &modbus.transactions[0],
858
                &DetectModbusRust {
859
                    unit_id: Some(5..15),
860
                    ..Default::default()
861
                }
862
            ),
863
            1
864
        );
865
        // unit >9
866
        assert_eq!(
867
            rs_modbus_inspect(
868
                &modbus.transactions[0],
869
                &DetectModbusRust {
870
                    unit_id: Some(9..u16::MAX),
871
                    ..Default::default()
872
                }
873
            ),
874
            1
875
        );
876
        // unit <11
877
        assert_eq!(
878
            rs_modbus_inspect(
879
                &modbus.transactions[0],
880
                &DetectModbusRust {
881
                    unit_id: Some(u16::MIN..11),
882
                    ..Default::default()
883
                }
884
            ),
885
            1
886
        );
887
        // unit 10, function 20
888
        assert_ne!(
889
            rs_modbus_inspect(
890
                &modbus.transactions[0],
891
                &DetectModbusRust {
892
                    function: Some(FunctionCode::RdFileRec),
893
                    unit_id: Some(10..10),
894
                    ..Default::default()
895
                }
896
            ),
897
            1
898
        );
899
        // unit 11, function 20
900
        assert_ne!(
901
            rs_modbus_inspect(
902
                &modbus.transactions[0],
903
                &DetectModbusRust {
904
                    function: Some(FunctionCode::RdFileRec),
905
                    unit_id: Some(11..11),
906
                    ..Default::default()
907
                }
908
            ),
909
            1
910
        );
911
        // unit 11, function 23
912
        assert_ne!(
913
            rs_modbus_inspect(
914
                &modbus.transactions[0],
915
                &DetectModbusRust {
916
                    function: Some(FunctionCode::RdWrMultRegs),
917
                    unit_id: Some(11..11),
918
                    ..Default::default()
919
                }
920
            ),
921
            1
922
        );
923
        // unit 11, function public
924
        assert_ne!(
925
            rs_modbus_inspect(
926
                &modbus.transactions[0],
927
                &DetectModbusRust {
928
                    category: Some(CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED),
929
                    unit_id: Some(11..11),
930
                    ..Default::default()
931
                }
932
            ),
933
            1
934
        );
935
        // unit 10, function user
936
        assert_ne!(
937
            rs_modbus_inspect(
938
                &modbus.transactions[0],
939
                &DetectModbusRust {
940
                    category: Some(Flags::from(CodeCategory::USER_DEFINED)),
941
                    unit_id: Some(10..10),
942
                    ..Default::default()
943
                }
944
            ),
945
            1
946
        );
947
        // unit 10, function 23
948
        assert_eq!(
949
            rs_modbus_inspect(
950
                &modbus.transactions[0],
951
                &DetectModbusRust {
952
                    function: Some(FunctionCode::RdWrMultRegs),
953
                    unit_id: Some(10..10),
954
                    ..Default::default()
955
                }
956
            ),
957
            1
958
        );
959
        // unit 10, function public
960
        assert_eq!(
961
            rs_modbus_inspect(
962
                &modbus.transactions[0],
963
                &DetectModbusRust {
964
                    category: Some(CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED),
965
                    unit_id: Some(10..10),
966
                    ..Default::default()
967
                }
968
            ),
969
            1
970
        );
971
        // unit 10, function !user
972
        assert_eq!(
973
            rs_modbus_inspect(
974
                &modbus.transactions[0],
975
                &DetectModbusRust {
976
                    category: Some(!CodeCategory::USER_DEFINED),
977
                    unit_id: Some(10..10),
978
                    ..Default::default()
979
                }
980
            ),
981
            1
982
        );
983
984
        // Force Listen Only Mode
985
        assert_eq!(
986
            modbus.parse(
987
                &[
988
                    0x0A, 0x00, // Transaction ID
989
                    0x00, 0x00, // Protocol ID
990
                    0x00, 0x06, // Length
991
                    0x00, // Unit ID
992
                    0x08, // Function code
993
                    0x00, 0x04, // Sub-function code
994
                    0x00, 0x00 // Data
995
                ],
996
                Direction::ToServer
997
            ),
998
            AppLayerResult::ok()
999
        );
1000
        assert_eq!(modbus.transactions.len(), 2);
1001
        // function 8, subfunction 4
1002
        assert_eq!(
1003
            rs_modbus_inspect(
1004
                &modbus.transactions[1],
1005
                &DetectModbusRust {
1006
                    function: Some(FunctionCode::Diagnostic),
1007
                    subfunction: Some(4),
1008
                    ..Default::default()
1009
                }
1010
            ),
1011
            1
1012
        );
1013
1014
        // Encapsulated Interface Transport (MEI)
1015
        assert_eq!(
1016
            modbus.parse(
1017
                &[
1018
                    0x00, 0x10, // Transaction ID
1019
                    0x00, 0x00, // Protocol ID
1020
                    0x00, 0x05, // Length
1021
                    0x00, // Unit ID
1022
                    0x2B, // Function code
1023
                    0x0F, // MEI Type
1024
                    0x00, 0x00 // Data
1025
                ],
1026
                Direction::ToServer
1027
            ),
1028
            AppLayerResult::ok()
1029
        );
1030
        assert_eq!(modbus.transactions.len(), 3);
1031
        // function reserved
1032
        assert_eq!(
1033
            rs_modbus_inspect(
1034
                &modbus.transactions[2],
1035
                &DetectModbusRust {
1036
                    category: Some(Flags::from(CodeCategory::RESERVED)),
1037
                    ..Default::default()
1038
                }
1039
            ),
1040
            1
1041
        );
1042
1043
        // Unassigned/Unknown function
1044
        assert_eq!(
1045
            modbus.parse(
1046
                &[
1047
                    0x00, 0x0A, // Transaction ID
1048
                    0x00, 0x00, // Protocol ID
1049
                    0x00, 0x02, // Length
1050
                    0x00, // Unit ID
1051
                    0x12  // Function code
1052
                ],
1053
                Direction::ToServer
1054
            ),
1055
            AppLayerResult::ok()
1056
        );
1057
        assert_eq!(modbus.transactions.len(), 4);
1058
        // function !assigned
1059
        assert_ne!(
1060
            rs_modbus_inspect(
1061
                &modbus.transactions[3],
1062
                &DetectModbusRust {
1063
                    category: Some(!CodeCategory::PUBLIC_ASSIGNED),
1064
                    ..Default::default()
1065
                }
1066
            ),
1067
            1
1068
        );
1069
1070
        // Read Coils request
1071
        assert_eq!(
1072
            modbus.parse(
1073
                &[
1074
                    0x00, 0x00, // Transaction ID
1075
                    0x00, 0x00, // Protocol ID
1076
                    0x00, 0x06, // Length
1077
                    0x0a, // Unit ID
1078
                    0x01, // Function code
1079
                    0x78, 0x90, // Starting Address
1080
                    0x00, 0x13 // Quantity of coils
1081
                ],
1082
                Direction::ToServer
1083
            ),
1084
            AppLayerResult::ok()
1085
        );
1086
        assert_eq!(modbus.transactions.len(), 5);
1087
        // access read
1088
        assert_eq!(
1089
            rs_modbus_inspect(
1090
                &modbus.transactions[4],
1091
                &DetectModbusRust {
1092
                    access_type: Some(Flags::from(AccessType::READ)),
1093
                    ..Default::default()
1094
                }
1095
            ),
1096
            1
1097
        );
1098
        // access read, address 30870
1099
        assert_eq!(
1100
            rs_modbus_inspect(
1101
                &modbus.transactions[4],
1102
                &DetectModbusRust {
1103
                    access_type: Some(Flags::from(AccessType::READ)),
1104
                    address: Some(30870..30870),
1105
                    ..Default::default()
1106
                }
1107
            ),
1108
            1
1109
        );
1110
        // unit 10, access read, address 30863
1111
        assert_ne!(
1112
            rs_modbus_inspect(
1113
                &modbus.transactions[4],
1114
                &DetectModbusRust {
1115
                    access_type: Some(Flags::from(AccessType::READ)),
1116
                    unit_id: Some(10..10),
1117
                    address: Some(30863..30863),
1118
                    ..Default::default()
1119
                }
1120
            ),
1121
            1
1122
        );
1123
        // unit 11, access read, address 30870
1124
        assert_ne!(
1125
            rs_modbus_inspect(
1126
                &modbus.transactions[4],
1127
                &DetectModbusRust {
1128
                    access_type: Some(Flags::from(AccessType::READ)),
1129
                    unit_id: Some(11..11),
1130
                    address: Some(30870..30870),
1131
                    ..Default::default()
1132
                }
1133
            ),
1134
            1
1135
        );
1136
        // unit 11, access read, address 30863
1137
        assert_ne!(
1138
            rs_modbus_inspect(
1139
                &modbus.transactions[4],
1140
                &DetectModbusRust {
1141
                    access_type: Some(Flags::from(AccessType::READ)),
1142
                    unit_id: Some(11..11),
1143
                    address: Some(30863..30863),
1144
                    ..Default::default()
1145
                }
1146
            ),
1147
            1
1148
        );
1149
        // unit 10, access write
1150
        assert_ne!(
1151
            rs_modbus_inspect(
1152
                &modbus.transactions[4],
1153
                &DetectModbusRust {
1154
                    access_type: Some(Flags::from(AccessType::WRITE)),
1155
                    unit_id: Some(10..10),
1156
                    ..Default::default()
1157
                }
1158
            ),
1159
            1
1160
        );
1161
        // unit 10, access read, address 30870
1162
        assert_eq!(
1163
            rs_modbus_inspect(
1164
                &modbus.transactions[4],
1165
                &DetectModbusRust {
1166
                    access_type: Some(Flags::from(AccessType::READ)),
1167
                    unit_id: Some(10..10),
1168
                    address: Some(30870..30870),
1169
                    ..Default::default()
1170
                }
1171
            ),
1172
            1
1173
        );
1174
1175
        // Read Inputs Register request
1176
        assert_eq!(
1177
            modbus.parse(
1178
                &[
1179
                    0x00, 0x0A, // Transaction ID
1180
                    0x00, 0x00, // Protocol ID
1181
                    0x00, 0x06, // Length
1182
                    0x00, // Unit ID
1183
                    0x04, // Function code
1184
                    0x00, 0x08, // Starting Address
1185
                    0x00, 0x60 // Quantity of Registers
1186
                ],
1187
                Direction::ToServer
1188
            ),
1189
            AppLayerResult::ok()
1190
        );
1191
        assert_eq!(modbus.transactions.len(), 6);
1192
        // access read input
1193
        assert_eq!(
1194
            rs_modbus_inspect(
1195
                &modbus.transactions[5],
1196
                &DetectModbusRust {
1197
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1198
                    ..Default::default()
1199
                }
1200
            ),
1201
            1
1202
        );
1203
        // access read input, address <9
1204
        assert_ne!(
1205
            rs_modbus_inspect(
1206
                &modbus.transactions[5],
1207
                &DetectModbusRust {
1208
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1209
                    address: Some(u16::MIN..9),
1210
                    ..Default::default()
1211
                }
1212
            ),
1213
            1
1214
        );
1215
        // access read input, address 5<>9
1216
        assert_ne!(
1217
            rs_modbus_inspect(
1218
                &modbus.transactions[5],
1219
                &DetectModbusRust {
1220
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1221
                    address: Some(5..9),
1222
                    ..Default::default()
1223
                }
1224
            ),
1225
            1
1226
        );
1227
        // access read input, address >104
1228
        assert_ne!(
1229
            rs_modbus_inspect(
1230
                &modbus.transactions[5],
1231
                &DetectModbusRust {
1232
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1233
                    address: Some(104..u16::MAX),
1234
                    ..Default::default()
1235
                }
1236
            ),
1237
            1
1238
        );
1239
        // access read input, address 104<>110
1240
        assert_ne!(
1241
            rs_modbus_inspect(
1242
                &modbus.transactions[5],
1243
                &DetectModbusRust {
1244
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1245
                    address: Some(104..110),
1246
                    ..Default::default()
1247
                }
1248
            ),
1249
            1
1250
        );
1251
        // access read input, address 9
1252
        assert_eq!(
1253
            rs_modbus_inspect(
1254
                &modbus.transactions[5],
1255
                &DetectModbusRust {
1256
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1257
                    address: Some(9..9),
1258
                    ..Default::default()
1259
                }
1260
            ),
1261
            1
1262
        );
1263
        // access read input, address <10
1264
        assert_eq!(
1265
            rs_modbus_inspect(
1266
                &modbus.transactions[5],
1267
                &DetectModbusRust {
1268
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1269
                    address: Some(u16::MIN..10),
1270
                    ..Default::default()
1271
                }
1272
            ),
1273
            1
1274
        );
1275
        // access read input, address 5<>10
1276
        assert_eq!(
1277
            rs_modbus_inspect(
1278
                &modbus.transactions[5],
1279
                &DetectModbusRust {
1280
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1281
                    address: Some(5..10),
1282
                    ..Default::default()
1283
                }
1284
            ),
1285
            1
1286
        );
1287
        // access read input, address >103
1288
        assert_eq!(
1289
            rs_modbus_inspect(
1290
                &modbus.transactions[5],
1291
                &DetectModbusRust {
1292
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1293
                    address: Some(103..u16::MAX),
1294
                    ..Default::default()
1295
                }
1296
            ),
1297
            1
1298
        );
1299
        // access read input, address 103<>110
1300
        assert_eq!(
1301
            rs_modbus_inspect(
1302
                &modbus.transactions[5],
1303
                &DetectModbusRust {
1304
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1305
                    address: Some(103..110),
1306
                    ..Default::default()
1307
                }
1308
            ),
1309
            1
1310
        );
1311
        // access read input, address 104
1312
        assert_eq!(
1313
            rs_modbus_inspect(
1314
                &modbus.transactions[5],
1315
                &DetectModbusRust {
1316
                    access_type: Some(AccessType::READ | AccessType::INPUT),
1317
                    address: Some(104..104),
1318
                    ..Default::default()
1319
                }
1320
            ),
1321
            1
1322
        );
1323
1324
        // Origin: https://github.com/bro/bro/blob/master/testing/btest/Traces/modbus/modbus.trace
1325
        // Read Coils Response
1326
        assert_eq!(
1327
            modbus.parse(
1328
                &[
1329
                    0x00, 0x01, // Transaction ID
1330
                    0x00, 0x00, // Protocol ID
1331
                    0x00, 0x04, // Length
1332
                    0x0a, // Unit ID
1333
                    0x01, // Function code
1334
                    0x01, // Count
1335
                    0x00, // Data
1336
                ],
1337
                Direction::ToClient
1338
            ),
1339
            AppLayerResult::ok()
1340
        );
1341
        assert_eq!(modbus.transactions.len(), 7);
1342
        // function 1
1343
        assert_eq!(
1344
            rs_modbus_inspect(
1345
                &modbus.transactions[6],
1346
                &DetectModbusRust {
1347
                    function: Some(FunctionCode::RdCoils),
1348
                    ..Default::default()
1349
                }
1350
            ),
1351
            1
1352
        );
1353
        // access read, address 104
1354
        // Fails because there was no request, and the address is not retrievable
1355
        // from the response.
1356
        assert_ne!(
1357
            rs_modbus_inspect(
1358
                &modbus.transactions[6],
1359
                &DetectModbusRust {
1360
                    access_type: Some(Flags::from(AccessType::READ)),
1361
                    address: Some(104..104),
1362
                    ..Default::default()
1363
                }
1364
            ),
1365
            1
1366
        );
1367
1368
        // Origin: https://github.com/bro/bro/blob/master/testing/btest/Traces/modbus/modbus.trace
1369
        // Write Single Register Response
1370
        assert_eq!(
1371
            modbus.parse(
1372
                &[
1373
                    0x00, 0x01, // Transaction ID
1374
                    0x00, 0x00, // Protocol ID
1375
                    0x00, 0x06, // Length
1376
                    0x0a, // Unit ID
1377
                    0x06, // Function code
1378
                    0x00, 0x05, // Starting address
1379
                    0x00, 0x0b // Data
1380
                ],
1381
                Direction::ToClient
1382
            ),
1383
            AppLayerResult::ok()
1384
        );
1385
        assert_eq!(modbus.transactions.len(), 8);
1386
        // function 6
1387
        assert_eq!(
1388
            rs_modbus_inspect(
1389
                &modbus.transactions[7],
1390
                &DetectModbusRust {
1391
                    function: Some(FunctionCode::WrSingleReg),
1392
                    ..Default::default()
1393
                }
1394
            ),
1395
            1
1396
        );
1397
        // access write, address 10
1398
        assert_ne!(
1399
            rs_modbus_inspect(
1400
                &modbus.transactions[7],
1401
                &DetectModbusRust {
1402
                    access_type: Some(Flags::from(AccessType::WRITE)),
1403
                    address: Some(10..10),
1404
                    ..Default::default()
1405
                }
1406
            ),
1407
            1
1408
        );
1409
1410
        // Origin: https://github.com/bro/bro/blob/master/testing/btest/Traces/modbus/modbus.trace
1411
        // Write Single Register Response
1412
        assert_eq!(
1413
            modbus.parse(
1414
                &[
1415
                    0x00, 0x00, // Transaction ID
1416
                    0x00, 0x00, // Protocol ID
1417
                    0x00, 0x06, // Length
1418
                    0x0a, // Unit ID
1419
                    0x08, // Function code
1420
                    0x00, 0x0a, // Diagnostic code
1421
                    0x00, 0x00 // Data
1422
                ],
1423
                Direction::ToClient
1424
            ),
1425
            AppLayerResult::ok()
1426
        );
1427
        assert_eq!(modbus.transactions.len(), 9);
1428
        // function 8
1429
        assert_eq!(
1430
            rs_modbus_inspect(
1431
                &modbus.transactions[8],
1432
                &DetectModbusRust {
1433
                    function: Some(FunctionCode::Diagnostic),
1434
                    ..Default::default()
1435
                }
1436
            ),
1437
            1
1438
        );
1439
        // access read
1440
        assert_ne!(
1441
            rs_modbus_inspect(
1442
                &modbus.transactions[8],
1443
                &DetectModbusRust {
1444
                    access_type: Some(Flags::from(AccessType::READ)),
1445
                    ..Default::default()
1446
                }
1447
            ),
1448
            1
1449
        );
1450
    }
1451
}