Coverage Report

Created: 2026-05-16 07:38

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