Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/sawp-modbus-0.12.1/src/lib.rs
Line
Count
Source
1
//! A modbus protocol parser. Given bytes and a [`sawp::parser::Direction`], it will
2
//! attempt to parse the bytes and return a [`Message`]. The parser will
3
//! inform the caller about what went wrong if no message is returned (see [`sawp::parser::Parse`]
4
//! for details on possible return types).
5
//!
6
//! The following protocol references were used to create this module:
7
//!
8
//! [Modbus_V1_1b](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
9
//!
10
//! [PI_MBUS_300](https://modbus.org/docs/PI_MBUS_300.pdf)
11
//!
12
//! # Example
13
//! ```
14
//! use sawp::parser::{Direction, Parse};
15
//! use sawp::error::Error;
16
//! use sawp::error::ErrorKind;
17
//! use sawp_modbus::{Modbus, Message};
18
//!
19
//! fn parse_bytes(input: &[u8]) -> std::result::Result<&[u8], Error> {
20
//!     let modbus = Modbus::default();
21
//!     let mut bytes = input;
22
//!     while bytes.len() > 0 {
23
//!         // If we know that this is a request or response, change the Direction
24
//!         // for a more accurate parsing
25
//!         match modbus.parse(bytes, Direction::Unknown) {
26
//!             // The parser succeeded and returned the remaining bytes and the parsed modbus message
27
//!             Ok((rest, Some(message))) => {
28
//!                 println!("Modbus message: {:?}", message);
29
//!                 bytes = rest;
30
//!             }
31
//!             // The parser recognized that this might be modbus and made some progress,
32
//!             // but more bytes are needed
33
//!             Ok((rest, None)) => return Ok(rest),
34
//!             // The parser was unable to determine whether this was modbus or not and more
35
//!             // bytes are needed
36
//!             Err(Error { kind: ErrorKind::Incomplete(_) }) => return Ok(bytes),
37
//!             // The parser determined that this was not modbus
38
//!             Err(e) => return Err(e)
39
//!         }
40
//!     }
41
//!
42
//!     Ok(bytes)
43
//! }
44
//! ```
45
46
#![allow(clippy::unneeded_field_pattern)]
47
48
/// Re-export of the `Flags` struct that is used to represent bit flags
49
/// in this crate.
50
pub use sawp_flags::{Flag, Flags};
51
52
use sawp::error::{Error, ErrorKind, Result};
53
use sawp::parser::{Direction, Parse};
54
use sawp::probe::{Probe, Status};
55
use sawp::protocol::Protocol;
56
57
use sawp_flags::BitFlags;
58
59
use nom::bytes::streaming::take;
60
use nom::number::streaming::{be_u16, be_u8};
61
62
use num_enum::TryFromPrimitive;
63
use std::convert::TryFrom;
64
use std::ops::RangeInclusive;
65
66
/// FFI structs and Accessors
67
#[cfg(feature = "ffi")]
68
mod ffi;
69
70
#[cfg(feature = "ffi")]
71
use sawp_ffi::GenerateFFI;
72
73
// Used for exception handling -- any function above this is an exception
74
const ERROR_MASK: u8 = 0x80;
75
// Maximum read/write quantity
76
const MAX_QUANTITY_BIT_ACCESS: u16 = 2000;
77
const MAX_QUANTITY_WORD_ACCESS: u16 = 125;
78
// Valid count range for reading
79
const MIN_RD_COUNT: u8 = 1;
80
const MAX_RD_COUNT: u8 = 250;
81
82
const MIN_LENGTH: u16 = 2;
83
const MAX_LENGTH: u16 = 254;
84
85
/// Function code groups based on general use. Allows for easier
86
/// parsing of certain functions, since generally most functions in a group
87
/// will have the same request/response structure.
88
#[allow(non_camel_case_types)]
89
#[repr(u8)]
90
0
#[derive(Copy, Clone, Debug, PartialEq, BitFlags)]
91
pub enum AccessType {
92
    READ = 0b0000_0001,
93
    WRITE = 0b0000_0010,
94
    DISCRETES = 0b0000_0100,
95
    COILS = 0b0000_1000,
96
    INPUT = 0b0001_0000,
97
    HOLDING = 0b0010_0000,
98
    SINGLE = 0b0100_0000,
99
    MULTIPLE = 0b1000_0000,
100
    /// DISCRETES | COILS
101
    BIT_ACCESS_MASK = 0b0000_1100,
102
    /// DISCRETES | COILS | INPUT | HOLDING
103
    FUNC_MASK = 0b0011_1100,
104
    /// WRITE | SINGLE
105
    WRITE_SINGLE = 0b0100_0010,
106
    /// WRITE | MULTIPLE
107
    WRITE_MULTIPLE = 0b1000_0010,
108
}
109
110
impl From<FunctionCode> for Flags<AccessType> {
111
275k
    fn from(code: FunctionCode) -> Self {
112
275k
        match code {
113
13.2k
            FunctionCode::RdCoils => AccessType::COILS | AccessType::READ,
114
2.28k
            FunctionCode::RdDiscreteInputs => AccessType::DISCRETES | AccessType::READ,
115
3.00k
            FunctionCode::RdHoldRegs => AccessType::HOLDING | AccessType::READ,
116
4.00k
            FunctionCode::RdInputRegs => AccessType::INPUT | AccessType::READ,
117
11.2k
            FunctionCode::WrSingleCoil => AccessType::COILS | AccessType::WRITE_SINGLE,
118
11.4k
            FunctionCode::WrSingleReg => AccessType::HOLDING | AccessType::WRITE_SINGLE,
119
11.6k
            FunctionCode::WrMultCoils => AccessType::COILS | AccessType::WRITE_MULTIPLE,
120
17.7k
            FunctionCode::WrMultRegs => AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
121
18.5k
            FunctionCode::MaskWrReg => AccessType::HOLDING | AccessType::WRITE,
122
            FunctionCode::RdWrMultRegs => {
123
8.23k
                AccessType::HOLDING | AccessType::READ | AccessType::WRITE_MULTIPLE
124
            }
125
174k
            _ => AccessType::none(),
126
        }
127
275k
    }
128
}
129
130
/// Function Code Categories as stated in the [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
131
#[allow(non_camel_case_types)]
132
#[repr(u8)]
133
0
#[derive(Copy, Clone, Debug, PartialEq, BitFlags)]
134
pub enum CodeCategory {
135
    PUBLIC_ASSIGNED = 0b0000_0001,
136
    PUBLIC_UNASSIGNED = 0b0000_0010,
137
    USER_DEFINED = 0b0000_0100,
138
    RESERVED = 0b0000_1000,
139
}
140
141
impl CodeCategory {
142
68.2k
    fn from_raw(id: u8) -> Flags<Self> {
143
28.1k
        match id {
144
22.1k
            0 => CodeCategory::none(),
145
46.0k
            x if x < 9 => CodeCategory::PUBLIC_UNASSIGNED.into(),
146
46.0k
            x if x < 15 => CodeCategory::RESERVED.into(),
147
46.0k
            x if x < 41 => CodeCategory::PUBLIC_UNASSIGNED.into(),
148
43.8k
            x if x < 43 => CodeCategory::RESERVED.into(),
149
43.2k
            x if x < 65 => CodeCategory::PUBLIC_UNASSIGNED.into(),
150
40.3k
            x if x < 73 => CodeCategory::USER_DEFINED.into(),
151
37.9k
            x if x < 90 => CodeCategory::PUBLIC_UNASSIGNED.into(),
152
36.2k
            x if x < 92 => CodeCategory::RESERVED.into(),
153
35.3k
            x if x < 100 => CodeCategory::PUBLIC_UNASSIGNED.into(),
154
33.3k
            x if x < 111 => CodeCategory::USER_DEFINED.into(),
155
31.5k
            x if x < 125 => CodeCategory::PUBLIC_UNASSIGNED.into(),
156
28.1k
            x if x < 128 => CodeCategory::RESERVED.into(),
157
28.1k
            _ => CodeCategory::none(),
158
        }
159
68.2k
    }
160
}
161
162
impl From<&Message> for Flags<CodeCategory> {
163
275k
    fn from(msg: &Message) -> Self {
164
275k
        match msg.function.code {
165
66.1k
            FunctionCode::Diagnostic => match &msg.data {
166
57.6k
                Data::Diagnostic { func, .. } => {
167
57.6k
                    if func.code == DiagnosticSubfunction::Reserved {
168
6.45k
                        CodeCategory::RESERVED.into()
169
                    } else {
170
51.2k
                        CodeCategory::PUBLIC_ASSIGNED.into()
171
                    }
172
                }
173
8.43k
                _ => CodeCategory::none(),
174
            },
175
10.2k
            FunctionCode::MEI => match &msg.data {
176
8.02k
                Data::MEI { mei_type, .. } => {
177
8.02k
                    if mei_type.code == MEIType::Unknown {
178
5.15k
                        CodeCategory::RESERVED.into()
179
                    } else {
180
2.86k
                        CodeCategory::PUBLIC_ASSIGNED.into()
181
                    }
182
                }
183
2.25k
                _ => CodeCategory::none(),
184
            },
185
68.2k
            FunctionCode::Unknown => CodeCategory::from_raw(msg.function.raw),
186
131k
            _ => CodeCategory::PUBLIC_ASSIGNED.into(),
187
        }
188
275k
    }
189
}
190
191
/// Flags which identify messages which parse as modbus
192
/// but contain invalid data. The caller can use the message's
193
/// error flags to see if and what errors were in the
194
/// pack of bytes and take action using this information.
195
#[allow(non_camel_case_types)]
196
#[repr(u8)]
197
0
#[derive(Copy, Clone, Debug, PartialEq, BitFlags)]
198
pub enum ErrorFlags {
199
    DATA_VALUE = 0b0000_0001,
200
    DATA_LENGTH = 0b0000_0010,
201
    EXC_CODE = 0b0000_0100,
202
    FUNC_CODE = 0b0000_1000,
203
    PROTO_ID = 0b0001_0000,
204
}
205
206
/// Information on the function code parsed
207
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
208
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
209
#[derive(Debug, PartialEq)]
210
pub struct Function {
211
    /// Value of the function byte
212
    pub raw: u8,
213
    /// Function name associated with the raw value
214
    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
215
    pub code: FunctionCode,
216
}
217
218
impl Function {
219
2.06M
    fn new(val: u8) -> Function {
220
        Function {
221
2.06M
            raw: val,
222
            code: {
223
2.06M
                if val >= ERROR_MASK {
224
56.2k
                    FunctionCode::from_raw(val ^ ERROR_MASK)
225
                } else {
226
2.00M
                    FunctionCode::from_raw(val)
227
                }
228
            },
229
        }
230
2.06M
    }
231
}
232
233
/// Function code names as stated in the [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
234
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
235
#[repr(u8)]
236
pub enum FunctionCode {
237
    RdCoils = 0x01,
238
    RdDiscreteInputs,
239
    RdHoldRegs,
240
    RdInputRegs,
241
    WrSingleCoil,
242
    WrSingleReg,
243
    RdExcStatus,
244
    Diagnostic,
245
    Program484,
246
    Poll484,
247
    GetCommEventCtr,
248
    GetCommEventLog,
249
    ProgramController,
250
    PollController,
251
    WrMultCoils,
252
    WrMultRegs,
253
    ReportServerID,
254
    Program884,
255
    ResetCommLink,
256
    RdFileRec,
257
    WrFileRec,
258
    MaskWrReg,
259
    RdWrMultRegs,
260
    RdFIFOQueue,
261
    MEI = 0x2b,
262
    Unknown,
263
}
264
265
impl std::fmt::Display for FunctionCode {
266
9.90k
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
267
9.90k
        write!(fmt, "{:?}", self)
268
9.90k
    }
269
}
270
271
impl FunctionCode {
272
2.06M
    pub fn from_raw(val: u8) -> Self {
273
2.06M
        FunctionCode::try_from(val).unwrap_or(FunctionCode::Unknown)
274
2.06M
    }
275
}
276
277
/// Information on the diagnostic subfunction code parsed
278
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
279
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
280
#[derive(Debug, PartialEq)]
281
pub struct Diagnostic {
282
    /// Value of the subfunction bytes
283
    pub raw: u16,
284
    /// Subfunction name associated with the raw value
285
    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
286
    pub code: DiagnosticSubfunction,
287
}
288
289
impl Diagnostic {
290
57.6k
    fn new(val: u16) -> Diagnostic {
291
57.6k
        Diagnostic {
292
57.6k
            raw: val,
293
57.6k
            code: DiagnosticSubfunction::try_from(val).unwrap_or(DiagnosticSubfunction::Reserved),
294
57.6k
        }
295
57.6k
    }
296
}
297
298
/// Subfunction code names as stated in the [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
299
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
300
#[repr(u16)]
301
pub enum DiagnosticSubfunction {
302
    RetQueryData = 0x00,
303
    RestartCommOpt,
304
    RetDiagReg,
305
    ChangeInputDelimiter,
306
    ForceListenOnlyMode,
307
    // 0x05 - 0x09: RESERVED
308
    ClearCtrDiagReg = 0x0a,
309
    RetBusMsgCount,
310
    RetBusCommErrCount,
311
    RetBusExcErrCount,
312
    RetServerMsgCount,
313
    RetServerNoRespCount,
314
    RetServerNAKCount,
315
    RetServerBusyCount,
316
    RetBusCharOverrunCount,
317
    RetOverrunErrCount,
318
    ClearOverrunCounterFlag,
319
    GetClearPlusStats,
320
    // 0x16 and on: RESERVED
321
    Reserved,
322
}
323
324
impl std::fmt::Display for DiagnosticSubfunction {
325
478
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
326
478
        write!(fmt, "{:?}", self)
327
478
    }
328
}
329
330
/// Information on the mei code parsed
331
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
332
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
333
#[derive(Debug, PartialEq)]
334
pub struct MEI {
335
    /// Value of the mei function byte
336
    pub raw: u8,
337
    /// Function name associated with the raw value
338
    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
339
    pub code: MEIType,
340
}
341
342
impl MEI {
343
8.02k
    fn new(val: u8) -> MEI {
344
8.02k
        MEI {
345
8.02k
            raw: val,
346
8.02k
            code: MEIType::try_from(val).unwrap_or(MEIType::Unknown),
347
8.02k
        }
348
8.02k
    }
349
}
350
351
/// MEI function code names as stated in the [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
352
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
353
#[repr(u8)]
354
pub enum MEIType {
355
    Unknown = 0x00,
356
    CANOpenGenRefReqResp = 0x0d,
357
    RdDevId = 0x0e,
358
}
359
360
impl std::fmt::Display for MEIType {
361
45
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
362
45
        write!(fmt, "{:?}", self)
363
45
    }
364
}
365
366
/// Information on the exception code parsed
367
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
368
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
369
#[derive(Debug, PartialEq)]
370
pub struct Exception {
371
    /// Value of the exception code byte
372
    pub raw: u8,
373
    /// Exception name associated with the raw value
374
    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
375
    pub code: ExceptionCode,
376
}
377
378
impl Exception {
379
36.2k
    fn new(val: u8) -> Exception {
380
36.2k
        Exception {
381
36.2k
            raw: val,
382
36.2k
            code: ExceptionCode::try_from(val).unwrap_or(ExceptionCode::Unknown),
383
36.2k
        }
384
36.2k
    }
385
}
386
387
/// Exception code names as stated in the [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
388
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
389
#[repr(u8)]
390
pub enum ExceptionCode {
391
    IllegalFunction = 0x01,
392
    IllegalDataAddr,
393
    IllegalDataValue,
394
    ServerDeviceFail,
395
    Ack,
396
    ServerDeviceBusy,
397
    NegAck,
398
    MemParityErr,
399
    GatewayPathUnavailable = 0x0a,
400
    GatewayTargetFailToResp,
401
    Unknown,
402
}
403
404
impl std::fmt::Display for ExceptionCode {
405
227
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
406
227
        write!(fmt, "{:?}", self)
407
227
    }
