1#![allow(clippy::unneeded_field_pattern)]
47
48pub 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#[cfg(feature = "ffi")]
68mod ffi;
69
70#[cfg(feature = "ffi")]
71use sawp_ffi::GenerateFFI;
72
73const ERROR_MASK: u8 = 0x80;
75const MAX_QUANTITY_BIT_ACCESS: u16 = 2000;
77const MAX_QUANTITY_WORD_ACCESS: u16 = 125;
78const MIN_RD_COUNT: u8 = 1;
80const MAX_RD_COUNT: u8 = 250;
81
82const MIN_LENGTH: u16 = 2;
83const MAX_LENGTH: u16 = 254;
84
85#[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 BIT_ACCESS_MASK = 0b0000_1100,
102 FUNC_MASK = 0b0011_1100,
104 WRITE_SINGLE = 0b0100_0010,
106 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#[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#[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#[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 pub raw: u8,
213 #[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#[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#[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 pub raw: u16,
284 #[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#[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 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 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#[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 pub raw: u8,
337 #[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#[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#[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 pub raw: u8,
373 #[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#[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#[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#[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 MultReq {
426 address: u16,
427 quantity: u16,
428 data: Vec<u8>,
429 },
430 Mask {
434 address: u16,
435 and_mask: u16,
436 or_mask: u16,
437 },
438 Other { address: u16, data: u16 },
440}
441
442#[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 ByteVec(Vec<u8>),
464 Empty,
465}
466
467#[derive(Debug, Default)]
468pub struct Modbus {
469 pub probe_strict: bool,
472}
473
474#[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 fn data_length(&self) -> u16 {
497 self.length - 2
498 }
499
500 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 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 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 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 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 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; }
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 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 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 if self.category != CodeCategory::PUBLIC_ASSIGNED {
895 return true;
896 }
897
898 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 pub fn get_write_value_at_address(&self, address: u16) -> Option<u16> {
1064 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 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 let mut offset = (address - (start + 1)) as usize * 2;
1110
1111 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 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 0x00, 0x01,
1307 0x00, 0x00,
1309 0x00, 0x06,
1311 0x03,
1313 0x08,
1315 0x00, 0x04,
1317 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 0x00, 0x01,
1336 0x00, 0x00,
1338 0x00, 0x02,
1340 0x03,
1342 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 0x00, 0x01,
1361 0x00, 0x00,
1363 0x00, 0x04,
1365 0x03,
1367 0x08,
1369 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 0x00, 0x01,
1388 0x00, 0x00,
1390 0x00, 0x04,
1392 0x03,
1394 0x08,
1396 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 0x00, 0x01,
1415 0x00, 0x00,
1417 0x00, 0x04,
1419 0x03,
1421 0x08,
1423 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 0x00, 0x00,
1442 0x00, 0x00,
1444 0x00, 0x03,
1446 0x08,
1448 0x88,
1450 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 0x00, 0x00,
1469 0x00, 0x00,
1471 0x00, 0x03,
1473 0x01,
1475 0x81,
1477 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 0x00, 0x00,
1496 0x00, 0x00,
1498 0x00, 0x03,
1500 0x08,
1502 0xe4,
1504 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 0x00, 0x00,
1523 0x00, 0x00,
1525 0x00, 0x02,
1527 0x08,
1529 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 0x00, 0x00,
1548 0x00, 0x00,
1550 0x00, 0x03,
1552 0x08,
1554 0x88,
1556 0x0b,
1558 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 0x00, 0x00,
1577 0x00, 0x04,
1579 0x00, 0x02,
1581 0x08,
1583 0x88,
1585 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 0x00, 0x01,
1604 0x00, 0x00,
1606 0x00, 0x02,
1608 0x01,
1610 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 0x00, 0x01,
1629 0x00, 0x00,
1631 0x00, 0x02,
1633 0x01,
1635 0x11,
1637 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 0x00, 0x01,
1656 0x00, 0x00,
1658 0x00, 0x01,
1660 0x01,
1662 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 0x00, 0x01,
1681 0x00, 0x00,
1683 0x00, 0x02,
1685 0x01,
1687 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 0x00, 0x01,
1706 0x00, 0x00,
1708 0x00, 0x02,
1710 0x01,
1712 0x64,
1714 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 0x00, 0x00,
1733 0x00, 0x00,
1735 0x00, 0x03,
1737 0x01,
1739 0x2b,
1741 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 0x00, 0x00,
1760 0x00, 0x00,
1762 0x00, 0x03,
1764 0x01,
1766 0x2b,
1768 0x0d,
1770 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 0x00, 0x00,
1789 0x00, 0x00,
1791 0x00, 0x04,
1793 0x01,
1795 0x2b,
1797 0x0d,
1799 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 0x00, 0x00,
1818 0x00, 0x00,
1820 0x00, 0x02,
1822 0x01,
1824 0x2b,
1826 0x0d,
1828 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 0x00, 0x00,
1847 0x00, 0x00,
1849 0x00, 0x05,
1851 0x01,
1853 0x2b,
1855 0x0d,
1857 0x00
1859 ],
1860 Err(Error::incomplete_needed(1))
1861 ),
1862 case::mei_dev_id(
1863 &[
1864 0x00, 0x00,
1866 0x00, 0x00,
1868 0x00, 0x04,
1870 0x01,
1872 0x2b,
1874 0x0e,
1876 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 0x00, 0x00,
1895 0x00, 0x00,
1897 0x00, 0x03,
1899 0x01,
1901 0x2b,
1903 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 0x00, 0x00,
1922 0x00, 0x00,
1924 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 0x00, 0x00,
1943 0x00, 0x00,
1945 0x00, 0x00,
1947 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 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 0x00, 0x01,
1987 0x00, 0x00,
1989 0x00, 0x06,
1991 0x01,
1993 0x01,
1995 0x00, 0x00,
1997 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 0x00, 0x01,
2021 0x00, 0x00,
2023 0x00, 0x06,
2025 0x01,
2027 0x02,
2029 0x00, 0x01,
2031 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 0x00, 0x01,
2053 0x00, 0x00,
2055 0x00, 0x06,
2057 0x01,
2059 0x04,
2061 0x00, 0x01,
2063 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 0x00, 0x01,
2085 0x00, 0x00,
2087 0x00, 0x02,
2089 0x01,
2091 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 0x00, 0x01,
2110 0x00, 0x00,
2112 0x00, 0x06,
2114 0x01,
2116 0x03,
2118 0x00, 0x05,
2120 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 0x00, 0x01,
2144 0x00, 0x00,
2146 0x00, 0x06,
2148 0x01,
2150 0x05,
2152 0x00, 0x02,
2154 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 0x00, 0x00,
2178 0x00, 0x00,
2180 0x00, 0x09,
2182 0x01,
2184 0x0f,
2186 0x00, 0x13,
2188 0x00, 0x0a,
2190 0x02,
2192 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 0x00, 0x01,
2217 0x00, 0x00,
2219 0x00, 0x0b,
2221 0x01,
2223 0x10,
2225 0x00, 0x03,
2227 0x00, 0x02,
2229 0x04,
2231 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 0x00, 0x01,
2257 0x00, 0x00,
2259 0x00, 0x09,
2261 0x01,
2263 0x10,
2265 0x00, 0x03,
2267 0x00, 0x02,
2269 0x04,
2271 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 0x00, 0x01,
2297 0x00, 0x00,
2299 0x00, 0x0d,
2301 0x01,
2303 0x17,
2305 0x00, 0x01,
2307 0x00, 0x02,
2309 0x00, 0x03,
2311 0x00, 0x01,
2313 0x02,
2315 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 0x00, 0x01,
2344 0x00, 0x00,
2346 0x00, 0x08,
2348 0x01,
2350 0x16,
2352 0x00, 0x01,
2354 0x00, 0x02,
2356 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 0x00, 0x01,
2381 0x00, 0x00,
2383 0x00, 0x06,
2385 0x01,
2387 0x16,
2389 0x00, 0x01,
2391 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 0x00, 0x01,
2410 0x00, 0x00,
2412 0x00, 0x06,
2414 0x01,
2416 0x16,
2418 0x00, 0x01,
2420 0x00, 0x02,
2422 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 0x00, 0x00,
2441 0x00, 0x00,
2443 0x00, 0x03,
2445 0x01,
2447 0x2b,
2449 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 0x00, 0x01,
2468 0x00, 0x00,
2470 0x00, 0x06,
2472 0x03,
2474 0x08,
2476 0x00, 0x04,
2478 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 0x00, 0x01,
2497 0x00, 0x00,
2499 0x00, 0x06,
2501 0x03,
2503 0x08,
2505 0x00, 0x01,
2507 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 0x00, 0x01,
2526 0x00, 0x00,
2528 0x00, 0x02,
2530 0x03,
2532 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 0x00, 0x01,
2551 0x00, 0x00,
2553 0x00, 0x06,
2555 0x03,
2557 0x08,
2559 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 0x00, 0x01,
2596 0x00, 0x00,
2598 0x00, 0x04,
2600 0x01,
2602 0x01,
2604 0x01,
2606 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 0x00, 0x01,
2625 0x00, 0x00,
2627 0x00, 0x07,
2629 0x01,
2631 0x03,
2633 0x04,
2635 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 0x00, 0x01,
2655 0x00, 0x00,
2657 0x00, 0x05,
2659 0x01,
2661 0x17,
2663 0x02,
2665 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 0x00, 0x01,
2684 0x00, 0x00,
2686 0x00, 0x04,
2688 0x01,
2690 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 0x00, 0x01,
2710 0x00, 0x00,
2712 0x00, 0x06,
2714 0x01,
2716 0x05,
2718 0x00, 0x02,
2720 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 0x00, 0x01,
2744 0x00, 0x00,
2746 0x00, 0x06,
2748 0x01,
2750 0x10,
2752 0x00, 0x03,
2754 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 0x00, 0x01,
2778 0x00, 0x00,
2780 0x00, 0x08,
2782 0x01,
2784 0x16,
2786 0x00, 0x01,
2788 0x00, 0x02,
2790 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 0x00, 0x00,
2815 0x00, 0x00,
2817 0x00, 0x03,
2819 0x01,
2821 0x2b,
2823 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 0x00, 0x01,
2842 0x00, 0x00,
2844 0x00, 0x06,
2846 0x03,
2848 0x08,
2850 0x00, 0x04,
2852 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 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 0x00, 0x01,
3255 0x00, 0x00,
3257 0x00, 0x06,
3259 0x03,
3261 0x08,
3263 0x00, 0x04,
3265 0x00, 0x00
3267 ],
3268 false,
3269 Status::Recognized
3270 ),
3271 case::invalid_diagnostic(
3272 &[
3273 0x00, 0x01,
3275 0x00, 0x00,
3277 0x00, 0x02,
3279 0x03,
3281 0x08
3283 ],
3284 false,
3285 Status::Unrecognized
3286 ),
3287 case::unknown_func(
3288 &[
3289 0x00, 0x01,
3291 0x00, 0x00,
3293 0x00, 0x02,
3295 0x01,
3297 0x64
3299 ],
3300 false,
3301 Status::Recognized
3302 ),
3303 case::strict_diagnostic(
3304 &[
3305 0x00, 0x01,
3307 0x00, 0x00,
3309 0x00, 0x06,
3311 0x03,
3313 0x08,
3315 0x00, 0x04,
3317 0x00, 0x00
3319 ],
3320 true,
3321 Status::Recognized
3322 ),
3323 case::strict_unknown_func(
3324 &[
3325 0x00, 0x01,
3327 0x00, 0x00,
3329 0x00, 0x02,
3331 0x01,
3333 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 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}