sawp_modbus/
lib.rs

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.
50pub use sawp_flags::{Flag, Flags};
51
52use sawp::error::{Error, ErrorKind, Result};
53use sawp::parser::{Direction, Parse};
54use sawp::probe::{Probe, Status};
55use sawp::protocol::Protocol;
56
57use sawp_flags::BitFlags;
58
59use nom::bytes::streaming::take;
60use nom::number::streaming::{be_u16, be_u8};
61
62use num_enum::TryFromPrimitive;
63use std::convert::TryFrom;
64use std::ops::RangeInclusive;
65
66/// FFI structs and Accessors
67#[cfg(feature = "ffi")]
68mod ffi;
69
70#[cfg(feature = "ffi")]
71use sawp_ffi::GenerateFFI;
72
73// Used for exception handling -- any function above this is an exception
74const ERROR_MASK: u8 = 0x80;
75// Maximum read/write quantity
76const MAX_QUANTITY_BIT_ACCESS: u16 = 2000;
77const MAX_QUANTITY_WORD_ACCESS: u16 = 125;
78// Valid count range for reading
79const MIN_RD_COUNT: u8 = 1;
80const MAX_RD_COUNT: u8 = 250;
81
82const MIN_LENGTH: u16 = 2;
83const 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#[derive(Copy, Clone, Debug, PartialEq, Eq, BitFlags)]
91pub 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
110impl From<FunctionCode> for Flags<AccessType> {
111    fn from(code: FunctionCode) -> Self {
112        match code {
113            FunctionCode::RdCoils => AccessType::COILS | AccessType::READ,
114            FunctionCode::RdDiscreteInputs => AccessType::DISCRETES | AccessType::READ,
115            FunctionCode::RdHoldRegs => AccessType::HOLDING | AccessType::READ,
116            FunctionCode::RdInputRegs => AccessType::INPUT | AccessType::READ,
117            FunctionCode::WrSingleCoil => AccessType::COILS | AccessType::WRITE_SINGLE,
118            FunctionCode::WrSingleReg => AccessType::HOLDING | AccessType::WRITE_SINGLE,
119            FunctionCode::WrMultCoils => AccessType::COILS | AccessType::WRITE_MULTIPLE,
120            FunctionCode::WrMultRegs => AccessType::HOLDING | AccessType::WRITE_MULTIPLE,
121            FunctionCode::MaskWrReg => AccessType::HOLDING | AccessType::WRITE,
122            FunctionCode::RdWrMultRegs => {
123                AccessType::HOLDING | AccessType::READ | AccessType::WRITE_MULTIPLE
124            }
125            _ => AccessType::none(),
126        }
127    }
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#[derive(Copy, Clone, Debug, PartialEq, Eq, BitFlags)]
134pub enum CodeCategory {
135    PUBLIC_ASSIGNED = 0b0000_0001,
136    PUBLIC_UNASSIGNED = 0b0000_0010,
137    USER_DEFINED = 0b0000_0100,
138    RESERVED = 0b0000_1000,
139}
140
141impl CodeCategory {
142    fn from_raw(id: u8) -> Flags<Self> {
143        match id {
144            0 => CodeCategory::none(),
145            x if x < 9 => CodeCategory::PUBLIC_UNASSIGNED.into(),
146            x if x < 15 => CodeCategory::RESERVED.into(),
147            x if x < 41 => CodeCategory::PUBLIC_UNASSIGNED.into(),
148            x if x < 43 => CodeCategory::RESERVED.into(),
149            x if x < 65 => CodeCategory::PUBLIC_UNASSIGNED.into(),
150            x if x < 73 => CodeCategory::USER_DEFINED.into(),
151            x if x < 90 => CodeCategory::PUBLIC_UNASSIGNED.into(),
152            x if x < 92 => CodeCategory::RESERVED.into(),
153            x if x < 100 => CodeCategory::PUBLIC_UNASSIGNED.into(),
154            x if x < 111 => CodeCategory::USER_DEFINED.into(),
155            x if x < 125 => CodeCategory::PUBLIC_UNASSIGNED.into(),
156            x if x < 128 => CodeCategory::RESERVED.into(),
157            _ => CodeCategory::none(),
158        }
159    }
160}
161
162impl From<&Message> for Flags<CodeCategory> {
163    fn from(msg: &Message) -> Self {
164        match msg.function.code {
165            FunctionCode::Diagnostic => match &msg.data {
166                Data::Diagnostic { func, .. } => {
167                    if func.code == DiagnosticSubfunction::Reserved {
168                        CodeCategory::RESERVED.into()
169                    } else {
170                        CodeCategory::PUBLIC_ASSIGNED.into()
171                    }
172                }
173                _ => CodeCategory::none(),
174            },
175            FunctionCode::MEI => match &msg.data {
176                Data::MEI { mei_type, .. } => {
177                    if mei_type.code == MEIType::Unknown {
178                        CodeCategory::RESERVED.into()
179                    } else {
180                        CodeCategory::PUBLIC_ASSIGNED.into()
181                    }
182                }
183                _ => CodeCategory::none(),
184            },
185            FunctionCode::Unknown => CodeCategory::from_raw(msg.function.raw),
186            _ => CodeCategory::PUBLIC_ASSIGNED.into(),
187        }
188    }
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#[derive(Copy, Clone, Debug, PartialEq, Eq, BitFlags)]
198pub 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, Eq)]
210pub 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
218impl Function {
219    fn new(val: u8) -> Function {
220        Function {
221            raw: val,
222            code: {
223                if val >= ERROR_MASK {
224                    FunctionCode::from_raw(val ^ ERROR_MASK)
225                } else {
226                    FunctionCode::from_raw(val)
227                }
228            },
229        }
230    }
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, Eq, TryFromPrimitive)]
235#[repr(u8)]
236pub 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
265impl std::fmt::Display for FunctionCode {
266    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
267        write!(fmt, "{:?}", self)
268    }
269}
270
271impl FunctionCode {
272    pub fn from_raw(val: u8) -> Self {
273        FunctionCode::try_from(val).unwrap_or(FunctionCode::Unknown)
274    }
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, Eq)]
281pub 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
289impl Diagnostic {
290    fn new(val: u16) -> Diagnostic {
291        Diagnostic {
292            raw: val,
293            code: DiagnosticSubfunction::try_from(val).unwrap_or(DiagnosticSubfunction::Reserved),
294        }
295    }
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, Eq, TryFromPrimitive)]
300#[repr(u16)]
301pub 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
324impl std::fmt::Display for DiagnosticSubfunction {
325    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
326        write!(fmt, "{:?}", self)
327    }
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, Eq)]
334pub 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
342impl MEI {
343    fn new(val: u8) -> MEI {
344        MEI {
345            raw: val,
346            code: MEIType::try_from(val).unwrap_or(MEIType::Unknown),
347        }
348    }
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, Eq, TryFromPrimitive)]
353#[repr(u8)]
354pub enum MEIType {
355    Unknown = 0x00,
356    CANOpenGenRefReqResp = 0x0d,
357    RdDevId = 0x0e,
358}
359
360impl std::fmt::Display for MEIType {
361    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
362        write!(fmt, "{:?}", self)
363    }
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, Eq)]
370pub 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
378impl Exception {
379    fn new(val: u8) -> Exception {
380        Exception {
381            raw: val,
382            code: ExceptionCode::try_from(val).unwrap_or(ExceptionCode::Unknown),
383        }
384    }
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, Eq, TryFromPrimitive)]
389#[repr(u8)]
390pub 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
404impl std::fmt::Display for ExceptionCode {
405    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
406        write!(fmt, "{:?}", self)
407    }
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, Eq)]
414pub 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, Eq)]
423pub 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, Eq)]
446pub 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)]
468pub 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, Eq)]
478pub struct Message {
479    pub transaction_id: u16,
480    pub protocol_id: u16,
481    pub 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
493impl 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    fn data_length(&self) -> u16 {
497        self.length - 2
498    }
499
500    //          Num Bytes  Byte Placement
501    // Code:    1          (0)
502    fn parse_exception<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
503        let (input, exc_code) = be_u8(input)?;
504        let exc = Exception::new(exc_code);
505        match exc.code {
506            ExceptionCode::IllegalDataValue
507                if self.function.code != FunctionCode::Diagnostic
508                    && ((self.function.raw > 6 && self.function.raw < 15)
509                        || (self.function.raw > 16 && self.function.raw < 20)) =>
510            {
511                self.error_flags |= ErrorFlags::EXC_CODE
512            }
513            ExceptionCode::IllegalDataAddr
514                if (self.function.raw > 6 && self.function.raw < 15)
515                    || (self.function.raw > 16 && self.function.raw < 20) =>
516            {
517                self.error_flags |= ErrorFlags::EXC_CODE
518            }
519            ExceptionCode::MemParityErr
520                if self.function.code != FunctionCode::RdFileRec
521                    && self.function.code != FunctionCode::WrFileRec =>
522            {
523                self.error_flags |= ErrorFlags::EXC_CODE
524            }
525            ExceptionCode::Unknown => self.error_flags |= ErrorFlags::EXC_CODE,
526            _ => {}
527        }
528
529        self.data = Data::Exception(exc);
530        Ok(input)
531    }
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    fn parse_diagnostic<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
541        if self.data_length() < 2 {
542            self.error_flags |= ErrorFlags::DATA_LENGTH;
543            return Ok(input);
544        }
545
546        let (input, diag_func) = be_u16(input)?;
547        let (input, rest) = take(self.data_length() - 2)(input)?;
548
549        self.data = Data::Diagnostic {
550            func: Diagnostic::new(diag_func),
551            data: rest.to_vec(),
552        };
553        Ok(input)
554    }
555
556    //                             Num Bytes   Byte Placement
557    //     MEI Code:               2           (0,1)
558    //     Data:                   x           (2..)
559    fn parse_mei<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
560        if self.data_length() < 1 {
561            self.error_flags |= ErrorFlags::DATA_LENGTH;
562            return Ok(input);
563        }
564
565        let (input, raw_mei) = be_u8(input)?;
566        let mei_type = MEI::new(raw_mei);
567        let (input, rest) = take(self.data_length() - 1)(input)?;
568
569        self.data = Data::MEI {
570            mei_type,
571            data: rest.to_vec(),
572        };
573
574        Ok(input)
575    }
576
577    fn parse_bytevec<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
578        let (input, data) = take(self.data_length())(input)?;
579        self.data = Data::ByteVec(data.to_vec());
580        Ok(input)
581    }
582
583    //                     Num Bytes   Byte Placement
584    // Starting Address:   2           (0,1)
585    // Quantity of Regs:   2           (2,3)
586    fn parse_read_request<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
587        let (input, address) = be_u16(input)?;
588        let (input, quantity) = be_u16(input)?;
589
590        if quantity == 0 {
591            self.error_flags |= ErrorFlags::DATA_VALUE;
592        }
593
594        if self.function.code != FunctionCode::RdWrMultRegs && self.data_length() > 4 {
595            self.error_flags |= ErrorFlags::DATA_LENGTH;
596        }
597
598        if self.access_type.intersects(AccessType::BIT_ACCESS_MASK) {
599            if quantity > MAX_QUANTITY_BIT_ACCESS {
600                self.error_flags |= ErrorFlags::DATA_VALUE;
601            }
602        } else if quantity > MAX_QUANTITY_WORD_ACCESS {
603            self.error_flags |= ErrorFlags::DATA_VALUE;
604        }
605
606        self.data = Data::Read(Read::Request { address, quantity });
607        Ok(input)
608    }
609
610    //          Num Bytes  Byte Placement
611    // Count:   1          (0)
612    // Data:    Count      (1..Count + 1)
613    fn parse_read_response<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
614        if self.data_length() < 1 {
615            self.error_flags |= ErrorFlags::DATA_LENGTH;
616            return Ok(input);
617        }
618
619        let (input, count) = be_u8(input)?;
620
621        if !(MIN_RD_COUNT..=MAX_RD_COUNT).contains(&count) {
622            self.error_flags |= ErrorFlags::DATA_VALUE;
623        }
624
625        if self.data_length() - 1 != count.into() {
626            self.error_flags |= ErrorFlags::DATA_VALUE;
627        }
628
629        let (input, data) = take(self.data_length() - 1)(input)?;
630        self.data = Data::Read(Read::Response(data.to_vec()));
631        Ok(input)
632    }
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    fn parse_write_request<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
655        let (input, address) = be_u16(input)?;
656
657        if self.access_type.contains(AccessType::SINGLE) {
658            let (input, data) = be_u16(input)?;
659
660            if self.access_type.contains(AccessType::COILS) && data != 0x0000 && data != 0xff00 {
661                self.error_flags |= ErrorFlags::DATA_VALUE;
662            }
663
664            self.data = Data::Write(Write::Other { address, data });
665            Ok(input)
666        } else if self.access_type.contains(AccessType::MULTIPLE) {
667            let (input, quantity) = be_u16(input)?;
668            let (input, count) = be_u8(input)?;
669
670            let mut offset = 7;
671            if self.function.code == FunctionCode::RdWrMultRegs {
672                offset += 4; // Add 4 bytes for the read section of the request
673            }
674
675            if quantity == 0 || self.length - offset != count.into() {
676                self.error_flags |= ErrorFlags::DATA_LENGTH;
677            }
678
679            if self.access_type.intersects(AccessType::BIT_ACCESS_MASK) {
680                if quantity > MAX_QUANTITY_BIT_ACCESS
681                    || u16::from(count) != (quantity / 8) + u16::from(quantity % 8 != 0)
682                {
683                    self.error_flags |= ErrorFlags::DATA_VALUE;
684                }
685            } else if quantity > MAX_QUANTITY_WORD_ACCESS
686                || u32::from(count) != 2 * u32::from(quantity)
687            {
688                self.error_flags |= ErrorFlags::DATA_VALUE;
689            }
690
691            let (input, data) = take(self.length - offset)(input)?;
692
693            self.data = match &self.data {
694                Data::Read(read) => Data::ReadWrite {
695                    read: read.clone(),
696                    write: Write::MultReq {
697                        address,
698                        quantity,
699                        data: data.to_vec(),
700                    },
701                },
702                _ => Data::Write(Write::MultReq {
703                    address,
704                    quantity,
705                    data: data.to_vec(),
706                }),
707            };
708            Ok(input)
709        } else {
710            let (input, and_mask) = be_u16(input)?;
711            let (input, or_mask) = be_u16(input)?;
712
713            self.data = Data::Write(Write::Mask {
714                address,
715                and_mask,
716                or_mask,
717            });
718            Ok(input)
719        }
720    }
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    fn parse_write_response<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
737        let (input, address) = be_u16(input)?;
738
739        if self.access_type.contains(AccessType::SINGLE) {
740            let (input, data) = be_u16(input)?;
741            self.data = Data::Write(Write::Other { address, data });
742            Ok(input)
743        } else if self.access_type.contains(AccessType::MULTIPLE) {
744            let (input, quantity) = be_u16(input)?;
745            if quantity == 0 {
746                self.error_flags |= ErrorFlags::DATA_VALUE;
747            }
748
749            if self.access_type.intersects(AccessType::BIT_ACCESS_MASK) {
750                if quantity > MAX_QUANTITY_WORD_ACCESS {
751                    self.error_flags |= ErrorFlags::DATA_VALUE;
752                }
753            } else if quantity > MAX_QUANTITY_BIT_ACCESS {
754                self.error_flags |= ErrorFlags::DATA_VALUE;
755            }
756
757            self.data = Data::Write(Write::Other {
758                address,
759                data: quantity,
760            });
761            Ok(input)
762        } else {
763            let (input, and_mask) = be_u16(input)?;
764            let (input, or_mask) = be_u16(input)?;
765
766            self.data = Data::Write(Write::Mask {
767                address,
768                and_mask,
769                or_mask,
770            });
771            Ok(input)
772        }
773    }
774
775    fn parse_request<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
776        match self.function.code {
777            FunctionCode::Diagnostic => {
778                if self.data_length() != 4 {
779                    self.error_flags |= ErrorFlags::DATA_LENGTH;
780                }
781
782                let input = self.parse_diagnostic(input)?;
783                if let Data::Diagnostic { func, data } = &self.data {
784                    if data.len() == 2 {
785                        match func.code {
786                            DiagnosticSubfunction::RetQueryData
787                            | DiagnosticSubfunction::ForceListenOnlyMode
788                            | DiagnosticSubfunction::Reserved => {}
789                            DiagnosticSubfunction::RestartCommOpt => {
790                                if data[1] != 0x00 || (data[0] != 0x00 && data[0] != 0xff) {
791                                    self.error_flags |= ErrorFlags::DATA_VALUE;
792                                }
793                            }
794                            DiagnosticSubfunction::ChangeInputDelimiter => {
795                                if data[1] != 0x00 {
796                                    self.error_flags |= ErrorFlags::DATA_VALUE;
797                                }
798                            }
799                            _ => {
800                                if data[0] != 0x00 || data[1] != 0x00 {
801                                    self.error_flags |= ErrorFlags::DATA_VALUE;
802                                }
803                            }
804                        }
805                    }
806                }
807
808                return Ok(input);
809            }
810            FunctionCode::MEI => return self.parse_mei(input),
811            FunctionCode::RdFileRec | FunctionCode::WrFileRec if self.data_length() == 0 => {
812                self.error_flags |= ErrorFlags::DATA_LENGTH
813            }
814            FunctionCode::RdExcStatus
815            | FunctionCode::GetCommEventCtr
816            | FunctionCode::GetCommEventLog
817            | FunctionCode::ReportServerID
818                if self.data_length() > 0 =>
819            {
820                self.error_flags |= ErrorFlags::DATA_LENGTH
821            }
822            FunctionCode::RdFIFOQueue if self.data_length() != 2 => {
823                self.error_flags |= ErrorFlags::DATA_LENGTH
824            }
825            _ => {
826                if self.function.raw == 0 || self.function.raw >= ERROR_MASK {
827                    self.error_flags |= ErrorFlags::FUNC_CODE;
828                }
829
830                if self.access_type.intersects(AccessType::READ) {
831                    let input = self.parse_read_request(input)?;
832
833                    if self.access_type.intersects(AccessType::WRITE) {
834                        return self.parse_write_request(input);
835                    }
836
837                    return Ok(input);
838                }
839
840                if self.access_type.intersects(AccessType::WRITE) {
841                    return self.parse_write_request(input);
842                }
843            }
844        }
845
846        self.parse_bytevec(input)
847    }
848
849    fn parse_response<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
850        match self.function.code {
851            _ if self.function.raw >= ERROR_MASK => return self.parse_exception(input),
852            FunctionCode::Diagnostic => return self.parse_diagnostic(input),
853            FunctionCode::MEI => return self.parse_mei(input),
854            FunctionCode::RdExcStatus if self.data_length() != 1 => {
855                self.error_flags |= ErrorFlags::DATA_LENGTH
856            }
857            FunctionCode::GetCommEventCtr if self.data_length() != 4 => {
858                self.error_flags |= ErrorFlags::DATA_LENGTH
859            }
860            _ => {
861                if self.access_type.intersects(AccessType::READ) {
862                    return self.parse_read_response(input);
863                }
864
865                if self.access_type.intersects(AccessType::WRITE) {
866                    return self.parse_write_response(input);
867                }
868            }
869        }
870
871        self.parse_bytevec(input)
872    }
873
874    fn parse_unknown<'a>(&mut self, input: &'a [u8]) -> Result<&'a [u8]> {
875        match self.function.code {
876            _ if self.function.raw >= ERROR_MASK => self.parse_exception(input),
877            FunctionCode::Diagnostic => self.parse_diagnostic(input),
878            FunctionCode::MEI => self.parse_mei(input),
879            _ => self.parse_bytevec(input),
880        }
881    }
882
883    /// Matches this message with another. Used to validate requests with responses.
884    pub fn matches(&mut self, other: &Message) -> bool {
885        if self.transaction_id != other.transaction_id
886            || self.unit_id != other.unit_id
887            || self.function.code != other.function.code
888            || self.access_type != other.access_type
889        {
890            return false;
891        }
892
893        // This isn't a known function, no validation can be done
894        if self.category != CodeCategory::PUBLIC_ASSIGNED {
895            return true;
896        }
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        if let Data::Exception(_) = &other.data {
902            return true;
903        }
904
905        match (&self.data, &other.data) {
906            (Data::Exception(_), _) => true,
907            (Data::ByteVec(_), Data::ByteVec(_)) => true,
908            (Data::ByteVec(_), _) => self.error_flags.intersects(ErrorFlags::DATA_LENGTH),
909            (_, Data::ByteVec(_)) => other.error_flags.intersects(ErrorFlags::DATA_LENGTH),
910            (
911                Data::Read(Read::Response(data)),
912                Data::Read(Read::Request {
913                    address: _,
914                    quantity,
915                }),
916            ) => {
917                let other_count = usize::from(*quantity);
918
919                if data.len() != (other_count / 8) + usize::from((other_count % 8) != 0) {
920                    self.error_flags |= ErrorFlags::DATA_VALUE;
921                }
922
923                true
924            }
925            (
926                Data::Read(Read::Response(data)),
927                Data::ReadWrite {
928                    read:
929                        Read::Request {
930                            address: _,
931                            quantity,
932                        },
933                    write: _,
934                },
935            ) => {
936                if data.len() != 2 * usize::from(*quantity) {
937                    self.error_flags |= ErrorFlags::DATA_VALUE;
938                }
939
940                true
941            }
942            (
943                Data::Read(Read::Request {
944                    address: _,
945                    quantity,
946                }),
947                Data::Read(Read::Response(data)),
948            ) => {
949                let count = usize::from(*quantity);
950
951                if data.len() != (count / 8) + usize::from((count % 8) != 0) {
952                    self.error_flags |= ErrorFlags::DATA_VALUE;
953                }
954
955                true
956            }
957            (
958                Data::ReadWrite {
959                    read:
960                        Read::Request {
961                            address: _,
962                            quantity,
963                        },
964                    write: _,
965                },
966                Data::Read(Read::Response(data)),
967            ) => {
968                if data.len() != 2 * usize::from(*quantity) {
969                    self.error_flags |= ErrorFlags::DATA_VALUE;
970                }
971
972                true
973            }
974            (
975                Data::Write(Write::Other {
976                    address: addr,
977                    data,
978                }),
979                Data::Write(other_write),
980            ) => match &other_write {
981                Write::Other {
982                    address: other_addr,
983                    data: other_data,
984                } => {
985                    if addr != other_addr || data != other_data {
986                        self.error_flags |= ErrorFlags::DATA_VALUE;
987                    }
988
989                    true
990                }
991                Write::MultReq {
992                    address: other_addr,
993                    quantity: other_quantity,
994                    data: _,
995                } => {
996                    if addr != other_addr || data != other_quantity {
997                        self.error_flags |= ErrorFlags::DATA_VALUE;
998                    }
999
1000                    true
1001                }
1002                _ => false,
1003            },
1004            (
1005                Data::Write(Write::MultReq {
1006                    address: addr,
1007                    quantity,
1008                    data: _,
1009                }),
1010                Data::Write(Write::Other {
1011                    address: other_addr,
1012                    data: other_data,
1013                }),
1014            ) => {
1015                if addr != other_addr || quantity != other_data {
1016                    self.error_flags |= ErrorFlags::DATA_VALUE;
1017                }
1018
1019                true
1020            }
1021            (
1022                Data::Write(Write::Mask {
1023                    address: addr,
1024                    and_mask: and,
1025                    or_mask: or,
1026                }),
1027                Data::Write(Write::Mask {
1028                    address: other_addr,
1029                    and_mask: other_and,
1030                    or_mask: other_or,
1031                }),
1032            ) => {
1033                if addr != other_addr || and != other_and || or != other_or {
1034                    self.error_flags |= ErrorFlags::DATA_VALUE;
1035                }
1036
1037                true
1038            }
1039            (
1040                Data::Diagnostic { func, data: _ },
1041                Data::Diagnostic {
1042                    func: other_func,
1043                    data: _,
1044                },
1045            ) => func == other_func,
1046            (
1047                Data::MEI { mei_type, data: _ },
1048                Data::MEI {
1049                    mei_type: other_mei,
1050                    data: _,
1051                },
1052            ) => mei_type == other_mei,
1053            _ => false,
1054        }
1055    }
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    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        if let Some(range) = self.get_address_range() {
1066            if !range.contains(&address) {
1067                return None;
1068            }
1069        }
1070
1071        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            let data = if let Data::Write(Write::Other { address: _, data }) = &self.data {
1075                *data
1076            } else {
1077                return None;
1078            };
1079
1080            if self.access_type.contains(AccessType::COILS) {
1081                Some((data != 0) as u16)
1082            } else {
1083                Some(data)
1084            }
1085        } else if self.access_type.contains(AccessType::MULTIPLE) {
1086            let (start, data) = match &self.data {
1087                Data::Write(Write::MultReq {
1088                    address,
1089                    quantity: _,
1090                    data,
1091                }) => (address, data),
1092                Data::ReadWrite {
1093                    read: _,
1094                    write:
1095                        Write::MultReq {
1096                            address,
1097                            quantity: _,
1098                            data,
1099                        },
1100                } => (address, data),
1101                _ => return None,
1102            };
1103
1104            if *start == u16::MAX || *start >= address {
1105                return None;
1106            }
1107
1108            // Multiply by two because each register value is 2 bytes
1109            let mut offset = (address - (start + 1)) as usize * 2;
1110
1111            // In case of Coils, offset is in bit (convert to byte)
1112            if self.access_type.contains(AccessType::COILS) {
1113                offset >>= 3;
1114            }
1115
1116            let mut value =
1117                if let (Some(val1), Some(val2)) = (data.get(offset), data.get(offset + 1)) {
1118                    ((*val1 as u16) << 8) | *val2 as u16
1119                } else {
1120                    return None;
1121                };
1122
1123            if self.access_type.contains(AccessType::COILS) {
1124                value = (value >> ((address - (start + 1)) & 0x7)) & 0x1;
1125            }
1126
1127            Some(value)
1128        } else {
1129            None
1130        }
1131    }
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    pub fn get_address_range(&self) -> Option<RangeInclusive<u16>> {
1139        match &self.data {
1140            Data::Write(Write::Other { address, data: _ })
1141            | Data::Write(Write::Mask {
1142                address,
1143                and_mask: _,
1144                or_mask: _,
1145            }) => Some((address + 1)..=(address + 1)),
1146            Data::Read(Read::Request { address, quantity })
1147            | Data::Write(Write::MultReq {
1148                address,
1149                quantity,
1150                data: _,
1151            })
1152            | Data::ReadWrite {
1153                read: _,
1154                write:
1155                    Write::MultReq {
1156                        address,
1157                        quantity,
1158                        data: _,
1159                    },
1160            } => {
1161                if *quantity > 0 && *quantity <= u16::MAX - address {
1162                    Some((address + 1)..=(address + quantity))
1163                } else {
1164                    None
1165                }
1166            }
1167            _ => None,
1168        }
1169    }
1170}
1171
1172impl Protocol<'_> for Modbus {
1173    type Message = Message;
1174
1175    fn name() -> &'static str {
1176        "modbus"
1177    }
1178}
1179
1180impl<'a> Probe<'a> for Modbus {
1181    fn probe(&self, input: &'a [u8], direction: Direction) -> Status {
1182        match self.parse(input, direction) {
1183            Ok((_, Some(msg))) => {
1184                if msg.error_flags == ErrorFlags::none()
1185                    && (!self.probe_strict || msg.function.code != FunctionCode::Unknown)
1186                {
1187                    Status::Recognized
1188                } else {
1189                    Status::Unrecognized
1190                }
1191            }
1192            Ok((_, _)) => Status::Recognized,
1193            Err(Error {
1194                kind: ErrorKind::Incomplete(_),
1195            }) => Status::Incomplete,
1196            Err(_) => Status::Unrecognized,
1197        }
1198    }
1199}
1200
1201impl<'a> Parse<'a> for Modbus {
1202    fn parse(
1203        &self,
1204        input: &'a [u8],
1205        direction: Direction,
1206    ) -> Result<(&'a [u8], Option<Self::Message>)> {
1207        let (input, transaction_id) = be_u16(input)?;
1208        let (input, protocol_id) = be_u16(input)?;
1209        let mut err_flags = ErrorFlags::none();
1210        if protocol_id != 0 {
1211            err_flags |= ErrorFlags::PROTO_ID;
1212        }
1213
1214        let (input, length) = be_u16(input)?;
1215
1216        let mut message = Message {
1217            transaction_id,
1218            protocol_id,
1219            length,
1220            unit_id: 0,
1221            function: Function::new(0),
1222            access_type: AccessType::none(),
1223            category: CodeCategory::none(),
1224            data: Data::Empty,
1225            error_flags: err_flags,
1226        };
1227
1228        if !(MIN_LENGTH..=MAX_LENGTH).contains(&length) {
1229            message.error_flags |= ErrorFlags::DATA_LENGTH;
1230            if input.len() > usize::from(length) {
1231                return Ok((&input[usize::from(length)..input.len()], Some(message)));
1232            } else {
1233                return Ok((&[], Some(message)));
1234            }
1235        }
1236
1237        let (input, data) = take(length)(input)?;
1238        let (data, unit_id) = be_u8(data)?;
1239        let (data, raw_func) = be_u8(data)?;
1240        message.unit_id = unit_id;
1241        message.function = Function::new(raw_func);
1242        message.access_type = message.function.code.into();
1243
1244        let result = match direction {
1245            Direction::ToServer => message.parse_request(data),
1246            Direction::ToClient => message.parse_response(data),
1247            Direction::Unknown => message.parse_unknown(data),
1248        };
1249        match result {
1250            Ok(rest) => {
1251                if !rest.is_empty() {
1252                    message.error_flags |= ErrorFlags::DATA_LENGTH;
1253                }
1254            }
1255            Err(Error {
1256                kind: ErrorKind::Incomplete(_),
1257            }) => {
1258                message.error_flags |= ErrorFlags::DATA_LENGTH;
1259                if message.data == Data::Empty {
1260                    message.data = Data::ByteVec(data.to_vec());
1261                }
1262            }
1263            Err(err) => return Err(err),
1264        }
1265
1266        message.category = Flags::from(&message);
1267
1268        Ok((input, Some(message)))
1269    }
1270}
1271
1272#[cfg(test)]
1273mod 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}