408
}
409
410
/// Read information on parsed in function data
411
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
412
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
413
#[derive(Clone, Debug, PartialEq)]
414
pub enum Read {
415
    Request { address: u16, quantity: u16 },
416
    Response(Vec<u8>),
417
}
418
419
/// Write information on parsed in function data
420
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
421
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
422
#[derive(Debug, PartialEq)]
423
pub enum Write {
424
    /// [`AccessType::MULTIPLE`] requests, responses fall in [`Write::Other`]
425
    MultReq {
426
        address: u16,
427
        quantity: u16,
428
        data: Vec<u8>,
429
    },
430
    /// [`FunctionCode::MaskWrReg`] requests/responses, the only (public) write function
431
    /// that does not fall under [`AccessType::SINGLE`]/[`AccessType::MULTIPLE`]
432
    /// (with the exception of [`FunctionCode::WrFileRec`])
433
    Mask {
434
        address: u16,
435
        and_mask: u16,
436
        or_mask: u16,
437
    },
438
    /// Used for [`AccessType::SINGLE`] requests/responses and [`AccessType::MULTIPLE`] responses
439
    Other { address: u16, data: u16 },
440
}
441
442
/// Represents the various fields found in the PDU
443
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
444
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
445
#[derive(Debug, PartialEq)]
446
pub enum Data {
447
    Exception(Exception),
448
    Diagnostic {
449
        func: Diagnostic,
450
        data: Vec<u8>,
451
    },
452
    MEI {
453
        mei_type: MEI,
454
        data: Vec<u8>,
455
    },
456
    Read(Read),
457
    Write(Write),
458
    ReadWrite {
459
        read: Read,
460
        write: Write,
461
    },
462
    /// Used for data that doesn't fit elsewhere
463
    ByteVec(Vec<u8>),
464
    Empty,
465
}
466
467
#[derive(Debug, Default)]
468
pub struct Modbus {
469
    /// Enable strict probing, such as only recognizing
470
    /// public assigned function codes
471
    pub probe_strict: bool,
472
}
473
474
/// Breakdown of the parsed modbus bytes
475
#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
476
#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_modbus"))]
477
#[derive(Debug, PartialEq)]
478
pub struct Message {
479
    pub transaction_id: u16,
480
    pub protocol_id: u16,
481
    length: u16,
482
    pub unit_id: u8,
483
    pub function: Function,
484
    #[cfg_attr(feature = "ffi", sawp_ffi(flag = "u8"))]
485
    pub access_type: Flags<AccessType>,
486
    #[cfg_attr(feature = "ffi", sawp_ffi(flag = "u8"))]
487
    pub category: Flags<CodeCategory>,
488
    pub data: Data,
489
    #[cfg_attr(feature = "ffi", sawp_ffi(flag = "u8"))]
490
    pub error_flags: Flags<ErrorFlags>,
491
}
492
493
impl Message {
494
    /// Subtracts 2 from the length (the unit id and function bytes)
495
    /// so that length checks do not need to account for the 2 bytes
496
295k
    fn data_length(&self) -> u16 {
497
295k
        self.length - 2
498
295k
    }
499
500
    //          Num Bytes  Byte Placement
501
    // Code:    1          (0)
502
37.5k
    fn parse_exception<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
503
37.5k
        let (input, exc_code) = be_u8(input)?;
504
36.2k
        let exc = Exception::new(exc_code);
505
750
        match exc.code {
506
            ExceptionCode::IllegalDataValue
507
1.91k
                if self.function.code != FunctionCode::Diagnostic
508
508
                    && ((self.function.raw > 6 && self.function.raw < 15)
509
508
                        || (self.function.raw > 16 && self.function.raw < 20)) =>
510
            {
511
0
                self.error_flags |= ErrorFlags::EXC_CODE
512
            }
513
            ExceptionCode::IllegalDataAddr
514
750
                if (self.function.raw > 6 && self.function.raw < 15)
515
750
                    || (self.function.raw > 16 && self.function.raw < 20) =>
516
            {
517
0
                self.error_flags |= ErrorFlags::EXC_CODE
518
            }
519
            ExceptionCode::MemParityErr
520
940
                if self.function.code != FunctionCode::RdFileRec
521
394
                    && self.function.code != FunctionCode::WrFileRec =>
522
            {
523
354
                self.error_flags |= ErrorFlags::EXC_CODE
524
            }
525
21.7k
            ExceptionCode::Unknown => self.error_flags |= ErrorFlags::EXC_CODE,
526
14.1k
            _ => {}
527
        }
528
529
36.2k
        self.data = Data::Exception(exc);
530
36.2k
        Ok(input)
531
37.5k
    }
532
533
    //                             Num Bytes   Byte Placement
534
    // Request:
535
    //     Diagnostic Code:        2           (0,1)
536
    //     Data:                   2           (2,3)
537
    // Response:
538
    //     Diagnostic Code:        2           (0,1)
539
    //     Data:                   x           (2..)
540
59.6k
    fn parse_diagnostic<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
541
59.6k
        if self.data_length() < 2 {
542
1.99k
            self.error_flags |= ErrorFlags::DATA_LENGTH;
543
1.99k
            return Ok(input);
544
57.6k
        }
545
546
57.6k
        let (input, diag_func) = be_u16(input)?;
547
57.6k
        let (input, rest) = take(self.data_length() - 2)(input)?;
548
549
57.6k
        self.data = Data::Diagnostic {
550
57.6k
            func: Diagnostic::new(diag_func),
551
57.6k
            data: rest.to_vec(),
552
57.6k
        };
553
57.6k
        Ok(input)
554
59.6k
    }
555
556
    //                             Num Bytes   Byte Placement
557
    //     MEI Code:               2           (0,1)
558
    //     Data:                   x           (2..)
559
9.36k
    fn parse_mei<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
560
9.36k
        if self.data_length() < 1 {
561
1.34k
            self.error_flags |= ErrorFlags::DATA_LENGTH;
562
1.34k
            return Ok(input);
563
8.02k
        }
564
565
8.02k
        let (input, raw_mei) = be_u8(input)?;
566
8.02k
        let mei_type = MEI::new(raw_mei);
567
8.02k
        let (input, rest) = take(self.data_length() - 1)(input)?;
568
569
8.02k
        self.data = Data::MEI {
570
8.02k
            mei_type,
571
8.02k
            data: rest.to_vec(),
572
8.02k
        };
573
574
8.02k
        Ok(input)
575
9.36k
    }
576
577
74.4k
    fn parse_bytevec<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
578
74.4k
        let (input, data) = take(self.data_length())(input)?;
579
74.4k
        self.data = Data::ByteVec(data.to_vec());
580
74.4k
        Ok(input)
581
74.4k
    }
582
583
    //                     Num Bytes   Byte Placement
584
    // Starting Address:   2           (0,1)
585
    // Quantity of Regs:   2           (2,3)
586
16.5k
    fn parse_read_request<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
587
16.5k
        let (input, address) = be_u16(input)?;
588
13.8k
        let (input, quantity) = be_u16(input)?;
589
590
12.1k
        if quantity == 0 {
591
2.79k
            self.error_flags |= ErrorFlags::DATA_VALUE;
592
9.30k
        }
593
594
12.1k
        if self.function.code != FunctionCode::RdWrMultRegs && self.data_length() > 4 {
595
4.57k
            self.error_flags |= ErrorFlags::DATA_LENGTH;
596
7.52k
        }
597
598
12.1k
        if self.access_type.intersects(AccessType::BIT_ACCESS_MASK) {
599
4.37k
            if quantity > MAX_QUANTITY_BIT_ACCESS {
600
1.34k
                self.error_flags |= ErrorFlags::DATA_VALUE;
601
3.02k
            }
602
7.73k
        } else if quantity > MAX_QUANTITY_WORD_ACCESS {
603
3.60k
            self.error_flags |= ErrorFlags::DATA_VALUE;
604
4.12k
        }
605
606
12.1k
        self.data = Data::Read(Read::Request { address, quantity });
607
12.1k
        Ok(input)
608
16.5k
    }
609
610
    //          Num Bytes  Byte Placement
611
    // Count:   1          (0)
612
    // Data:    Count      (1..Count + 1)
613
10.5k
    fn parse_read_response<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
614
10.5k
        if self.data_length() < 1 {
615
1.13k
            self.error_flags |= ErrorFlags::DATA_LENGTH;
616
1.13k
            return Ok(input);
617
9.37k
        }
618
619
9.37k
        let (input, count) = be_u8(input)?;
620
621
9.37k
        if !(MIN_RD_COUNT..=MAX_RD_COUNT).contains(&count) {
622
3.64k
            self.error_flags |= ErrorFlags::DATA_VALUE;
623
5.73k
        }
624
625
9.37k
        if self.data_length() - 1 != count.into() {
626
8.08k
            self.error_flags |= ErrorFlags::DATA_VALUE;
627
8.08k
        }
628
629
9.37k
        let (input, data) = take(self.data_length() - 1)(input)?;
630
9.37k
        self.data = Data::Read(Read::Response(data.to_vec()));
631
9.37k
        Ok(input)
632
10.5k
    }
633
634
    //                             Num Bytes       Byte Placement
635
    // FunctionCode::RdWrMultRegs:
636
    //     Read Address:           2               (0,1)
637
    //     Read Quantity:          2               (2,3)
638
    //     <Multiple writes>
639
    // FunctionCode::MaskWrReg:
640
    //     Starting Address:       2               (0,1)
641
    //     And_mask:               2               (2,3)
642
    //     Or_mask:                2               (4,5)
643
    // Single write:
644
    //     Starting Address:       2               (0,1)
645
    //     Data:                   2               (2,3)
646
    // Multiple writes:
647
    //     Starting Address:       2               (0,1)
648
    //     Quantity of Regs:       2               (2,3)
649
    //     Byte Count:             1               (4)
650
    //     Data:                   Count           (5 to (Count + 5))
651
    //
652
    // Clippy wants us to factor out the first be_u16 call but we would lose
653
    // meaning in the variable name.
654
38.4k
    fn parse_write_request<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
655
38.4k
        let (input, address) = be_u16(input)?;
656
657
36.0k
        if self.access_type.contains(AccessType::SINGLE) {
658
9.39k
            let (input, data) = be_u16(input)?;
659
660
5.86k
            if self.access_type.contains(AccessType::COILS) && data != 0x0000 && data != 0xff00 {
661
1.36k
                self.error_flags |= ErrorFlags::DATA_VALUE;
662
4.50k
            }
663
664
5.86k
            self.data = Data::Write(Write::Other { address, data });
665
5.86k
            Ok(input)
666
26.6k
        } else if self.access_type.contains(AccessType::MULTIPLE) {
667
17.0k
            let (input, quantity) = be_u16(input)?;
668
15.8k
            let (input, count) = be_u8(input)?;
669
670
15.2k
            let mut offset = 7;
671
15.2k
            if self.function.code == FunctionCode::RdWrMultRegs {
672
4.19k
                offset += 4; // Add 4 bytes for the read section of the request
673
11.0k
            }
674
675
15.2k
            if quantity == 0 || self.length - offset != count.into() {
676
11.0k
                self.error_flags |= ErrorFlags::DATA_LENGTH;
677
11.0k
            }
678
679
15.2k
            if self.access_type.intersects(AccessType::BIT_ACCESS_MASK) {
680
5.92k
                if quantity > MAX_QUANTITY_BIT_ACCESS
681
4.78k
                    || u16::from(count) != (quantity / 8) + u16::from(quantity % 8 != 0)
682
4.27k
                {
683
4.27k
                    self.error_flags |= ErrorFlags::DATA_VALUE;
684
4.27k
                }
685
9.31k
            } else if quantity > MAX_QUANTITY_WORD_ACCESS
686
4.30k
                || u32::from(count) != 2 * u32::from(quantity)
687
7.41k
            {
688
7.41k
                self.error_flags |= ErrorFlags::DATA_VALUE;
689
7.41k
            }
690
691
15.2k
            let (input, data) = take(self.length - offset)(input)?;
692
693
15.2k
            self.data = match &self.data {
694
4.19k
                Data::Read(read) => Data::ReadWrite {
695
4.19k
                    read: read.clone(),
696
4.19k
                    write: Write::MultReq {
697
4.19k
                        address,
698
4.19k
                        quantity,
699
4.19k
                        data: data.to_vec(),
700
4.19k
                    },
701
4.19k
                },
702
11.0k
                _ => Data::Write(Write::MultReq {
703
11.0k
                    address,
704
11.0k
                    quantity,
705
11.0k
                    data: data.to_vec(),
706
11.0k
                }),
707
            };
708
15.2k
            Ok(input)
709
        } else {
710
9.58k
            let (input, and_mask) = be_u16(input)?;
711
7.62k
            let (input, or_mask) = be_u16(input)?;
712
713
7.07k
            self.data = Data::Write(Write::Mask {
714
7.07k
                address,
715
7.07k
                and_mask,
716
7.07k
                or_mask,
717
7.07k
            });
718
7.07k
            Ok(input)
719
        }
720
38.4k
    }
721
722
    //                             Num Bytes   Byte Placement
723
    // FunctionCode::MaskWrReg:
724
    //     Starting Address:       2           (0,1)
725
    //     And_mask:               2           (2,3)
726
    //     Or_mask:                2           (4,5)
727
    // Single write:
728
    //     Starting Address:       2           (0,1)
729
    //     Data:                   2           (2,3)
730
    // Multiple writes:
731
    //     Starting Address:       2           (0,1)
732
    //     Quantity of Regs:       2           (2,3)
733
    //
734
    // Clippy wants us to factor out the first be_u16 call but we would lose
735
    // meaning in the variable name.
736
34.5k
    fn parse_write_response<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
737
34.5k
        let (input, address) = be_u16(input)?;
738
739
33.2k
        if self.access_type.contains(AccessType::SINGLE) {
740
8.51k
            let (input, data) = be_u16(input)?;
741
5.34k
            self.data = Data::Write(Write::Other { address, data });
742
5.34k
            Ok(input)
743
24.7k
        } else if self.access_type.contains(AccessType::MULTIPLE) {
744
16.0k
            let (input, quantity) = be_u16(input)?;
745
14.8k
            if quantity == 0 {
746
2.86k
                self.error_flags |= ErrorFlags::DATA_VALUE;
747
12.0k
            }
748
749
14.8k
            if self.access_type.intersects(AccessType::BIT_ACCESS_MASK) {
750
5.08k
                if quantity > MAX_QUANTITY_WORD_ACCESS {
751
1.01k
                    self.error_flags |= ErrorFlags::DATA_VALUE;
752
4.06k
                }
753
9.80k
            } else if quantity > MAX_QUANTITY_BIT_ACCESS {
754
1.93k
                self.error_flags |= ErrorFlags::DATA_VALUE;
755
7.86k
            }
756
757
14.8k
            self.data = Data::Write(Write::Other {
758
14.8k
                address,
759
14.8k
                data: quantity,
760
14.8k
            });
761
14.8k
            Ok(input)
762
        } else {
763
8.66k
            let (input, and_mask) = be_u16(input)?;
764
6.51k
            let (input, or_mask) = be_u16(input)?;
765
766
6.16k
            self.data = Data::Write(Write::Mask {
767
6.16k
                address,
768
6.16k
                and_mask,
769
6.16k
                or_mask,
770
6.16k
            });
771
6.16k
            Ok(input)
772
        }
773
34.5k
    }
774
775
129k
    fn parse_request<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
776
2.67k
        match self.function.code {
777
            FunctionCode::Diagnostic => {
778
31.8k
                if self.data_length() != 4 {
779
3.94k
                    self.error_flags |= ErrorFlags::DATA_LENGTH;
780
27.9k
                }
781
782
31.8k
                let input = self.parse_diagnostic(input)?;
783
31.8k
                if let Data::Diagnostic { func, data } = &self.data {
784
30.7k
                    if data.len() == 2 {
785
27.9k
                        match func.code {
786
                            DiagnosticSubfunction::RetQueryData
787
                            | DiagnosticSubfunction::ForceListenOnlyMode
788
4.42k
                            | DiagnosticSubfunction::Reserved => {}
789
                            DiagnosticSubfunction::RestartCommOpt => {
790
3.69k
                                if data[1] != 0x00 || (data[0] != 0x00 && data[0] != 0xff) {
791
2.61k
                                    self.error_flags |= ErrorFlags::DATA_VALUE;
792
2.61k
                                }
793
                            }
794
                            DiagnosticSubfunction::ChangeInputDelimiter => {
795
820
                                if data[1] != 0x00 {
796
288
                                    self.error_flags |= ErrorFlags::DATA_VALUE;
797
532
                                }
798
                            }
799
                            _ => {
800
19.0k
                                if data[0] != 0x00 || data[1] != 0x00 {
801
2.33k
                                    self.error_flags |= ErrorFlags::DATA_VALUE;
802
16.6k
                                }
803
                            }
804
                        }
805
2.77k
                    }
806
1.17k
                }
807
808
31.8k
                return Ok(input);
809
            }
810
3.33k
            FunctionCode::MEI => return self.parse_mei(input),
811
1.98k
            FunctionCode::RdFileRec | FunctionCode::WrFileRec if self.data_length() == 0 => {
812
1.24k
                self.error_flags |= ErrorFlags::DATA_LENGTH
813
            }
814
            FunctionCode::RdExcStatus
815
            | FunctionCode::GetCommEventCtr
816
            | FunctionCode::GetCommEventLog
817
            | FunctionCode::ReportServerID
818
2.67k
                if self.data_length() > 0 =>
819
            {
820
4.38k
                self.error_flags |= ErrorFlags::DATA_LENGTH
821
            }
822
980
            FunctionCode::RdFIFOQueue if self.data_length() != 2 => {
823
530
                self.error_flags |= ErrorFlags::DATA_LENGTH
824
            }
825
            _ => {
826
87.9k
                if self.function.raw == 0 || self.function.raw >= ERROR_MASK {
827
24.6k
                    self.error_flags |= ErrorFlags::FUNC_CODE;
828
63.3k
                }
829
830
87.9k
                if self.access_type.intersects(AccessType::READ) {
831
16.5k
                    let input = self.parse_read_request(input)?;
832
833
12.1k
                    if self.access_type.intersects(AccessType::WRITE) {
834
5.20k
                        return self.parse_write_request(input);
835
6.89k
                    }
836
837
6.89k
                    return Ok(input);
838
71.4k
                }
839
840
71.4k
                if self.access_type.intersects(AccessType::WRITE) {
841
33.2k
                    return self.parse_write_request(input);
842
38.1k
                }
843
            }
844
        }
845
846
44.3k
        self.parse_bytevec(input)
847
129k
    }
848
849
126k
    fn parse_response<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
850
1.12k
        match self.function.code {
851
126k
            _ if self.function.raw >= ERROR_MASK => return self.parse_exception(input),
852
22.6k
            FunctionCode::Diagnostic => return self.parse_diagnostic(input),
853
3.67k
            FunctionCode::MEI => return self.parse_mei(input),
854
1.12k
            FunctionCode::RdExcStatus if self.data_length() != 1 => {
855
619
                self.error_flags |= ErrorFlags::DATA_LENGTH
856
            }
857
3.04k
            FunctionCode::GetCommEventCtr if self.data_length() != 4 => {
858
2.29k
                self.error_flags |= ErrorFlags::DATA_LENGTH
859
            }
860
            _ => {
861
65.0k
                if self.access_type.intersects(AccessType::READ) {
862
10.5k
                    return self.parse_read_response(input);
863
54.5k
                }
864
865
54.5k
                if self.access_type.intersects(AccessType::WRITE) {
866
34.5k
                    return self.parse_write_response(input);
867
19.9k
                }
868
            }
869
        }
870
871
22.8k
        self.parse_bytevec(input)
872
126k
    }
873
874
19.7k
    fn parse_unknown<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
875
14.7k
        match self.function.code {
876
19.7k
            _ if self.function.raw >= ERROR_MASK => self.parse_exception(input),
877
5.15k
            FunctionCode::Diagnostic => self.parse_diagnostic(input),
878
2.35k
            FunctionCode::MEI => self.parse_mei(input),
879
7.27k
            _ => self.parse_bytevec(input),
880
        }
881
19.7k
    }
882
883
    /// Matches this message with another. Used to validate requests with responses.
884
30.7M
    pub fn matches(&mut self, other: &Message) -> bool {
885
30.7M
        if self.transaction_id != other.transaction_id
886
2.76M
            || self.unit_id != other.unit_id
887
1.57M
            || self.function.code != other.function.code
888
329k
            || self.access_type != other.access_type
889
        {
890
30.4M
            return false;
891
329k
        }
892
893
        // This isn't a known function, no validation can be done
894
329k
        if self.category != CodeCategory::PUBLIC_ASSIGNED {
895
158k
            return true;
896
170k
        }
897
898
        // If there was an exception, don't bother trying to validate
899
        // Since we don't know which side is the response, both are checked
900
        // (self.data checked in the match right below)
901
170k
        if let Data::Exception(_) = &other.data {
902
66
            return true;
903
170k
        }
904
905
170k
        match (&self.data, &other.data) {
906
2.34k
            (Data::Exception(_), _) => true,
907
9.65k
            (Data::ByteVec(_), Data::ByteVec(_)) => true,
908
646
            (Data::ByteVec(_), _) => self.error_flags.intersects(ErrorFlags::DATA_LENGTH),
909
3.46k
            (_, Data::ByteVec(_)) => other.error_flags.intersects(ErrorFlags::DATA_LENGTH),
910
            (
911
2.16k
                Data::Read(Read::Response(data)),
912
                Data::Read(Read::Request {
913
                    address: _,
914
2.16k
                    quantity,
915
                }),
916
            ) => {
917
2.16k
                let other_count = usize::from(*quantity);
918
919
2.16k
                if data.len() != (other_count / 8) + usize::from((other_count % 8) != 0) {
920
1.72k
                    self.error_flags |= ErrorFlags::DATA_VALUE;
921
1.72k
                }
922
923
2.16k
                true
924
            }
925
            (
926
1.57k
                Data::Read(Read::Response(data)),
927
                Data::ReadWrite {
928
                    read:
929
                        Read::Request {
930
                            address: _,
931
1.57k
                            quantity,
932
                        },
933
                    write: _,
934
                },
935
            ) => {
936
1.57k
                if data.len() != 2 * usize::from(*quantity) {
937
975
                    self.error_flags |= ErrorFlags::DATA_VALUE;
938
975
                }
939
940
1.57k
                true
941
            }
942
            (
943
                Data::Read(Read::Request {
944
                    address: _,
945
31
                    quantity,
946
                }),
947
31
                Data::Read(Read::Response(data)),
948
            ) => {
949
31
                let count = usize::from(*quantity);
950
951
31
                if data.len() != (count / 8) + usize::from((count % 8) != 0) {
952
31
                    self.error_flags |= ErrorFlags::DATA_VALUE;
953
31
                }
954
955
31
                true
956
            }
957
            (
958
                Data::ReadWrite {
959
                    read:
960
                        Read::Request {
961
                            address: _,
962
1
                            quantity,
963
                        },
964
                    write: _,
965
                },
966
1
                Data::Read(Read::Response(data)),
967
            ) => {
968
1
                if data.len() != 2 * usize::from(*quantity) {
969
1
                    self.error_flags |= ErrorFlags::DATA_VALUE;
970
1
                }
971
972
1
                true
973
            }
974
            (
975
                Data::Write(Write::Other {
976
8.16k
                    address: addr,
977
8.16k
                    data,
978
                }),
979
8.16k
                Data::Write(other_write),
980
8.16k
            ) => match &other_write {
981
                Write::Other {
982
2.66k
                    address: other_addr,
983
2.66k
                    data: other_data,
984
                } => {
985
2.66k
                    if addr != other_addr || data != other_data {
986
1.17k
                        self.error_flags |= ErrorFlags::DATA_VALUE;
987
1.49k
                    }
988
989
2.66k
                    true
990
                }
991
                Write::MultReq {
992
5.50k
                    address: other_addr,
993
5.50k
                    quantity: other_quantity,
994
                    data: _,
995
                } => {
996
5.50k
                    if addr != other_addr || data != other_quantity {
997
2.01k
                        self.error_flags |= ErrorFlags::DATA_VALUE;
998
3.49k
                    }
999
1000
5.50k
                    true
1001
                }
1002
0
                _ => false,
1003
            },
1004
            (
1005
                Data::Write(Write::MultReq {
1006
519
                    address: addr,
1007
519
                    quantity,
1008
                    data: _,
1009
                }),
1010
                Data::Write(Write::Other {
1011
519
                    address: other_addr,
1012
519
                    data: other_data,
1013
                }),
1014
            ) => {
1015
519
                if addr != other_addr || quantity != other_data {
1016
2
                    self.error_flags |= ErrorFlags::DATA_VALUE;
1017
517
                }
1018
1019
519
                true
1020
            }
1021
            (
1022
                Data::Write(Write::Mask {
1023
5.21k
                    address: addr,
1024
5.21k
                    and_mask: and,
1025
5.21k
                    or_mask: or,
1026
                }),
1027
                Data::Write(Write::Mask {
1028
5.21k
                    address: other_addr,
1029
5.21k
                    and_mask: other_and,
1030
5.21k
                    or_mask: other_or,
1031
                }),
1032
            ) => {
1033
5.21k
                if addr != other_addr || and != other_and || or != other_or {
1034
3.19k
                    self.error_flags |= ErrorFlags::DATA_VALUE;
1035
3.19k
                }
1036
1037
5.21k
                true
1038
            }
1039
            (
1040
123k
                Data::Diagnostic { func, data: _ },
1041
                Data::Diagnostic {
1042
123k
                    func: other_func,
1043
                    data: _,
1044
                },
1045
123k
            ) => func == other_func,
1046
            (
1047
2.96k
                Data::MEI { mei_type, data: _ },
1048
                Data::MEI {
1049
2.96k
                    mei_type: other_mei,
1050
                    data: _,
1051
                },
1052
2.96k
            ) => mei_type == other_mei,
1053
9.64k
            _ => false,
1054
        }
1055
30.7M
    }
1056
1057
    /// Gets the register/coil/input value at the given address, if it has been
1058
    /// modified in the transaction. Returns the value as Some(u16) if it is found,
1059
    /// otherwise returns None. The address passed in must be offset by 1 to reflect
1060
    /// the actual coil/register and not the address found in the PDU. See the
1061
    /// [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
1062
    /// for more information on addresses.
1063
0
    pub fn get_write_value_at_address(&self, address: u16) -> Option<u16> {
1064
        // Compare the given address with the transaction's address to ensure it is covered
1065
0
        if let Some(range) = self.get_address_range() {
1066
0
            if !range.contains(&address) {
1067
0
                return None;
1068
0
            }
1069
0
        }
1070
1071
0
        if self.access_type.contains(AccessType::SINGLE) {
1072
            // The only functions with AccessType::SINGLE are write functions, limiting the
1073
            // data variant to Write::Other
1074
0
            let data = if let Data::Write(Write::Other { address: _, data }) = &self.data {
1075
0
                *data
1076
            } else {
1077
0
                return None;
1078
            };
1079
1080
0
            if self.access_type.contains(AccessType::COILS) {
1081
0
                Some((data != 0) as u16)
1082
            } else {
1083
0
                Some(data)
1084
            }
1085
0
        } else if self.access_type.contains(AccessType::MULTIPLE) {
1086
0
            let (start, data) = match &self.data {
1087
                Data::Write(Write::MultReq {
1088
0
                    address,
1089
                    quantity: _,
1090
0
                    data,
1091
0
                }) => (address, data),
1092
                Data::ReadWrite {
1093
                    read: _,
1094
                    write:
1095
                        Write::MultReq {
1096
0
                            address,
1097
                            quantity: _,
1098
0
                            data,
1099
                        },
1100
0
                } => (address, data),
1101
0
                _ => return None,
1102
            };
1103
1104
0
            if *start == std::u16::MAX || *start >= address {
1105
0
                return None;
1106
0
            }
1107
1108
            // Multiply by two because each register value is 2 bytes
1109
0
            let mut offset = (address - (start + 1)) as usize * 2;
1110
1111
            // In case of Coils, offset is in bit (convert to byte)
1112
0
            if self.access_type.contains(AccessType::COILS) {
1113
0
                offset >>= 3;
1114
0
            }
1115
1116
0
            let mut value =
1117
0
                if let (Some(val1), Some(val2)) = (data.get(offset), data.get(offset + 1)) {
1118
0
                    ((*val1 as u16) << 8) | *val2 as u16
1119
                } else {
1120
0
                    return None;
1121
                };
1122
1123
0
            if self.access_type.contains(AccessType::COILS) {
1124
0
                value = (value >> ((address - (start + 1)) & 0x7)) & 0x1;
1125
0
            }
1126
1127
0
            Some(value)
1128
        } else {
1129
0
            None
1130
        }
1131
0
    }
1132
1133
    /// Gets the address and quantity in the read/write data. If the data does not
1134
    /// match and they can't be found, None is returned.
1135
    /// The range returned is offset by 1 to account to reflect the coils/registers
1136
    /// that start at 1 instead of in the PDU numbers where they start at 0.
1137
    /// More details can be found in the [protocol reference](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf)
1138
6
    pub fn get_address_range(&self) -> Option<RangeInclusive<u16>> {
1139
6
        match &self.data {
1140
0
            Data::Write(Write::Other { address, data: _ })
1141
            | Data::Write(Write::Mask {
1142
0
                address,
1143
                and_mask: _,
1144
                or_mask: _,
1145
0
            }) => Some((address + 1)..=(address + 1)),
1146
4
            Data::Read(Read::Request { address, quantity })
1147
            | Data::Write(Write::MultReq {
1148
0
                address,
1149
0
                quantity,
1150
                data: _,
1151
            })
1152
            | Data::ReadWrite {
1153
                read: _,
1154
                write:
1155
                    Write::MultReq {
1156
0
                        address,
1157
0
                        quantity,
1158
                        data: _,
1159
                    },
1160
            } => {
1161
4
                if *quantity > 0 && *quantity <= std::u16::MAX - address {
1162
4
                    Some((address + 1)..=(address + quantity))
1163
                } else {
1164
0
                    None
1165
                }
1166
            }
1167
2
            _ => None,
1168
        }
1169
6
    }
1170
}
1171
1172
impl Protocol<'_> for Modbus {
1173
    type Message = Message;
1174
1175
0
    fn name() -> &'static str {
1176
0
        "modbus"
1177
0
    }
1178
}
1179
1180
impl<'a> Probe<'a> for Modbus {
1181
30.5k
    fn probe(&self, input: &'a [u8], direction: Direction) -> Status {
1182
30.5k
        match self.parse(input, direction) {
1183
20.6k
            Ok((_, Some(msg))) => {
1184
20.6k
                if msg.error_flags == ErrorFlags::none()
1185
19.2k
                    && (!self.probe_strict || msg.function.code != FunctionCode::Unknown)
1186
                {
1187
19.2k
                    Status::Recognized
1188
                } else {
1189
1.41k
                    Status::Unrecognized
1190
                }
1191
            }
1192
0
            Ok((_, _)) => Status::Recognized,
1193
            Err(Error {
1194
                kind: ErrorKind::Incomplete(_),
1195
9.88k
            }) => Status::Incomplete,
1196
0
            Err(_) => Status::Unrecognized,
1197
        }
1198
30.5k
    }
1199
}
1200
1201
impl<'a> Parse<'a> for Modbus {
1202
1.87M
    fn parse(
1203
1.87M
        &self,
1204
1.87M
        input: &'a [u8],
1205
1.87M
        direction: Direction,
1206
1.87M
    ) -> Result<(&'a [u8], Option<Self::Message>)> {
1207
1.87M
        let (input, transaction_id) = be_u16(input)?;
1208
1.85M
        let (input, protocol_id) = be_u16(input)?;
1209
1.81M
        let mut err_flags = ErrorFlags::none();
1210
1.81M
        if protocol_id != 0 {
1211
428k
            err_flags |= ErrorFlags::PROTO_ID;
1212
1.38M
        }
1213
1214
1.81M
        let (input, length) = be_u16(input)?;
1215
1216
1.78M
        let mut message = Message {
1217
1.78M
            transaction_id,
1218
1.78M
            protocol_id,
1219
1.78M
            length,
1220
1.78M
            unit_id: 0,
1221
1.78M
            function: Function::new(0),
1222
1.78M
            access_type: AccessType::none(),
1223
1.78M
            category: CodeCategory::none(),
1224
1.78M
            data: Data::Empty,
1225
1.78M
            error_flags: err_flags,
1226
1.78M
        };
1227
1228
1.78M
        if !(MIN_LENGTH..=MAX_LENGTH).contains(&length) {
1229
1.47M
            message.error_flags |= ErrorFlags::DATA_LENGTH;
1230
1.47M
            if input.len() > usize::from(length) {
1231
1.29M
                return Ok((&input[usize::from(length)..input.len()], Some(message)));
1232
            } else {
1233
181k
                return Ok((&[], Some(message)));
1234
            }
1235
309k
        }
1236
1237
309k
        let (input, data) = take(length)(input)?;
1238
275k
        let (data, unit_id) = be_u8(data)?;
1239
275k
        let (data, raw_func) = be_u8(data)?;
1240
275k
        message.unit_id = unit_id;
1241
275k
        message.function = Function::new(raw_func);
1242
275k
        message.access_type = message.function.code.into();
1243
1244
275k
        let result = match direction {
1245
129k
            Direction::ToServer => message.parse_request(data),
1246
126k
            Direction::ToClient => message.parse_response(data),
1247
19.7k
            Direction::Unknown => message.parse_unknown(data),
1248
        };
1249
24.1k
        match result {
1250
251k
            Ok(rest) => {
1251
251k
                if !rest.is_empty() {
1252
49.5k
                    message.error_flags |= ErrorFlags::DATA_LENGTH;
1253
202k
                }
1254
            }
1255
            Err(Error {
1256
                kind: ErrorKind::Incomplete(_),
1257
            }) => {
1258
24.1k
                message.error_flags |= ErrorFlags::DATA_LENGTH;
1259
24.1k
                if message.data == Data::Empty {
1260
23.1k
                    message.data = Data::ByteVec(data.to_vec());
1261
23.1k
                }
1262
            }
1263
0
            Err(err) => return Err(err),
1264
        }
1265
1266
275k
        message.category = Flags::from(&message);
1267
1268
275k
        Ok((input, Some(message)))
1269
1.87M
    }
1270
}
1271
1272
#[cfg(test)]
1273
mod tests {
1274
    use super::*;
1275
    use rstest::rstest;
1276
    use sawp::error::{Error, Result};
1277
    use sawp::probe::Status;
1278
    use std::str::FromStr;
1279
1280
    #[test]
1281
    fn test_name() {
1282
        assert_eq!(Modbus::name(), "modbus");
1283
    }
1284
1285
    #[rstest(
1286
        input,
1287
        expected,
1288
        case::empty(b"", Err(Error::incomplete_needed(2))),
1289
        case::hello_world(
1290
            b"hello world",
1291
            Ok((0, Some(Message{
1292
                transaction_id: 26725,
1293
                protocol_id: 27756,
1294
                length: 28448,
1295
                unit_id: 0,
1296
                function: Function { raw: 0, code: FunctionCode::Unknown },
1297
                access_type: AccessType::none(),
1298
                category: CodeCategory::none(),
1299
                data: Data::Empty,
1300
                error_flags: ErrorFlags::PROTO_ID | ErrorFlags::DATA_LENGTH,
1301
            })))
1302
        ),
1303
        case::diagnostic(
1304
            &[
1305
                // Transaction ID: 1
1306
                0x00, 0x01,
1307
                // Protocol ID: 0
1308
                0x00, 0x00,
1309
                // Length: 6
1310
                0x00, 0x06,
1311
                // Unit ID: 3
1312
                0x03,
1313
                // Function Code: Diagnostics (8)
1314
                0x08,
1315
                // Diagnostic Code: Force Listen Only Mode (4)
1316
                0x00, 0x04,
1317
                // Data: 0000
1318
                0x00, 0x00
1319
            ],
1320
            Ok((0, Some(Message{
1321
                transaction_id: 1,
1322
                protocol_id: 0,
1323
                length: 6,
1324
                unit_id: 3,
1325
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
1326
                access_type: AccessType::none(),
1327
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1328
                data: Data::Diagnostic { func: Diagnostic { raw: 4, code: DiagnosticSubfunction::ForceListenOnlyMode }, data: vec![0x00, 0x00] },
1329
                error_flags: ErrorFlags::none(),
1330
            })))
1331
        ),
1332
        case::diagnostic_missing_subfunc(
1333
            &[
1334
                // Transaction ID: 1
1335
                0x00, 0x01,
1336
                // Protocol ID: 0
1337
                0x00, 0x00,
1338
                // Length: 2
1339
                0x00, 0x02,
1340
                // Unit ID: 3
1341
                0x03,
1342
                // Function Code: Diagnostics (8)
1343
                0x08
1344
            ],
1345
            Ok((0, Some(Message{
1346
                transaction_id: 1,
1347
                protocol_id: 0,
1348
                length: 2,
1349
                unit_id: 3,
1350
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
1351
                access_type: AccessType::none(),
1352
                category: CodeCategory::none(),
1353
                data: Data::Empty,
1354
                error_flags: ErrorFlags::DATA_LENGTH.into(),
1355
            })))
1356
        ),
1357
        case::diagnostic_reserved_1(
1358
            &[
1359
                // Transaction ID: 1
1360
                0x00, 0x01,
1361
                // Protocol ID: 0
1362
                0x00, 0x00,
1363
                // Length: 4
1364
                0x00, 0x04,
1365
                // Unit ID: 3
1366
                0x03,
1367
                // Function Code: Diagnostics (8)
1368
                0x08,
1369
                // Diagnostic Code: Reserved (22)
1370
                0x00, 0x16
1371
            ],
1372
            Ok((0, Some(Message{
1373
                transaction_id: 1,
1374
                protocol_id: 0,
1375
                length: 4,
1376
                unit_id: 3,
1377
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
1378
                access_type: AccessType::none(),
1379
                category: CodeCategory::RESERVED.into(),
1380
                data: Data::Diagnostic { func: Diagnostic { raw: 22, code: DiagnosticSubfunction::Reserved }, data: vec![] },
1381
                error_flags: ErrorFlags::none(),
1382
            })))
1383
        ),
1384
        case::diagnostic_reserved_2(
1385
            &[
1386
                // Transaction ID: 1
1387
                0x00, 0x01,
1388
                // Protocol ID: 0
1389
                0x00, 0x00,
1390
                // Length: 4
1391
                0x00, 0x04,
1392
                // Unit ID: 3
1393
                0x03,
1394
                // Function Code: Diagnostics (8)
1395
                0x08,
1396
                // Diagnostic Code: Reserved (5)
1397
                0x00, 0x05
1398
            ],
1399
            Ok((0, Some(Message{
1400
                transaction_id: 1,
1401
                protocol_id: 0,
1402
                length: 4,
1403
                unit_id: 3,
1404
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
1405
                access_type: AccessType::none(),
1406
                category: CodeCategory::RESERVED.into(),
1407
                data: Data::Diagnostic { func: Diagnostic { raw: 5, code: DiagnosticSubfunction::Reserved }, data: vec![] },
1408
                error_flags: ErrorFlags::none(),
1409
            })))
1410
        ),
1411
        case::diagnostic_reserved_3(
1412
            &[
1413
                // Transaction ID: 1
1414
                0x00, 0x01,
1415
                // Protocol ID: 0
1416
                0x00, 0x00,
1417
                // Length: 4
1418
                0x00, 0x04,
1419
                // Unit ID: 3
1420
                0x03,
1421
                // Function Code: Diagnostics (8)
1422
                0x08,
1423
                // Diagnostic Code: Reserved (9)
1424
                0x00, 0x09
1425
            ],
1426
            Ok((0, Some(Message{
1427
                transaction_id: 1,
1428
                protocol_id: 0,
1429
                length: 4,
1430
                unit_id: 3,
1431
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
1432
                access_type: AccessType::none(),
1433
                category: CodeCategory::RESERVED.into(),
1434
                data: Data::Diagnostic { func: Diagnostic { raw: 9, code: DiagnosticSubfunction::Reserved }, data: vec![] },
1435
                error_flags: ErrorFlags::none(),
1436
            })))
1437
        ),
1438
        case::gateway_exception(
1439
            &[
1440
                // Transaction ID: 0
1441
                0x00, 0x00,
1442
                // Protocol ID: 0
1443
                0x00, 0x00,
1444
                // Length: 3
1445
                0x00, 0x03,
1446
                // Unit ID: 8
1447
                0x08,
1448
                // Function Code: Diagnostics (8) -- Exception
1449
                0x88,
1450
                // Exception Code: Gateway target device failed to respond (11)
1451
                0x0b
1452
            ],
1453
            Ok((0, Some(Message{
1454
                transaction_id: 0,
1455
                protocol_id: 0,
1456
                length: 3,
1457
                unit_id: 8,
1458
                function: Function { raw: 136, code: FunctionCode::Diagnostic },
1459
                access_type: AccessType::none(),
1460
                category: CodeCategory::none(),
1461
                data: Data::Exception(Exception { raw: 11, code: ExceptionCode::GatewayTargetFailToResp }),
1462
                error_flags: ErrorFlags::none(),
1463
            })))
1464
        ),
1465
        case::illegal_data_addr(
1466
            &[
1467
                // Transaction ID: 0
1468
                0x00, 0x00,
1469
                // Protocol ID: 0
1470
                0x00, 0x00,
1471
                // Length: 3
1472
                0x00, 0x03,
1473
                // Unit ID: 8
1474
                0x01,
1475
                // Function Code: Read Coils (1) -- Exception
1476
                0x81,
1477
                // Exception Code: Illegal Data Address (2)
1478
                0x02
1479
            ],
1480
            Ok((0, Some(Message{
1481
                transaction_id: 0,
1482
                protocol_id: 0,
1483
                length: 3,
1484
                unit_id: 1,
1485
                function: Function { raw: 129, code: FunctionCode::RdCoils },
1486
                access_type: AccessType::READ | AccessType::COILS,
1487
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1488
                data: Data::Exception(Exception { raw: 2, code: ExceptionCode::IllegalDataAddr }),
1489
                error_flags: ErrorFlags::none(),
1490
            })))
1491
        ),
1492
        case::exception_unknown(
1493
            &[
1494
                // Transaction ID: 0
1495
                0x00, 0x00,
1496
                // Protocol ID: 0
1497
                0x00, 0x00,
1498
                // Length: 3
1499
                0x00, 0x03,
1500
                // Unit ID: 8
1501
                0x08,
1502
                // Function Code: Unknown (228) -- Exception
1503
                0xe4,
1504
                // Exception Code: Unknown (12)
1505
                0x0c
1506
            ],
1507
            Ok((0, Some(Message{
1508
                transaction_id: 0,
1509
                protocol_id: 0,
1510
                length: 3,
1511
                unit_id: 8,
1512
                function: Function { raw: 228, code: FunctionCode::Unknown },
1513
                access_type: AccessType::none(),
1514
                category: CodeCategory::none(),
1515
                data: Data::Exception(Exception { raw: 12, code: ExceptionCode::Unknown }),
1516
                error_flags: ErrorFlags::EXC_CODE.into(),
1517
            })))
1518
        ),
1519
        case::exception_missing_code(
1520
            &[
1521
                // Transaction ID: 0
1522
                0x00, 0x00,
1523
                // Protocol ID: 0
1524
                0x00, 0x00,
1525
                // Length: 2
1526
                0x00, 0x02,
1527
                // Unit ID: 8
1528
                0x08,
1529
                // Function Code: Diagnostics (8) -- Exception
1530
                0x88
1531
            ],
1532
            Ok((0, Some(Message{
1533
                transaction_id: 0,
1534
                protocol_id: 0,
1535
                length: 2,
1536
                unit_id: 8,
1537
                function: Function { raw: 136, code: FunctionCode::Diagnostic },
1538
                access_type: AccessType::none(),
1539
                category: CodeCategory::none(),
1540
                data: Data::ByteVec(Vec::new()),
1541
                error_flags: ErrorFlags::DATA_LENGTH.into(),
1542
            })))
1543
        ),
1544
        case::exception_with_extra(
1545
            &[
1546
                // Transaction ID: 0
1547
                0x00, 0x00,
1548
                // Protocol ID: 0
1549
                0x00, 0x00,
1550
                // Length: 3
1551
                0x00, 0x03,
1552
                // Unit ID: 8
1553
                0x08,
1554
                // Function Code: Diagnostics (8) -- Exception
1555
                0x88,
1556
                // Exception Code: Gateway target device failed to respond (11)
1557
                0x0b,
1558
                // Extra: 00
1559
                0x00
1560
            ],
1561
            Ok((1, Some(Message{
1562
                transaction_id: 0,
1563
                protocol_id: 0,
1564
                length: 3,
1565
                unit_id: 8,
1566
                function: Function { raw: 136, code: FunctionCode::Diagnostic },
1567
                access_type: AccessType::none(),
1568
                category: CodeCategory::none(),
1569
                data: Data::Exception(Exception { raw: 11, code: ExceptionCode::GatewayTargetFailToResp }),
1570
                error_flags: ErrorFlags::none(),
1571
            })))
1572
        ),
1573
        case::exception_invalid_length(
1574
            &[
1575
                // Transaction ID: 0
1576
                0x00, 0x00,
1577
                // Protocol ID: 4
1578
                0x00, 0x04,
1579
                // Length: 2
1580
                0x00, 0x02,
1581
                // Unit ID: 8
1582
                0x08,
1583
                // Function Code: Diagnostics (8) -- Exception
1584
                0x88,
1585
                // Exception Code: Gateway target device failed to respond (11)
1586
                0x0b
1587
            ],
1588
            Ok((1, Some(Message{
1589
                transaction_id: 0,
1590
                protocol_id: 4,
1591
                length: 2,
1592
                unit_id: 8,
1593
                function: Function { raw: 136, code: FunctionCode::Diagnostic },
1594
                access_type: AccessType::none(),
1595
                category: CodeCategory::none(),
1596
                data: Data::ByteVec([].to_vec()),
1597
                error_flags: ErrorFlags::PROTO_ID | ErrorFlags::DATA_LENGTH,
1598
            })))
1599
        ),
1600
        case::server_id(
1601
            &[
1602
                // Transaction ID: 1
1603
                0x00, 0x01,
1604
                // Protocol ID: 0
1605
                0x00, 0x00,
1606
                // Length: 2
1607
                0x00, 0x02,
1608
                // Unit ID: 1
1609
                0x01,
1610
                // Function Code: Report Server ID (17)
1611
                0x11
1612
            ],
1613
            Ok((0, Some(Message{
1614
                transaction_id: 1,
1615
                protocol_id: 0,
1616
                length: 2,
1617
                unit_id: 1,
1618
                function: Function { raw: 17, code: FunctionCode::ReportServerID },
1619
                access_type: AccessType::none(),
1620
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1621
                data: Data::ByteVec(vec![]),
1622
                error_flags: ErrorFlags::none(),
1623
            })))
1624
        ),
1625
        case::server_id_with_extra(
1626
            &[
1627
                // Transaction ID: 1
1628
                0x00, 0x01,
1629
                // Protocol ID: 0
1630
                0x00, 0x00,
1631
                // Length: 2
1632
                0x00, 0x02,
1633
                // Unit ID: 1
1634
                0x01,
1635
                // Function Code: Report Server ID (17)
1636
                0x11,
1637
                // Extra: 05 06 07
1638
                0x05, 0x06, 0x07
1639
            ],
1640
            Ok((3, Some(Message{
1641
                transaction_id: 1,
1642
                protocol_id: 0,
1643
                length: 2,
1644
                unit_id: 1,
1645
                function: Function { raw: 17, code: FunctionCode::ReportServerID },
1646
                access_type: AccessType::none(),
1647
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1648
                data: Data::ByteVec(vec![]),
1649
                error_flags: ErrorFlags::none(),
1650
            })))
1651
        ),
1652
        case::invalid_length(
1653
            &[
1654
                // Transaction ID: 1
1655
                0x00, 0x01,
1656
                // Protocol ID: 0
1657
                0x00, 0x00,
1658
                // Length: 1
1659
                0x00, 0x01,
1660
                // Unit ID: 1
1661
                0x01,
1662
                // Function Code: Report Server ID (17)
1663
                0x11
1664
            ],
1665
            Ok((1, Some(Message{
1666
                transaction_id: 1,
1667
                protocol_id: 0,
1668
                length: 1,
1669
                unit_id: 0,
1670
                function: Function { raw: 0, code: FunctionCode::Unknown },
1671
                access_type: AccessType::none(),
1672
                category: CodeCategory::none(),
1673
                data: Data::Empty,
1674
                error_flags: ErrorFlags::DATA_LENGTH.into(),
1675
            })))
1676
        ),
1677
        case::unknown_func(
1678
            &[
1679
                // Transaction ID: 1
1680
                0x00, 0x01,
1681
                // Protocol ID: 0
1682
                0x00, 0x00,
1683
                // Length: 2
1684
                0x00, 0x02,
1685
                // Unit ID: 1
1686
                0x01,
1687
                // Function Code: Unknown (100)
1688
                0x64
1689
            ],
1690
            Ok((0, Some(Message{
1691
                transaction_id: 1,
1692
                protocol_id: 0,
1693
                length: 2,
1694
                unit_id: 1,
1695
                function: Function { raw: 100, code: FunctionCode::Unknown },
1696
                access_type: AccessType::none(),
1697
                category: CodeCategory::USER_DEFINED.into(),
1698
                data: Data::ByteVec(vec![]),
1699
                error_flags: ErrorFlags::none(),
1700
            })))
1701
        ),
1702
        case::unknown_func_with_extra(
1703
            &[
1704
                // Transaction ID: 1
1705
                0x00, 0x01,
1706
                // Protocol ID: 0
1707
                0x00, 0x00,
1708
                // Length: 2
1709
                0x00, 0x02,
1710
                // Unit ID: 1
1711
                0x01,
1712
                // Function Code: Unknown (100)
1713
                0x64,
1714
                // Extra: 0000
1715
                0x00, 0x00
1716
            ],
1717
            Ok((2, Some(Message{
1718
                transaction_id: 1,
1719
                protocol_id: 0,
1720
                length: 2,
1721
                unit_id: 1,
1722
                function: Function { raw: 100, code: FunctionCode::Unknown },
1723
                access_type: AccessType::none(),
1724
                category: CodeCategory::USER_DEFINED.into(),
1725
                data: Data::ByteVec(vec![]),
1726
                error_flags: ErrorFlags::none(),
1727
            })))
1728
        ),
1729
        case::mei_gen_ref(
1730
            &[
1731
                // Transaction ID: 0
1732
                0x00, 0x00,
1733
                // Protocol ID: 0
1734
                0x00, 0x00,
1735
                // Length: 3
1736
                0x00, 0x03,
1737
                // Unit ID: 1
1738
                0x01,
1739
                // Function Code: Encapsulated Interface Transport (43)
1740
                0x2b,
1741
                // MEI type: CAN Open General Reference Request and Response (13)
1742
                0x0d
1743
            ],
1744
            Ok((0, Some(Message{
1745
                transaction_id: 0,
1746
                protocol_id: 0,
1747
                length: 3,
1748
                unit_id: 1,
1749
                function: Function { raw: 43, code: FunctionCode::MEI },
1750
                access_type: AccessType::none(),
1751
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1752
                data: Data::MEI{ mei_type: MEI { raw: 13, code: MEIType::CANOpenGenRefReqResp }, data: vec![] },
1753
                error_flags: ErrorFlags::none(),
1754
            })))
1755
        ),
1756
        case::mei_gen_ref_with_extra(
1757
            &[
1758
                // Transaction ID: 0
1759
                0x00, 0x00,
1760
                // Protocol ID: 0
1761
                0x00, 0x00,
1762
                // Length: 3
1763
                0x00, 0x03,
1764
                // Unit ID: 1
1765
                0x01,
1766
                // Function Code: Encapsulated Interface Transport (43)
1767
                0x2b,
1768
                // MEI type: CAN Open General Reference Request and Response (13)
1769
                0x0d,
1770
                // Extra: 00
1771
                0x00
1772
            ],
1773
            Ok((1, Some(Message{
1774
                transaction_id: 0,
1775
                protocol_id: 0,
1776
                length: 3,
1777
                unit_id: 1,
1778
                function: Function { raw: 43, code: FunctionCode::MEI },
1779
                access_type: AccessType::none(),
1780
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1781
                data: Data::MEI{ mei_type: MEI { raw: 13, code: MEIType::CANOpenGenRefReqResp }, data: vec![] },
1782
                error_flags: ErrorFlags::none(),
1783
            })))
1784
        ),
1785
        case::mei_gen_ref_with_data(
1786
            &[
1787
                // Transaction ID: 0
1788
                0x00, 0x00,
1789
                // Protocol ID: 0
1790
                0x00, 0x00,
1791
                // Length: 4
1792
                0x00, 0x04,
1793
                // Unit ID: 1
1794
                0x01,
1795
                // Function Code: Encapsulated Interface Transport (43)
1796
                0x2b,
1797
                // MEI type: CAN Open General Reference Request and Response (13)
1798
                0x0d,
1799
                // Data: 00
1800
                0x00
1801
            ],
1802
            Ok((0, Some(Message{
1803
                transaction_id: 0,
1804
                protocol_id: 0,
1805
                length: 4,
1806
                unit_id: 1,
1807
                function: Function { raw: 43, code: FunctionCode::MEI },
1808
                access_type: AccessType::none(),
1809
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1810
                data: Data::MEI{ mei_type: MEI { raw: 13, code: MEIType::CANOpenGenRefReqResp }, data: vec![0x00] },
1811
                error_flags: ErrorFlags::none(),
1812
            })))
1813
        ),
1814
        case::mei_invalid_length(
1815
            &[
1816
                // Transaction ID: 0
1817
                0x00, 0x00,
1818
                // Protocol ID: 0
1819
                0x00, 0x00,
1820
                // Length: 2
1821
                0x00, 0x02,
1822
                // Unit ID: 1
1823
                0x01,
1824
                // Function Code: Encapsulated Interface Transport (43)
1825
                0x2b,
1826
                // MEI type: CAN Open General Reference Request and Response (13)
1827
                0x0d,
1828
                // Data: 00
1829
                0x00
1830
            ],
1831
            Ok((2, Some(Message{
1832
                transaction_id: 0,
1833
                protocol_id: 0,
1834
                length: 2,
1835
                unit_id: 1,
1836
                function: Function { raw: 43, code: FunctionCode::MEI },
1837
                access_type: AccessType::none(),
1838
                category: CodeCategory::none(),
1839
                data: Data::Empty,
1840
                error_flags: ErrorFlags::DATA_LENGTH.into(),
1841
            })))
1842
        ),
1843
        case::mei_missing_bytes(
1844
            &[
1845
                // Transaction ID: 0
1846
                0x00, 0x00,
1847
                // Protocol ID: 0
1848
                0x00, 0x00,
1849
                // Length: 5
1850
                0x00, 0x05,
1851
                // Unit ID: 1
1852
                0x01,
1853
                // Function Code: Encapsulated Interface Transport (43)
1854
                0x2b,
1855
                // MEI type: CAN Open General Reference Request and Response (13)
1856
                0x0d,
1857
                // Data: 00
1858
                0x00
1859
            ],
1860
            Err(Error::incomplete_needed(1))
1861
        ),
1862
        case::mei_dev_id(
1863
            &[
1864
                // Transaction ID: 0
1865
                0x00, 0x00,
1866
                // Protocol ID: 0
1867
                0x00, 0x00,
1868
                // Length: 4
1869
                0x00, 0x04,
1870
                // Unit ID: 1
1871
                0x01,
1872
                // Function Code: Encapsulated Interface Transport (43)
1873
                0x2b,
1874
                // MEI type: Read Device ID (14)
1875
                0x0e,
1876
                // Data: 00
1877
                0x00
1878
            ],
1879
            Ok((0, Some(Message{
1880
                transaction_id: 0,
1881
                protocol_id: 0,
1882
                length: 4,
1883
                unit_id: 1,
1884
                function: Function { raw: 43, code: FunctionCode::MEI },
1885
                access_type: AccessType::none(),
1886
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
1887
                data: Data::MEI{ mei_type: MEI { raw: 14, code: MEIType::RdDevId }, data: vec![0x00] },
1888
                error_flags: ErrorFlags::none(),
1889
            })))
1890
        ),
1891
        case::mei_unknown(
1892
            &[
1893
                // Transaction ID: 0
1894
                0x00, 0x00,
1895
                // Protocol ID: 0
1896
                0x00, 0x00,
1897
                // Length: 3
1898
                0x00, 0x03,
1899
                // Unit ID: 1
1900
                0x01,
1901
                // Function Code: Encapsulated Interface Transport (43)
1902
                0x2b,
1903
                // MEI type: Unknown (15)
1904
                0x0f
1905
            ],
1906
            Ok((0, Some(Message{
1907
                transaction_id: 0,
1908
                protocol_id: 0,
1909
                length: 3,
1910
                unit_id: 1,
1911
                function: Function { raw: 43, code: FunctionCode::MEI },
1912
                access_type: AccessType::none(),
1913
                category: CodeCategory::RESERVED.into(),
1914
                data: Data::MEI{ mei_type: MEI { raw: 15, code: MEIType::Unknown }, data: vec![] },
1915
                error_flags: ErrorFlags::none(),
1916
            })))
1917
        ),
1918
        case::zero_length(
1919
            &[
1920
                // Transaction ID: 0
1921
                0x00, 0x00,
1922
                // Protocol ID: 0
1923
                0x00, 0x00,
1924
                // Length: 0
1925
                0x00, 0x00
1926
            ],
1927
            Ok((0, Some(Message{
1928
                transaction_id: 0,
1929
                protocol_id: 0,
1930
                length: 0,
1931
                unit_id: 0,
1932
                function: Function { raw: 0, code: FunctionCode::Unknown },
1933
                access_type: AccessType::none(),
1934
                category: CodeCategory::none(),
1935
                data: Data::Empty,
1936
                error_flags: ErrorFlags::DATA_LENGTH.into(),
1937
            })))
1938
        ),
1939
        case::zero_length(
1940
            &[
1941
                // Transaction ID: 0
1942
                0x00, 0x00,
1943
                // Protocol ID: 0
1944
                0x00, 0x00,
1945
                // Length: 0
1946
                0x00, 0x00,
1947
                // Extra: 00 00 00 00
1948
                0x00, 0x00, 0x00, 0x00
1949
            ],
1950
            Ok((4, Some(Message{
1951
                transaction_id: 0,
1952
                protocol_id: 0,
1953
                length: 0,
1954
                unit_id: 0,
1955
                function: Function { raw: 0, code: FunctionCode::Unknown },
1956
                access_type: AccessType::none(),
1957
                category: CodeCategory::none(),
1958
                data: Data::Empty,
1959
                error_flags: ErrorFlags::DATA_LENGTH.into(),
1960
            })))
1961
        ),
1962
        case::missing_bytes(
1963
            &[
1964
                // Transaction ID: 0
1965
                0x00, 0x00,
1966
            ],
1967
            Err(Error::incomplete_needed(2))
1968
        ),
1969
    )]
1970
    fn test_parse(input: &[u8], expected: Result<(usize, Option<<Modbus as Protocol>::Message>)>) {
1971
        let modbus = Modbus::default();
1972
        assert_eq!(
1973
            modbus
1974
                .parse(input, Direction::Unknown)
1975
                .map(|(left, msg)| (left.len(), msg)),
1976
            expected
1977
        );
1978
    }
1979
1980
    #[rstest(
1981
        input,
1982
        expected,
1983
        case::read_coils(
1984
            &[
1985
                // Transaction ID: 1
1986
                0x00, 0x01,
1987
                // Protocol ID: 0
1988
                0x00, 0x00,
1989
                // Length: 6
1990
                0x00, 0x06,
1991
                // Unit ID: 1
1992
                0x01,
1993
                // Function Code: Read Coils (1)
1994
                0x01,
1995
                // Start Address: 0
1996
                0x00, 0x00,
1997
                // Quantity: 1
1998
                0x00, 0x01
1999
            ],
2000
            Ok((0, Some(Message{
2001
                transaction_id: 1,
2002
                protocol_id: 0,
2003
                length: 6,
2004
                unit_id: 1,
2005
                function: Function { raw: 1, code: FunctionCode::RdCoils },
2006
                access_type: AccessType::READ | AccessType::COILS,
2007
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2008
                data: Data::Read (
2009
                    Read::Request {
2010
                        address: 0x0000,
2011
                        quantity: 0x0001
2012
                    }
2013
                ),
2014
                error_flags: ErrorFlags::none(),
2015
            })))
2016
        ),
2017
        case::read_discrete_inputs(
2018
            &[
2019
                // Transaction ID: 1
2020
                0x00, 0x01,
2021
                // Protocol ID: 0
2022
                0x00, 0x00,
2023
                // Length: 6
2024
                0x00, 0x06,
2025
                // Unit ID: 1
2026
                0x01,
2027
                // Function Code: Read Discrete Inputs (2)
2028
                0x02,
2029
                // Start Address: 0
2030
                0x00, 0x01,
2031
                // Quantity: 0
2032
                0x00, 0x00
2033
            ],
2034
            Ok((0, Some(Message {
2035
                transaction_id: 1,
2036
                protocol_id: 0,
2037
                length: 6,
2038
                unit_id: 1,
2039
                function: Function { raw: 2, code: FunctionCode::RdDiscreteInputs },
2040
                access_type: AccessType::READ | AccessType::DISCRETES,
2041
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2042
                data: Data::Read(Read::Request {
2043
                    address: 1,
2044
                    quantity: 0
2045
                }),
2046
                error_flags: ErrorFlags::DATA_VALUE.into(),
2047
            })))
2048
        ),
2049
        case::read_input_regs(
2050
            &[
2051
                // Transaction ID: 1
2052
                0x00, 0x01,
2053
                // Protocol ID: 0
2054
                0x00, 0x00,
2055
                // Length: 6
2056
                0x00, 0x06,
2057
                // Unit ID: 1
2058
                0x01,
2059
                // Function Code: Read Input Registers (4)
2060
                0x04,
2061
                // Start Address: 0
2062
                0x00, 0x01,
2063
                // Quantity
2064
                0xFF, 0xFF
2065
            ],
2066
            Ok((0, Some(Message {
2067
                transaction_id: 1,
2068
                protocol_id: 0,
2069
                length: 6,
2070
                unit_id: 1,
2071
                function: Function { raw: 4, code: FunctionCode::RdInputRegs },
2072
                access_type: AccessType::READ | AccessType::INPUT,
2073
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2074
                data: Data::Read(Read::Request {
2075
                    address: 1,
2076
                    quantity: 65535
2077
                }),
2078
                error_flags: ErrorFlags::DATA_VALUE.into(),
2079
            })))
2080
        ),
2081
        case::read_exception_status(
2082
            &[
2083
                // Transaction ID: 1
2084
                0x00, 0x01,
2085
                // Protocol ID: 0
2086
                0x00, 0x00,
2087
                // Length: 2
2088
                0x00, 0x02,
2089
                // Unit ID: 1
2090
                0x01,
2091
                // Function Code: Read Exception Status (7)
2092
                0x07,
2093
            ],
2094
            Ok((0, Some(Message{
2095
                transaction_id: 1,
2096
                protocol_id: 0,
2097
                length: 2,
2098
                unit_id: 1,
2099
                function: Function { raw: 7, code: FunctionCode::RdExcStatus },
2100
                access_type: AccessType::none(),
2101
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2102
                data: Data::ByteVec(vec![]),
2103
                error_flags: ErrorFlags::none(),
2104
            })))
2105
        ),
2106
        case::read_holding_regs(
2107
            &[
2108
                // Transaction ID: 1
2109
                0x00, 0x01,
2110
                // Protocol ID: 0
2111
                0x00, 0x00,
2112
                // Length: 6
2113
                0x00, 0x06,
2114
                // Unit ID: 1
2115
                0x01,
2116
                // Function Code: Read Holding Registers (3)
2117
                0x03,
2118
                // Start Address: 5
2119
                0x00, 0x05,
2120
                // Quantity: 2
2121
                0x00, 0x02
2122
            ],
2123
            Ok((0, Some(Message{
2124
                transaction_id: 1,
2125
                protocol_id: 0,
2126
                length: 6,
2127
                unit_id: 1,
2128
                function: Function { raw: 3, code: FunctionCode::RdHoldRegs },
2129
                access_type: AccessType::READ | AccessType::HOLDING,
2130
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2131
                data: Data::Read (
2132
                    Read::Request {
2133
                        address: 0x0005,
2134
                        quantity: 0x0002
2135
                    }
2136
                ),
2137
                error_flags: ErrorFlags::none(),
2138
            })))
2139
        ),
2140
        case::write_single_coil(
2141
            &[
2142
                // Transaction ID: 1
2143
                0x00, 0x01,
2144
                // Protocol ID: 0
2145
                0x00, 0x00,
2146
                // Length: 6
2147
                0x00, 0x06,
2148
                // Unit ID: 1
2149
                0x01,
2150
                // Function Code: Write Single Coil (5)
2151
                0x05,
2152
                // Start Address: 2
2153
                0x00, 0x02,
2154
                // Value: 0
2155
                0x00, 0x00
2156
            ],
2157
            Ok((0, Some(Message{
2158
                transaction_id: 1,
2159
                protocol_id: 0,
2160
                length: 6,
2161
                unit_id: 1,
2162
                function: Function { raw: 5, code: FunctionCode::WrSingleCoil },
2163
                access_type: AccessType::WRITE_SINGLE | AccessType::COILS,
2164
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2165
                data: Data::Write (
2166
                    Write::Other {
2167
                        address: 0x0002,
2168
                        data: 0x0000
2169
                    }
2170
                ),
2171
                error_flags: ErrorFlags::none(),
2172
            })))
2173
        ),
2174
        case::write_mult_coils(
2175
            &[
2176
                // Transaction ID: 0
2177
                0x00, 0x00,
2178
                // Protocol ID: 0
2179
                0x00, 0x00,
2180
                // Length: 11
2181
                0x00, 0x09,
2182
                // Unit ID: 1
2183
                0x01,
2184
                // Function Code: Write Multiple Coils (15)
2185
                0x0f,
2186
                // Start Address: 19
2187
                0x00, 0x13,
2188
                // Quantity: 15
2189
                0x00, 0x0a,
2190
                // Byte Count: 2
2191
                0x02,
2192
                // Value
2193
                0xcd, 0x01
2194
            ],
2195
            Ok((0, Some(Message{
2196
                transaction_id: 0,
2197
                protocol_id: 0,
2198
                length: 9,
2199
                unit_id: 1,
2200
                function: Function { raw: 15, code: FunctionCode::WrMultCoils },
2201
                access_type: AccessType::COILS | AccessType::WRITE_MULTIPLE,
2202
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2203
                data: Data::Write (
2204
                    Write::MultReq {
2205
                        address: 0x0013,
2206
                        quantity: 0x000a,
2207
                        data: vec![0xcd, 0x01]
2208
                    }
2209
                ),
2210
                error_flags: ErrorFlags::none(),
2211
            })))
2212
        ),
2213
        case::write_mult_regs(
2214
            &[
2215
                // Transaction ID: 1
2216
                0x00, 0x01,
2217
                // Protocol ID: 0
2218
                0x00, 0x00,
2219
                // Length: 11
2220
                0x00, 0x0b,
2221
                // Unit ID: 1
2222
                0x01,
2223
                // Function Code: Write Multiple Registers (16)
2224
                0x10,
2225
                // Start Address: 3
2226
                0x00, 0x03,
2227
                // Quantity: 2
2228
                0x00, 0x02,
2229
                // Byte Count: 4
2230
                0x04,
2231
                // Value
2232
                0x0a, 0x0b,
2233
                0x00, 0x00
2234
            ],
2235
            Ok((0, Some(Message{
2236
                transaction_id: 1,
2237
                protocol_id: 0,
2238
                length: 11,
2239
                unit_id: 1,
2240
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
2241
                access_type: AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
2242
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2243
                data: Data::Write (
2244
                    Write::MultReq {
2245
                        address: 0x0003,
2246
                        quantity: 0x0002,
2247
                        data: vec![0x0a, 0x0b, 0x00, 0x00]
2248
                    }
2249
                ),
2250
                error_flags: ErrorFlags::none(),
2251
            })))
2252
        ),
2253
        case::write_mult_regs_invalid_length(
2254
            &[
2255
                // Transaction ID: 1
2256
                0x00, 0x01,
2257
                // Protocol ID: 0
2258
                0x00, 0x00,
2259
                // Length: 9
2260
                0x00, 0x09,
2261
                // Unit ID: 1
2262
                0x01,
2263
                // Function Code: Write Multiple Registers (16)
2264
                0x10,
2265
                // Start Address: 3
2266
                0x00, 0x03,
2267
                // Quantity: 2
2268
                0x00, 0x02,
2269
                // Byte Count: 4
2270
                0x04,
2271
                // Value
2272
                0x0a, 0x0b,
2273
                0x00, 0x00
2274
            ],
2275
            Ok((2, Some(Message{
2276
                transaction_id: 1,
2277
                protocol_id: 0,
2278
                length: 9,
2279
                unit_id: 1,
2280
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
2281
                access_type: AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
2282
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2283
                data: Data::Write (
2284
                    Write::MultReq {
2285
                        address: 0x0003,
2286
                        quantity: 0x0002,
2287
                        data: vec![0x0a, 0x0b]
2288
                    }
2289
                ),
2290
                error_flags: ErrorFlags::DATA_LENGTH.into(),
2291
            })))
2292
        ),
2293
        case::read_write_mult_regs(
2294
            &[
2295
                // Transaction ID: 1
2296
                0x00, 0x01,
2297
                // Protocol ID: 0
2298
                0x00, 0x00,
2299
                // Length: 13
2300
                0x00, 0x0d,
2301
                // Unit ID: 1
2302
                0x01,
2303
                // Function Code: Read/Write Multiple Registers (23)
2304
                0x17,
2305
                // Read Address: 1
2306
                0x00, 0x01,
2307
                // Read Quantity: 2
2308
                0x00, 0x02,
2309
                // Write Address: 3
2310
                0x00, 0x03,
2311
                // Write Quantity: 1
2312
                0x00, 0x01,
2313
                // Write Byte Count: 2
2314
                0x02,
2315
                // Write Value
2316
                0x05, 0x06,
2317
            ],
2318
            Ok((0, Some(Message{
2319
                transaction_id: 1,
2320
                protocol_id: 0,
2321
                length: 13,
2322
                unit_id: 1,
2323
                function: Function { raw: 23, code: FunctionCode::RdWrMultRegs },
2324
                access_type: AccessType::READ | AccessType::WRITE_MULTIPLE | AccessType::HOLDING,
2325
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2326
                data: Data::ReadWrite {
2327
                    read: Read::Request {
2328
                        address: 0x0001,
2329
                        quantity: 0x0002
2330
                    },
2331
                    write: Write::MultReq {
2332
                        address: 0x0003,
2333
                        quantity: 0x0001,
2334
                        data: vec![0x05, 0x06]
2335
                    }
2336
                },
2337
                error_flags: ErrorFlags::none(),
2338
            })))
2339
        ),
2340
        case::mask_write_reg(
2341
            &[
2342
                // Transaction ID: 1
2343
                0x00, 0x01,
2344
                // Protocol ID: 0
2345
                0x00, 0x00,
2346
                // Length: 8
2347
                0x00, 0x08,
2348
                // Unit ID: 1
2349
                0x01,
2350
                // Function Code: Mask Write Register (22)
2351
                0x16,
2352
                // Start Address: 1
2353
                0x00, 0x01,
2354
                // And mask: 2
2355
                0x00, 0x02,
2356
                // Or mask: 3
2357
                0x00, 0x03,
2358
            ],
2359
            Ok((0, Some(Message{
2360
                transaction_id: 1,
2361
                protocol_id: 0,
2362
                length: 8,
2363
                unit_id: 1,
2364
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
2365
                access_type: AccessType::WRITE | AccessType::HOLDING,
2366
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2367
                data: Data::Write (
2368
                    Write::Mask {
2369
                        address: 0x0001,
2370
                        and_mask: 0x0002,
2371
                        or_mask: 0x0003
2372
                    }
2373
                ),
2374
                error_flags: ErrorFlags::none(),
2375
            })))
2376
        ),
2377
        case::mask_write_reg_invalid_length(
2378
            &[
2379
                // Transaction ID: 1
2380
                0x00, 0x01,
2381
                // Protocol ID: 0
2382
                0x00, 0x00,
2383
                // Length: 6
2384
                0x00, 0x06,
2385
                // Unit ID: 1
2386
                0x01,
2387
                // Function Code: Mask Write Register (22)
2388
                0x16,
2389
                // Start Address: 1
2390
                0x00, 0x01,
2391
                // And mask: 2
2392
                0x00, 0x02,
2393
            ],
2394
            Ok((0, Some(Message{
2395
                transaction_id: 1,
2396
                protocol_id: 0,
2397
                length: 6,
2398
                unit_id: 1,
2399
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
2400
                access_type: AccessType::WRITE | AccessType::HOLDING,
2401
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2402
                data: Data::ByteVec ([0x00, 0x01, 0x00, 0x02].to_vec()),
2403
                error_flags: ErrorFlags::DATA_LENGTH.into(),
2404
            })))
2405
        ),
2406
        case::mask_write_reg_invalid_length_complete(
2407
            &[
2408
                // Transaction ID: 1
2409
                0x00, 0x01,
2410
                // Protocol ID: 0
2411
                0x00, 0x00,
2412
                // Length: 6
2413
                0x00, 0x06,
2414
                // Unit ID: 1
2415
                0x01,
2416
                // Function Code: Mask Write Register (22)
2417
                0x16,
2418
                // Start Address: 1
2419
                0x00, 0x01,
2420
                // And mask: 2
2421
                0x00, 0x02,
2422
                // Or mask: 3
2423
                0x00, 0x03,
2424
            ],
2425
            Ok((2, Some(Message{
2426
                transaction_id: 1,
2427
                protocol_id: 0,
2428
                length: 6,
2429
                unit_id: 1,
2430
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
2431
                access_type: AccessType::WRITE | AccessType::HOLDING,
2432
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2433
                data: Data::ByteVec ([0x00, 0x01, 0x00, 0x02].to_vec()),
2434
                error_flags: ErrorFlags::DATA_LENGTH.into(),
2435
            })))
2436
        ),
2437
        case::mei_gen_ref(
2438
            &[
2439
                // Transaction ID: 0
2440
                0x00, 0x00,
2441
                // Protocol ID: 0
2442
                0x00, 0x00,
2443
                // Length: 3
2444
                0x00, 0x03,
2445
                // Unit ID: 1
2446
                0x01,
2447
                // Function Code: Encapsulated Interface Transport (43)
2448
                0x2b,
2449
                // MEI type: CAN Open General Reference Request and Response (13)
2450
                0x0d
2451
            ],
2452
            Ok((0, Some(Message{
2453
                transaction_id: 0,
2454
                protocol_id: 0,
2455
                length: 3,
2456
                unit_id: 1,
2457
                function: Function { raw: 43, code: FunctionCode::MEI },
2458
                access_type: AccessType::none(),
2459
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2460
                data: Data::MEI{ mei_type: MEI { raw: 13, code: MEIType::CANOpenGenRefReqResp }, data: vec![] },
2461
                error_flags: ErrorFlags::none(),
2462
            })))
2463
        ),
2464
        case::diagnostic(
2465
            &[
2466
                // Transaction ID: 1
2467
                0x00, 0x01,
2468
                // Protocol ID: 0
2469
                0x00, 0x00,
2470
                // Length: 6
2471
                0x00, 0x06,
2472
                // Unit ID: 3
2473
                0x03,
2474
                // Function Code: Diagnostics (8)
2475
                0x08,
2476
                // Diagnostic Code: Force Listen Only Mode (4)
2477
                0x00, 0x04,
2478
                // Data: 0000
2479
                0x00, 0x00
2480
            ],
2481
            Ok((0, Some(Message{
2482
                transaction_id: 1,
2483
                protocol_id: 0,
2484
                length: 6,
2485
                unit_id: 3,
2486
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
2487
                access_type: AccessType::none(),
2488
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2489
                data: Data::Diagnostic { func: Diagnostic { raw: 4, code: DiagnosticSubfunction::ForceListenOnlyMode }, data: vec![0x00, 0x00] },
2490
                error_flags: ErrorFlags::none(),
2491
            })))
2492
        ),
2493
        case::diagnostic_invalid_value(
2494
            &[
2495
                // Transaction ID: 1
2496
                0x00, 0x01,
2497
                // Protocol ID: 0
2498
                0x00, 0x00,
2499
                // Length: 6
2500
                0x00, 0x06,
2501
                // Unit ID: 3
2502
                0x03,
2503
                // Function Code: Diagnostics (8)
2504
                0x08,
2505
                // Diagnostic Code: Restart Communications Option (1)
2506
                0x00, 0x01,
2507
                // Data: 0000
2508
                0x01, 0x00
2509
            ],
2510
            Ok((0, Some(Message{
2511
                transaction_id: 1,
2512
                protocol_id: 0,
2513
                length: 6,
2514
                unit_id: 3,
2515
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
2516
                access_type: AccessType::none(),
2517
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2518
                data: Data::Diagnostic { func: Diagnostic { raw: 1, code: DiagnosticSubfunction::RestartCommOpt }, data: vec![0x01, 0x00] },
2519
                error_flags: ErrorFlags::DATA_VALUE.into(),
2520
            })))
2521
        ),
2522
        case::diagnostic_missing_subfunc(
2523
            &[
2524
                // Transaction ID: 1
2525
                0x00, 0x01,
2526
                // Protocol ID: 0
2527
                0x00, 0x00,
2528
                // Length: 2
2529
                0x00, 0x02,
2530
                // Unit ID: 3
2531
                0x03,
2532
                // Function Code: Diagnostics (8)
2533
                0x08
2534
            ],
2535
            Ok((0, Some(Message{
2536
                transaction_id: 1,
2537
                protocol_id: 0,
2538
                length: 2,
2539
                unit_id: 3,
2540
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
2541
                access_type: AccessType::none(),
2542
                category: CodeCategory::none(),
2543
                data: Data::Empty,
2544
                error_flags: ErrorFlags::DATA_LENGTH.into(),
2545
            })))
2546
        ),
2547
        case::diagnostic_reserved(
2548
            &[
2549
                // Transaction ID: 1
2550
                0x00, 0x01,
2551
                // Protocol ID: 0
2552
                0x00, 0x00,
2553
                // Length: 4
2554
                0x00, 0x06,
2555
                // Unit ID: 3
2556
                0x03,
2557
                // Function Code: Diagnostics (8)
2558
                0x08,
2559
                // Diagnostic Code: Reserved (22)
2560
                0x00, 0x16,
2561
                0x00, 0x00
2562
            ],
2563
            Ok((0, Some(Message{
2564
                transaction_id: 1,
2565
                protocol_id: 0,
2566
                length: 6,
2567
                unit_id: 3,
2568
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
2569
                access_type: AccessType::none(),
2570
                category: CodeCategory::RESERVED.into(),
2571
                data: Data::Diagnostic { func: Diagnostic { raw: 22, code: DiagnosticSubfunction::Reserved }, data: vec![0x00, 0x00] },
2572
                error_flags: ErrorFlags::none(),
2573
            })))
2574
        ),
2575
    )]
2576
    fn test_request(
2577
        input: &[u8],
2578
        expected: Result<(usize, Option<<Modbus as Protocol>::Message>)>,
2579
    ) {
2580
        let modbus = Modbus::default();
2581
        assert_eq!(
2582
            modbus
2583
                .parse(input, sawp::parser::Direction::ToServer)
2584
                .map(|(left, msg)| (left.len(), msg)),
2585
            expected
2586
        );
2587
    }
2588
2589
    #[rstest(
2590
        input,
2591
        expected,
2592
        case::read_coils(
2593
            &[
2594
                // Transaction ID: 1
2595
                0x00, 0x01,
2596
                // Protocol ID: 0
2597
                0x00, 0x00,
2598
                // Length: 4
2599
                0x00, 0x04,
2600
                // Unit ID: 1
2601
                0x01,
2602
                // Function Code: Read Coils (1)
2603
                0x01,
2604
                // Byte Count: 1
2605
                0x01,
2606
                // Data
2607
                0x00
2608
            ],
2609
            Ok((0, Some(Message{
2610
                transaction_id: 1,
2611
                protocol_id: 0,
2612
                length: 4,
2613
                unit_id: 1,
2614
                function: Function { raw: 1, code: FunctionCode::RdCoils },
2615
                access_type: AccessType::READ | AccessType::COILS,
2616
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2617
                data: Data::Read(Read::Response(vec![0x00])),
2618
                error_flags: ErrorFlags::none(),
2619
            })))
2620
        ),
2621
        case::read_holding_regs(
2622
            &[
2623
                // Transaction ID: 1
2624
                0x00, 0x01,
2625
                // Protocol ID: 0
2626
                0x00, 0x00,
2627
                // Length: 7
2628
                0x00, 0x07,
2629
                // Unit ID: 1
2630
                0x01,
2631
                // Function Code: Read Holding Registers (3)
2632
                0x03,
2633
                // Byte Count: 4
2634
                0x04,
2635
                // Data
2636
                0x00, 0x09,
2637
                0x00, 0x18
2638
            ],
2639
            Ok((0, Some(Message{
2640
                transaction_id: 1,
2641
                protocol_id: 0,
2642
                length: 7,
2643
                unit_id: 1,
2644
                function: Function { raw: 3, code: FunctionCode::RdHoldRegs },
2645
                access_type: AccessType::READ | AccessType::HOLDING,
2646
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2647
                data: Data::Read(Read::Response(vec![0x00, 0x09, 0x00, 0x18])),
2648
                error_flags: ErrorFlags::none(),
2649
            })))
2650
        ),
2651
        case::read_write_mult_regs(
2652
            &[
2653
                // Transaction ID: 1
2654
                0x00, 0x01,
2655
                // Protocol ID: 0
2656
                0x00, 0x00,
2657
                // Length: 5
2658
                0x00, 0x05,
2659
                // Unit ID: 1
2660
                0x01,
2661
                // Function Code: Read/Write Multiple Registers (23)
2662
                0x17,
2663
                // Byte Count: 2
2664
                0x02,
2665
                // Data
2666
                0x0e, 0x0f
2667
            ],
2668
            Ok((0, Some(Message{
2669
                transaction_id: 1,
2670
                protocol_id: 0,
2671
                length: 5,
2672
                unit_id: 1,
2673
                function: Function { raw: 23, code: FunctionCode::RdWrMultRegs },
2674
                access_type: AccessType::READ | AccessType::WRITE_MULTIPLE | AccessType::HOLDING,
2675
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2676
                data: Data::Read(Read::Response(vec![0x0e, 0x0f])),
2677
                error_flags: ErrorFlags::none(),
2678
            })))
2679
        ),
2680
        case::invalid_read_exception_status(
2681
            &[
2682
                // Transaction ID: 1
2683
                0x00, 0x01,
2684
                // Protocol ID: 0
2685
                0x00, 0x00,
2686
                // Length: 4
2687
                0x00, 0x04,
2688
                // Unit ID: 1
2689
                0x01,
2690
                // Function Code: Read Exception Status (7)
2691
                0x07,
2692
                0x00, 0x00
2693
            ],
2694
            Ok((0, Some(Message{
2695
                transaction_id: 1,
2696
                protocol_id: 0,
2697
                length: 4,
2698
                unit_id: 1,
2699
                function: Function { raw: 7, code: FunctionCode::RdExcStatus },
2700
                access_type: AccessType::none(),
2701
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2702
                data: Data::ByteVec(vec![0x00, 0x00]),
2703
                error_flags: ErrorFlags::DATA_LENGTH.into(),
2704
            })))
2705
        ),
2706
        case::write_single_coil(
2707
            &[
2708
                // Transaction ID: 1
2709
                0x00, 0x01,
2710
                // Protocol ID: 0
2711
                0x00, 0x00,
2712
                // Length: 6
2713
                0x00, 0x06,
2714
                // Unit ID: 1
2715
                0x01,
2716
                // Function Code: Write Single Coil (5)
2717
                0x05,
2718
                // Start Address: 2
2719
                0x00, 0x02,
2720
                // Value: 0
2721
                0x00, 0x00
2722
            ],
2723
            Ok((0, Some(Message{
2724
                transaction_id: 1,
2725
                protocol_id: 0,
2726
                length: 6,
2727
                unit_id: 1,
2728
                function: Function { raw: 5, code: FunctionCode::WrSingleCoil },
2729
                access_type: AccessType::WRITE_SINGLE | AccessType::COILS,
2730
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2731
                data: Data::Write(
2732
                    Write::Other {
2733
                        address: 0x0002,
2734
                        data: 0x0000
2735
                    }
2736
                ),
2737
                error_flags: ErrorFlags::none(),
2738
            })))
2739
        ),
2740
        case::write_mult_regs(
2741
            &[
2742
                // Transaction ID: 1
2743
                0x00, 0x01,
2744
                // Protocol ID: 0
2745
                0x00, 0x00,
2746
                // Length: 6
2747
                0x00, 0x06,
2748
                // Unit ID: 1
2749
                0x01,
2750
                // Function Code: Write Multiple Registers (16)
2751
                0x10,
2752
                // Start Address: 3
2753
                0x00, 0x03,
2754
                // Quantity: 4
2755
                0x00, 0x04
2756
            ],
2757
            Ok((0, Some(Message{
2758
                transaction_id: 1,
2759
                protocol_id: 0,
2760
                length: 6,
2761
                unit_id: 1,
2762
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
2763
                access_type: AccessType::WRITE_MULTIPLE | AccessType::HOLDING,
2764
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2765
                data: Data::Write(
2766
                    Write::Other {
2767
                        address: 0x0003,
2768
                        data: 0x0004
2769
                    }
2770
                ),
2771
                error_flags: ErrorFlags::none(),
2772
            })))
2773
        ),
2774
        case::mask_write_reg(
2775
            &[
2776
                // Transaction ID: 1
2777
                0x00, 0x01,
2778
                // Protocol ID: 0
2779
                0x00, 0x00,
2780
                // Length: 8
2781
                0x00, 0x08,
2782
                // Unit ID: 1
2783
                0x01,
2784
                // Function Code: Mask Write Register (22)
2785
                0x16,
2786
                // Start Address: 1
2787
                0x00, 0x01,
2788
                // And mask: 2
2789
                0x00, 0x02,
2790
                // Or mask: 3
2791
                0x00, 0x03,
2792
            ],
2793
            Ok((0, Some(Message{
2794
                transaction_id: 1,
2795
                protocol_id: 0,
2796
                length: 8,
2797
                unit_id: 1,
2798
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
2799
                access_type: AccessType::WRITE | AccessType::HOLDING,
2800
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2801
                data: Data::Write (
2802
                    Write::Mask {
2803
                        address: 0x0001,
2804
                        and_mask: 0x0002,
2805
                        or_mask: 0x0003
2806
                    }
2807
                ),
2808
                error_flags: ErrorFlags::none(),
2809
            })))
2810
        ),
2811
        case::mei_gen_ref(
2812
            &[
2813
                // Transaction ID: 0
2814
                0x00, 0x00,
2815
                // Protocol ID: 0
2816
                0x00, 0x00,
2817
                // Length: 3
2818
                0x00, 0x03,
2819
                // Unit ID: 1
2820
                0x01,
2821
                // Function Code: Encapsulated Interface Transport (43)
2822
                0x2b,
2823
                // MEI type: CAN Open General Reference Request and Response (13)
2824
                0x0d
2825
            ],
2826
            Ok((0, Some(Message{
2827
                transaction_id: 0,
2828
                protocol_id: 0,
2829
                length: 3,
2830
                unit_id: 1,
2831
                function: Function { raw: 43, code: FunctionCode::MEI },
2832
                access_type: AccessType::none(),
2833
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2834
                data: Data::MEI{ mei_type: MEI { raw: 13, code: MEIType::CANOpenGenRefReqResp }, data: vec![] },
2835
                error_flags: ErrorFlags::none(),
2836
            })))
2837
        ),
2838
        case::diagnostic(
2839
            &[
2840
                // Transaction ID: 1
2841
                0x00, 0x01,
2842
                // Protocol ID: 0
2843
                0x00, 0x00,
2844
                // Length: 6
2845
                0x00, 0x06,
2846
                // Unit ID: 3
2847
                0x03,
2848
                // Function Code: Diagnostics (8)
2849
                0x08,
2850
                // Diagnostic Code: Force Listen Only Mode (4)
2851
                0x00, 0x04,
2852
                // Data: 0000
2853
                0x00, 0x00
2854
            ],
2855
            Ok((0, Some(Message{
2856
                transaction_id: 1,
2857
                protocol_id: 0,
2858
                length: 6,
2859
                unit_id: 3,
2860
                function: Function { raw: 8, code: FunctionCode::Diagnostic },
2861
                access_type: AccessType::none(),
2862
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2863
                data: Data::Diagnostic { func: Diagnostic { raw: 4, code: DiagnosticSubfunction::ForceListenOnlyMode }, data: vec![0x00, 0x00] },
2864
                error_flags: ErrorFlags::none(),
2865
            })))
2866
        ),
2867
    )]
2868
    fn test_response(
2869
        input: &[u8],
2870
        expected: Result<(usize, Option<<Modbus as Protocol>::Message>)>,
2871
    ) {
2872
        let modbus = Modbus::default();
2873
        assert_eq!(
2874
            modbus
2875
                .parse(input, sawp::parser::Direction::ToClient)
2876
                .map(|(left, msg)| (left.len(), msg)),
2877
            expected
2878
        );
2879
    }
2880
2881
    #[rstest(
2882
        req,
2883
        resp,
2884
        expected,
2885
        case::read_coils(
2886
            Message{
2887
                transaction_id: 1,
2888
                protocol_id: 0,
2889
                length: 6,
2890
                unit_id: 1,
2891
                function: Function { raw: 1, code: FunctionCode::RdCoils },
2892
                access_type: AccessType::READ | AccessType::COILS,
2893
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2894
                data: Data::Read (
2895
                    Read::Request {
2896
                        address: 0x0000,
2897
                        quantity: 0x0001
2898
                    }
2899
                ),
2900
                error_flags: ErrorFlags::none(),
2901
            },
2902
            Message{
2903
                transaction_id: 1,
2904
                protocol_id: 0,
2905
                length: 4,
2906
                unit_id: 1,
2907
                function: Function { raw: 1, code: FunctionCode::RdCoils },
2908
                access_type: AccessType::READ | AccessType::COILS,
2909
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2910
                data: Data::Read(Read::Response(vec![0x00])),
2911
                error_flags: ErrorFlags::none(),
2912
            },
2913
            true
2914
        ),
2915
        case::write_single_coil(
2916
            Message{
2917
                transaction_id: 1,
2918
                protocol_id: 0,
2919
                length: 6,
2920
                unit_id: 1,
2921
                function: Function { raw: 5, code: FunctionCode::WrSingleCoil },
2922
                access_type: AccessType::WRITE_SINGLE | AccessType::COILS,
2923
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2924
                data: Data::Write (
2925
                    Write::Other {
2926
                        address: 0x0002,
2927
                        data: 0x0000
2928
                    }
2929
                ),
2930
                error_flags: ErrorFlags::none(),
2931
            },
2932
            Message{
2933
                transaction_id: 1,
2934
                protocol_id: 0,
2935
                length: 6,
2936
                unit_id: 1,
2937
                function: Function { raw: 5, code: FunctionCode::WrSingleCoil },
2938
                access_type: AccessType::WRITE_SINGLE | AccessType::COILS,
2939
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2940
                data: Data::Write(
2941
                    Write::Other {
2942
                        address: 0x0002,
2943
                        data: 0x0000
2944
                    }
2945
                ),
2946
                error_flags: ErrorFlags::none(),
2947
            },
2948
            true
2949
        ),
2950
        case::write_mult_regs(
2951
            Message{
2952
                transaction_id: 1,
2953
                protocol_id: 0,
2954
                length: 11,
2955
                unit_id: 1,
2956
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
2957
                access_type: AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
2958
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2959
                data: Data::Write (
2960
                    Write::MultReq {
2961
                        address: 0x0003,
2962
                        quantity: 0x0002,
2963
                        data: vec![0x0a, 0x0b, 0x00, 0x00]
2964
                    }
2965
                ),
2966
                error_flags: ErrorFlags::none(),
2967
            },
2968
            Message{
2969
                transaction_id: 1,
2970
                protocol_id: 0,
2971
                length: 6,
2972
                unit_id: 1,
2973
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
2974
                access_type: AccessType::WRITE_MULTIPLE | AccessType::HOLDING,
2975
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2976
                data: Data::Write(
2977
                    Write::Other {
2978
                        address: 0x0003,
2979
                        data: 0x0004
2980
                    }
2981
                ),
2982
                error_flags: ErrorFlags::none(),
2983
            },
2984
            true
2985
        ),
2986
        case::read_file_record(
2987
            Message{
2988
                transaction_id: 1,
2989
                protocol_id: 0,
2990
                length: 10,
2991
                unit_id: 1,
2992
                function: Function { raw: 20, code: FunctionCode::RdFileRec },
2993
                access_type: AccessType::none(),
2994
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
2995
                data: Data::ByteVec(vec![0x07, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]),
2996
                error_flags: ErrorFlags::none(),
2997
            },
2998
            Message{
2999
                transaction_id: 1,
3000
                protocol_id: 0,
3001
                length: 10,
3002
                unit_id: 1,
3003
                function: Function { raw: 20, code: FunctionCode::RdFileRec },
3004
                access_type: AccessType::none(),
3005
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3006
                data: Data::ByteVec(vec![0x07, 0x07, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00]),
3007
                error_flags: ErrorFlags::none(),
3008
            },
3009
            true
3010
        ),
3011
        case::mask_write_reg(
3012
            Message {
3013
                transaction_id: 1,
3014
                protocol_id: 0,
3015
                length: 8,
3016
                unit_id: 1,
3017
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
3018
                access_type: AccessType::WRITE | AccessType::HOLDING,
3019
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3020
                data: Data::Write (
3021
                    Write::Mask {
3022
                        address: 0x0001,
3023
                        and_mask: 0x0002,
3024
                        or_mask: 0x0003
3025
                    }
3026
                ),
3027
                error_flags: ErrorFlags::none(),
3028
            },
3029
            Message {
3030
                transaction_id: 1,
3031
                protocol_id: 0,
3032
                length: 8,
3033
                unit_id: 1,
3034
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
3035
                access_type: AccessType::WRITE | AccessType::HOLDING,
3036
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3037
                data: Data::Write (
3038
                    Write::Mask {
3039
                        address: 0x0002,
3040
                        and_mask: 0x0002,
3041
                        or_mask: 0x0003
3042
                    }
3043
                ),
3044
                error_flags: ErrorFlags::none(),
3045
            },
3046
            true
3047
        ),
3048
        case::unit_mismatch(
3049
            Message{
3050
                transaction_id: 1,
3051
                protocol_id: 0,
3052
                length: 10,
3053
                unit_id: 2,
3054
                function: Function { raw: 20, code: FunctionCode::RdFileRec },
3055
                access_type: AccessType::none(),
3056
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3057
                data: Data::ByteVec(vec![]),
3058
                error_flags: ErrorFlags::none(),
3059
            },
3060
            Message{
3061
                transaction_id: 1,
3062
                protocol_id: 0,
3063
                length: 10,
3064
                unit_id: 1,
3065
                function: Function { raw: 20, code: FunctionCode::RdFileRec },
3066
                access_type: AccessType::none(),
3067
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3068
                data: Data::ByteVec(vec![]),
3069
                error_flags: ErrorFlags::none(),
3070
            },
3071
            false
3072
        ),
3073
    )]
3074
    fn test_matching(mut req: Message, mut resp: Message, expected: bool) {
3075
        assert_eq!(req.matches(&resp), expected);
3076
        assert_eq!(resp.matches(&req), expected);
3077
    }
3078
3079
    #[rstest(
3080
        msg,
3081
        addr,
3082
        expected,
3083
        case::write_single_coil(
3084
            Message{
3085
                transaction_id: 1,
3086
                protocol_id: 0,
3087
                length: 6,
3088
                unit_id: 1,
3089
                function: Function { raw: 5, code: FunctionCode::WrSingleCoil },
3090
                access_type: AccessType::WRITE_SINGLE | AccessType::COILS,
3091
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3092
                data: Data::Write (
3093
                    Write::Other {
3094
                        address: 0x0002,
3095
                        data: 0x0000
3096
                    }
3097
                ),
3098
                error_flags: ErrorFlags::none(),
3099
            },
3100
            3,
3101
            Some(0)
3102
        ),
3103
        case::write_mult_regs(
3104
            Message{
3105
                transaction_id: 1,
3106
                protocol_id: 0,
3107
                length: 11,
3108
                unit_id: 1,
3109
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
3110
                access_type: AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
3111
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3112
                data: Data::Write (
3113
                    Write::MultReq {
3114
                        address: 0x0003,
3115
                        quantity: 0x0002,
3116
                        data: vec![0x0a, 0x0b, 0x00, 0x00]
3117
                    }
3118
                ),
3119
                error_flags: ErrorFlags::none(),
3120
            },
3121
            4,
3122
            Some(0x0a0b)
3123
        ),
3124
        case::read_file_record(
3125
            Message{
3126
                transaction_id: 1,
3127
                protocol_id: 0,
3128
                length: 10,
3129
                unit_id: 1,
3130
                function: Function { raw: 20, code: FunctionCode::RdFileRec },
3131
                access_type: AccessType::none(),
3132
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3133
                data: Data::ByteVec(vec![0x07, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]),
3134
                error_flags: ErrorFlags::none(),
3135
            },
3136
            0,
3137
            None
3138
        )
3139
    )]
3140
    fn test_write_value_at_address(msg: Message, addr: u16, expected: Option<u16>) {
3141
        assert_eq!(msg.get_write_value_at_address(addr), expected);
3142
    }
3143
3144
    #[rstest(
3145
        msg,
3146
        expected,
3147
        case::write_single_coil(
3148
            Message{
3149
                transaction_id: 1,
3150
                protocol_id: 0,
3151
                length: 6,
3152
                unit_id: 1,
3153
                function: Function { raw: 5, code: FunctionCode::WrSingleCoil },
3154
                access_type: AccessType::WRITE_SINGLE | AccessType::COILS,
3155
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3156
                data: Data::Write (
3157
                    Write::Other {
3158
                        address: 0x0002,
3159
                        data: 0x0000
3160
                    }
3161
                ),
3162
                error_flags: ErrorFlags::none(),
3163
            },
3164
            Some(3..=3)
3165
        ),
3166
        case::write_mult_regs(
3167
            Message{
3168
                transaction_id: 1,
3169
                protocol_id: 0,
3170
                length: 11,
3171
                unit_id: 1,
3172
                function: Function { raw: 16, code: FunctionCode::WrMultRegs },
3173
                access_type: AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
3174
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3175
                data: Data::Write (
3176
                    Write::MultReq {
3177
                        address: 0x0003,
3178
                        quantity: 0x0002,
3179
                        data: vec![0x0a, 0x0b, 0x00, 0x00]
3180
                    }
3181
                ),
3182
                error_flags: ErrorFlags::none(),
3183
            },
3184
            Some(4..=5)
3185
        ),
3186
        case::mask_write_reg(
3187
            Message {
3188
                transaction_id: 1,
3189
                protocol_id: 0,
3190
                length: 8,
3191
                unit_id: 1,
3192
                function: Function { raw: 22, code: FunctionCode::MaskWrReg },
3193
                access_type: AccessType::WRITE | AccessType::HOLDING,
3194
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3195
                data: Data::Write (
3196
                    Write::Mask {
3197
                        address: 0x0001,
3198
                        and_mask: 0x0002,
3199
                        or_mask: 0x0003
3200
                    }
3201
                ),
3202
                error_flags: ErrorFlags::none(),
3203
            },
3204
            Some(2..=2)
3205
        ),
3206
        case::read_file_record(
3207
            Message{
3208
                transaction_id: 1,
3209
                protocol_id: 0,
3210
                length: 10,
3211
                unit_id: 1,
3212
                function: Function { raw: 20, code: FunctionCode::RdFileRec },
3213
                access_type: AccessType::none(),
3214
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3215
                data: Data::ByteVec(vec![0x07, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]),
3216
                error_flags: ErrorFlags::none(),
3217
            },
3218
            None
3219
        ),
3220
        // test with overflow of address + quantity
3221
        case::read_file_record(
3222
            Message{
3223
                transaction_id: 1,
3224
                protocol_id: 0,
3225
                length: 10,
3226
                unit_id: 1,
3227
                function: Function { raw: 1, code: FunctionCode::RdCoils },
3228
                access_type: AccessType::COILS | AccessType::READ,
3229
                category: CodeCategory::PUBLIC_ASSIGNED.into(),
3230
                data: Data::Read (
3231
                    Read::Request {
3232
                        address: 0xA000,
3233
                        quantity: 0xC000,
3234
                    }
3235
                ),
3236
                error_flags: ErrorFlags::none(),
3237
            },
3238
            None
3239
        )
3240
    )]
3241
    fn test_address_range(msg: Message, expected: Option<RangeInclusive<u16>>) {
3242
        assert_eq!(msg.get_address_range(), expected);
3243
    }
3244
3245
    #[rstest(
3246
        input,
3247
        probe_strict,
3248
        expected,
3249
        case::empty(b"", false, Status::Incomplete),
3250
        case::hello_world(b"hello world", false, Status::Unrecognized),
3251
        case::diagnostic(
3252
            &[
3253
                // Transaction ID: 1
3254
                0x00, 0x01,
3255
                // Protocol ID: 0
3256
                0x00, 0x00,
3257
                // Length: 6
3258
                0x00, 0x06,
3259
                // Unit ID: 3
3260
                0x03,
3261
                // Function Code: Diagnostics (8)
3262
                0x08,
3263
                // Diagnostic Code: Force Listen Only Mode (4)
3264
                0x00, 0x04,
3265
                // Data: 0000
3266
                0x00, 0x00
3267
            ],
3268
            false,
3269
            Status::Recognized
3270
        ),
3271
        case::invalid_diagnostic(
3272
            &[
3273
                // Transaction ID: 1
3274
                0x00, 0x01,
3275
                // Protocol ID: 0
3276
                0x00, 0x00,
3277
                // Length: 2
3278
                0x00, 0x02,
3279
                // Unit ID: 3
3280
                0x03,
3281
                // Function Code: Diagnostics (8)
3282
                0x08
3283
            ],
3284
            false,
3285
            Status::Unrecognized
3286
        ),
3287
        case::unknown_func(
3288
            &[
3289
                // Transaction ID: 1
3290
                0x00, 0x01,
3291
                // Protocol ID: 0
3292
                0x00, 0x00,
3293
                // Length: 2
3294
                0x00, 0x02,
3295
                // Unit ID: 1
3296
                0x01,
3297
                // Function Code: Unknown (100)
3298
                0x64
3299
            ],
3300
            false,
3301
            Status::Recognized
3302
        ),
3303
        case::strict_diagnostic(
3304
            &[
3305
                // Transaction ID: 1
3306
                0x00, 0x01,
3307
                // Protocol ID: 0
3308
                0x00, 0x00,
3309
                // Length: 6
3310
                0x00, 0x06,
3311
                // Unit ID: 3
3312
                0x03,
3313
                // Function Code: Diagnostics (8)
3314
                0x08,
3315
                // Diagnostic Code: Force Listen Only Mode (4)
3316
                0x00, 0x04,
3317
                // Data: 0000
3318
                0x00, 0x00
3319
            ],
3320
            true,
3321
            Status::Recognized
3322
        ),
3323
        case::strict_unknown_func(
3324
            &[
3325
                // Transaction ID: 1
3326
                0x00, 0x01,
3327
                // Protocol ID: 0
3328
                0x00, 0x00,
3329
                // Length: 2
3330
                0x00, 0x02,
3331
                // Unit ID: 1
3332
                0x01,
3333
                // Function Code: Unknown (100)
3334
                0x64
3335
            ],
3336
            true,
3337
            Status::Unrecognized
3338
        ),
3339
    )]
3340
    fn test_probe(input: &[u8], probe_strict: bool, expected: Status) {
3341
        let modbus = Modbus { probe_strict };
3342
        assert_eq!(modbus.probe(input, Direction::Unknown), expected);
3343
    }
3344
3345
    #[test]
3346
    fn test_categories() {
3347
        assert_eq!(CodeCategory::PUBLIC_UNASSIGNED, CodeCategory::from_raw(99));
3348
        assert_eq!(CodeCategory::USER_DEFINED, CodeCategory::from_raw(100));
3349
        assert_eq!(CodeCategory::RESERVED, CodeCategory::from_raw(126));
3350
    }
3351
3352
    #[test]
3353
    fn test_access_type() {
3354
        // make sure complex access types didn't get typoed
3355
        assert_eq!(
3356
            AccessType::DISCRETES | AccessType::COILS,
3357
            AccessType::BIT_ACCESS_MASK
3358
        );
3359
        assert_eq!(
3360
            AccessType::DISCRETES | AccessType::COILS | AccessType::INPUT | AccessType::HOLDING,
3361
            AccessType::FUNC_MASK
3362
        );
3363
        assert_eq!(
3364
            AccessType::WRITE | AccessType::SINGLE,
3365
            AccessType::WRITE_SINGLE
3366
        );
3367
        assert_eq!(
3368
            AccessType::WRITE | AccessType::MULTIPLE,
3369
            AccessType::WRITE_MULTIPLE
3370
        );
3371
    }
3372
3373
    #[test]
3374
    fn test_printing() {
3375
        assert_eq!(
3376
            "PUBLIC_ASSIGNED | RESERVED",
3377
            (CodeCategory::PUBLIC_ASSIGNED | CodeCategory::RESERVED).to_string()
3378
        );
3379
        assert_eq!("NONE", CodeCategory::none().to_string());
3380
        assert_eq!(
3381
            "READ | COILS",
3382
            (AccessType::READ | AccessType::COILS).to_string()
3383
        );
3384
        assert_eq!(
3385
            "WRITE | MULTIPLE | WRITE_MULTIPLE",
3386
            AccessType::WRITE_MULTIPLE.to_string()
3387
        );
3388
        assert_eq!(AccessType::from_str("write"), Ok(AccessType::WRITE));
3389
        assert_eq!(AccessType::from_str("writ"), Err(()));
3390
        assert_eq!("RdCoils", FunctionCode::RdCoils.to_string());
3391
        assert_eq!(
3392
            "RetQueryData",
3393
            DiagnosticSubfunction::RetQueryData.to_string()
3394
        );
3395
        assert_eq!("Unknown", MEIType::Unknown.to_string());
3396
        assert_eq!(
3397
            "IllegalFunction",
3398
            ExceptionCode::IllegalFunction.to_string()
3399
        );
3400
    }
3401
}