1use crate::errors::MbusError;
13use crate::function_codes::public::FunctionCode;
14use crate::transport::{SerialMode, TransportType, UnitIdOrSlaveAddr, checksum};
15use heapless::Vec;
16
17pub const MAX_PDU_DATA_LEN: usize = 252;
19
20pub const MODBUS_PROTOCOL_ID: u16 = 0x0000;
22
23pub const MAX_ADU_FRAME_LEN_TCP_RTU: usize = 260;
25
26pub const MAX_ADU_FRAME_LEN_ASCII: usize = 513;
28
29#[cfg(feature = "serial-ascii")]
34pub const MAX_ADU_FRAME_LEN: usize = MAX_ADU_FRAME_LEN_ASCII;
35
36#[cfg(not(feature = "serial-ascii"))]
38pub const MAX_ADU_FRAME_LEN: usize = MAX_ADU_FRAME_LEN_TCP_RTU;
39
40#[cfg(test)]
41mod frame_len_tests {
42 use super::*;
43
44 #[test]
45 #[cfg(feature = "serial-ascii")]
46 fn test_max_adu_frame_len_ascii_enabled() {
47 assert_eq!(MAX_ADU_FRAME_LEN, 513);
48 }
49
50 #[test]
51 #[cfg(not(feature = "serial-ascii"))]
52 fn test_max_adu_frame_len_ascii_disabled() {
53 assert_eq!(MAX_ADU_FRAME_LEN, 260);
54 }
55}
56
57pub const MBAP_HEADER_SIZE: usize = 7;
59
60pub const MIN_RTU_ADU_LEN: usize = 4;
62
63pub const MIN_ASCII_ADU_LEN: usize = 9;
65
66pub const RTU_CRC_SIZE: usize = 2;
68
69pub const MBAP_TXN_ID_OFFSET_1B: usize = 0;
71pub const MBAP_TXN_ID_OFFSET_2B: usize = MBAP_TXN_ID_OFFSET_1B + 1;
73pub const MBAP_PROTO_ID_OFFSET_1B: usize = 2;
75pub const MBAP_PROTO_ID_OFFSET_2B: usize = MBAP_PROTO_ID_OFFSET_1B + 1;
77pub const MBAP_LENGTH_OFFSET_1B: usize = 4;
79pub const MBAP_LENGTH_OFFSET_2B: usize = MBAP_LENGTH_OFFSET_1B + 1;
81pub const MBAP_UNIT_ID_OFFSET: usize = 6;
83
84pub const ASCII_START_SIZE: usize = 1;
86
87pub const ASCII_END_SIZE: usize = 2;
89
90pub const ERROR_BIT_MASK: u8 = 0x80;
92
93pub const FUNCTION_CODE_MASK: u8 = 0x7F;
95
96#[inline]
104pub fn is_exception_code(function_code_byte: u8) -> bool {
105 function_code_byte & ERROR_BIT_MASK != 0
106}
107
108#[inline]
116pub fn clear_exception_bit(function_code_byte: u8) -> u8 {
117 function_code_byte & FUNCTION_CODE_MASK
118}
119
120#[derive(Debug, Clone)]
125pub struct Pdu {
126 function_code: FunctionCode,
128 error_code: Option<u8>,
130 data: heapless::Vec<u8, MAX_PDU_DATA_LEN>,
132 data_len: u8,
134}
135
136#[derive(Debug, Clone, Copy)]
138pub struct MbapHeader {
139 pub transaction_id: u16,
141 pub protocol_id: u16,
143 pub length: u16,
145 pub unit_id: u8,
147}
148
149impl MbapHeader {
150 pub fn new(transaction_id: u16, length: u16, unit_id: u8) -> Self {
161 Self {
162 transaction_id,
163 protocol_id: 0, length,
165 unit_id,
166 }
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct SlaveAddress(u8);
173
174impl SlaveAddress {
175 pub fn new(address: u8) -> Result<Self, MbusError> {
177 if !(0..=247).contains(&address) {
178 return Err(MbusError::InvalidSlaveAddress);
179 }
180 Ok(Self(address))
181 }
182
183 pub fn address(&self) -> u8 {
185 self.0
186 }
187}
188
189#[derive(Debug, Clone, Copy)]
191#[allow(dead_code)]
192pub enum AdditionalAddress {
193 MbapHeader(MbapHeader),
195 SlaveAddress(SlaveAddress),
197}
198
199#[derive(Debug, Clone)]
201pub struct ModbusMessage {
202 pub additional_address: AdditionalAddress,
204 pub pdu: Pdu,
206 }
208
209impl ModbusMessage {
210 pub fn new(additional_address: AdditionalAddress, pdu: Pdu) -> Self {
219 Self {
220 additional_address,
221 pdu,
222 }
223 }
224
225 pub fn additional_address(&self) -> &AdditionalAddress {
227 &self.additional_address
228 }
229
230 pub fn pdu(&self) -> &Pdu {
236 &self.pdu
237 }
238
239 pub fn unit_id_or_slave_addr(&self) -> UnitIdOrSlaveAddr {
247 match self.additional_address {
248 AdditionalAddress::MbapHeader(header) => {
249 UnitIdOrSlaveAddr::try_from(header.unit_id).unwrap_or(UnitIdOrSlaveAddr::default())
250 }
251 AdditionalAddress::SlaveAddress(slave_address) => {
252 UnitIdOrSlaveAddr::try_from(slave_address.address())
253 .unwrap_or(UnitIdOrSlaveAddr::default())
254 }
255 }
256 }
257
258 pub fn transaction_id(&self) -> u16 {
263 match self.additional_address {
264 AdditionalAddress::MbapHeader(header) => header.transaction_id,
265 AdditionalAddress::SlaveAddress(_) => 0,
266 }
267 }
268
269 pub fn function_code(&self) -> FunctionCode {
271 self.pdu.function_code()
272 }
273
274 pub fn data(&self) -> &heapless::Vec<u8, MAX_PDU_DATA_LEN> {
276 self.pdu.data()
277 }
278
279 pub fn data_len(&self) -> u8 {
281 self.pdu.data_len()
282 }
283
284 pub fn to_bytes(&self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
293 let mut adu_bytes = Vec::new();
294
295 match &self.additional_address {
296 AdditionalAddress::MbapHeader(header) => {
297 adu_bytes
299 .extend_from_slice(&header.transaction_id.to_be_bytes())
300 .map_err(|_| MbusError::Unexpected)?;
301 adu_bytes
302 .extend_from_slice(&header.protocol_id.to_be_bytes())
303 .map_err(|_| MbusError::Unexpected)?;
304 adu_bytes
305 .extend_from_slice(&header.length.to_be_bytes())
306 .map_err(|_| MbusError::Unexpected)?;
307 adu_bytes
308 .push(header.unit_id)
309 .map_err(|_| MbusError::Unexpected)?;
310 }
311 AdditionalAddress::SlaveAddress(address) => {
312 adu_bytes
313 .push(address.address())
314 .map_err(|_| MbusError::Unexpected)?;
315 }
316 }
317
318 let pdu_bytes = self.pdu.to_bytes()?;
319 adu_bytes
320 .extend_from_slice(&pdu_bytes)
321 .map_err(|_| MbusError::Unexpected)?;
322
323 Ok(adu_bytes)
324 }
325
326 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MbusError> {
336 if bytes.len() < MBAP_HEADER_SIZE + 1 {
338 return Err(MbusError::InvalidAduLength); }
340
341 let transaction_id =
344 u16::from_be_bytes([bytes[MBAP_TXN_ID_OFFSET_1B], bytes[MBAP_TXN_ID_OFFSET_2B]]);
345 let protocol_id = u16::from_be_bytes([
347 bytes[MBAP_PROTO_ID_OFFSET_1B],
348 bytes[MBAP_PROTO_ID_OFFSET_2B],
349 ]);
350 let length =
352 u16::from_be_bytes([bytes[MBAP_LENGTH_OFFSET_1B], bytes[MBAP_LENGTH_OFFSET_2B]]);
353 let unit_id = bytes[MBAP_UNIT_ID_OFFSET];
355
356 if protocol_id != MODBUS_PROTOCOL_ID {
358 return Err(MbusError::BasicParseError); }
360
361 const INITIAL_FRAME_LEN: usize = MBAP_HEADER_SIZE - 1; let expected_total_len_from_header = length as usize + INITIAL_FRAME_LEN; if bytes.len() < expected_total_len_from_header {
370 return Err(MbusError::InvalidPduLength);
371 }
372
373 let frame_bytes = &bytes[..expected_total_len_from_header];
375 let pdu_bytes_slice = &frame_bytes[MBAP_HEADER_SIZE..];
377
378 let pdu = Pdu::from_bytes(pdu_bytes_slice)?;
380
381 let additional_addr = AdditionalAddress::MbapHeader(MbapHeader {
382 transaction_id,
383 protocol_id,
384 length,
385 unit_id,
386 });
387
388 Ok(ModbusMessage::new(additional_addr, pdu))
389 }
390
391 pub fn to_ascii_bytes(&self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
402 let mut binary_data = self.to_bytes()?;
403
404 let lrc = checksum::lrc(&binary_data);
406
407 binary_data
409 .push(lrc)
410 .map_err(|_| MbusError::BufferTooSmall)?;
411
412 let mut ascii_data = Vec::new();
413
414 ascii_data
416 .push(b':')
417 .map_err(|_| MbusError::BufferTooSmall)?;
418
419 for byte in binary_data {
420 let high = (byte >> 4) & 0x0F;
421 let low = byte & 0x0F;
422
423 ascii_data
424 .push(nibble_to_hex(high))
425 .map_err(|_| MbusError::BufferTooSmall)?;
426 ascii_data
427 .push(nibble_to_hex(low))
428 .map_err(|_| MbusError::BufferTooSmall)?;
429 }
430
431 ascii_data
433 .push(b'\r')
434 .map_err(|_| MbusError::BufferTooSmall)?;
435 ascii_data
436 .push(b'\n')
437 .map_err(|_| MbusError::BufferTooSmall)?;
438
439 Ok(ascii_data)
440 }
441
442 pub fn from_rtu_bytes(frame: &[u8]) -> Result<Self, MbusError> {
454 if frame.len() < MIN_RTU_ADU_LEN {
457 return Err(MbusError::InvalidAduLength);
458 }
459
460 let data_len = frame.len() - RTU_CRC_SIZE;
462 let data_to_check = &frame[..data_len];
463
464 let received_crc = u16::from_le_bytes([frame[data_len], frame[data_len + 1]]);
466 let calculated_crc = checksum::crc16(data_to_check);
467
468 if calculated_crc != received_crc {
470 return Err(MbusError::ChecksumError); }
472
473 let slave_address = SlaveAddress::new(frame[0])?;
475
476 let pdu_bytes = &data_to_check[1..];
478 let pdu = Pdu::from_bytes(pdu_bytes)?;
479
480 Ok(ModbusMessage::new(
481 AdditionalAddress::SlaveAddress(slave_address),
482 pdu,
483 ))
484 }
485
486 pub fn from_ascii_bytes(frame: &[u8]) -> Result<Self, MbusError> {
501 if frame.len() < MIN_ASCII_ADU_LEN {
504 return Err(MbusError::InvalidAduLength);
505 }
506
507 if frame[0] != b':' {
509 return Err(MbusError::BasicParseError); }
511 if frame[frame.len() - 2] != b'\r' || frame[frame.len() - 1] != b'\n' {
512 return Err(MbusError::BasicParseError); }
514
515 let hex_content = &frame[ASCII_START_SIZE..frame.len() - ASCII_END_SIZE];
517 if !hex_content.len().is_multiple_of(2) {
518 return Err(MbusError::BasicParseError); }
520
521 let mut binary_data: Vec<u8, 260> = Vec::new();
524 for chunk in hex_content.chunks(2) {
525 let byte = hex_pair_to_byte(chunk[0], chunk[1])?;
526 binary_data
527 .push(byte)
528 .map_err(|_| MbusError::BufferTooSmall)?;
529 }
530
531 if binary_data.len() < 2 {
533 return Err(MbusError::InvalidAduLength);
534 }
535
536 let data_len = binary_data.len() - 1;
537 let data_to_check = &binary_data[..data_len];
538 let received_lrc = binary_data[data_len];
539
540 let calculated_lrc = checksum::lrc(data_to_check);
541
542 if calculated_lrc != received_lrc {
543 return Err(MbusError::ChecksumError); }
545
546 let slave_address = SlaveAddress::new(binary_data[0])?;
547 let pdu_bytes = &binary_data[1..data_len];
548 let pdu = Pdu::from_bytes(pdu_bytes)?;
549
550 Ok(ModbusMessage::new(
551 AdditionalAddress::SlaveAddress(slave_address),
552 pdu,
553 ))
554 }
555}
556
557impl Pdu {
558 pub fn new(
569 function_code: FunctionCode,
570 data: heapless::Vec<u8, MAX_PDU_DATA_LEN>,
571 data_len: u8,
572 ) -> Self {
573 Self {
574 function_code,
575 error_code: None, data, data_len,
578 }
579 }
580
581 pub fn function_code(&self) -> FunctionCode {
583 self.function_code
584 }
585
586 pub fn data(&self) -> &Vec<u8, MAX_PDU_DATA_LEN> {
588 &self.data
589 }
590
591 pub fn data_len(&self) -> u8 {
593 self.data_len
594 }
595
596 pub fn error_code(&self) -> Option<u8> {
598 self.error_code
599 }
600
601 pub fn to_bytes(&self) -> Result<Vec<u8, 253>, MbusError> {
612 let mut pdu_bytes = Vec::new(); pdu_bytes
614 .push(self.function_code as u8)
615 .map_err(|_| MbusError::Unexpected)?; pdu_bytes
618 .extend_from_slice(&self.data.as_slice()[..self.data_len as usize])
619 .map_err(|_| MbusError::BufferLenMissmatch)?; Ok(pdu_bytes)
622 }
623
624 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MbusError> {
634 if bytes.is_empty() || bytes.len() < 2 {
635 return Err(MbusError::InvalidPduLength);
636 }
637
638 let error_code = if is_exception_code(bytes[0]) {
639 Some(bytes[1]) } else {
641 None
642 };
643 let function_code = clear_exception_bit(bytes[0]); let function_code = FunctionCode::try_from(function_code)?;
646
647 let data_slice = &bytes[1..];
648 let data_len = data_slice.len();
649
650 if data_len > MAX_PDU_DATA_LEN {
651 return Err(MbusError::InvalidPduLength);
652 }
653
654 let mut data = heapless::Vec::new();
655 data.extend_from_slice(data_slice)
656 .map_err(|_| MbusError::BufferLenMissmatch)?;
657
658 Ok(Pdu {
659 function_code,
660 error_code,
661 data,
662 data_len: data_len as u8,
663 })
664 }
665}
666
667pub fn compile_adu_frame(
669 txn_id: u16,
670 unit_id: u8,
671 pdu: Pdu,
672 transport_type: TransportType,
673) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
674 match transport_type {
675 TransportType::StdTcp | TransportType::CustomTcp => {
676 let pdu_bytes_len = pdu.to_bytes()?.len() as u16;
677 let mbap_header = MbapHeader::new(txn_id, pdu_bytes_len + 1, unit_id);
678 ModbusMessage::new(AdditionalAddress::MbapHeader(mbap_header), pdu).to_bytes()
679 }
680 TransportType::StdSerial(serial_mode) | TransportType::CustomSerial(serial_mode) => {
681 let slave_address = SlaveAddress(unit_id);
682 let adu_bytes = match serial_mode {
683 SerialMode::Rtu => {
684 let mut adu_bytes =
685 ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_address), pdu)
686 .to_bytes()?;
687 let crc16 = checksum::crc16(adu_bytes.as_slice());
689 let crc_bytes = crc16.to_le_bytes();
691 adu_bytes
692 .extend_from_slice(&crc_bytes)
693 .map_err(|_| MbusError::Unexpected)?;
694
695 adu_bytes
696 }
697 SerialMode::Ascii => {
698 ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_address), pdu)
699 .to_ascii_bytes()?
700 }
701 };
702
703 Ok(adu_bytes)
704 }
705 }
706}
707
708pub fn decompile_adu_frame(
710 frame: &[u8],
711 transport_type: TransportType,
712) -> Result<ModbusMessage, MbusError> {
713 match transport_type {
714 TransportType::StdTcp | TransportType::CustomTcp => {
715 ModbusMessage::from_bytes(frame)
717 }
718 TransportType::StdSerial(serial_mode) | TransportType::CustomSerial(serial_mode) => {
719 match serial_mode {
720 SerialMode::Rtu => ModbusMessage::from_rtu_bytes(frame),
721 SerialMode::Ascii => ModbusMessage::from_ascii_bytes(frame),
722 }
723 }
724 }
725}
726
727pub fn derive_length_from_bytes(frame: &[u8], transport_type: TransportType) -> Option<usize> {
740 match transport_type {
741 TransportType::StdTcp | TransportType::CustomTcp => {
742 if frame.len() < 6 {
745 return None;
746 }
747
748 let protocol_id = u16::from_be_bytes([frame[2], frame[3]]);
752 if protocol_id != MODBUS_PROTOCOL_ID {
753 return Some(usize::MAX);
754 }
755
756 let length_field = u16::from_be_bytes([frame[4], frame[5]]) as usize;
759 Some(6 + length_field)
760 }
761 TransportType::StdSerial(SerialMode::Rtu)
762 | TransportType::CustomSerial(SerialMode::Rtu) => {
763 if frame.len() < 2 {
764 return None;
765 }
766
767 let fc = frame[1];
768
769 if is_exception_code(fc) {
771 return Some(5);
772 }
773
774 let check_crc = |len: usize| -> bool {
777 if frame.len() >= len && len >= MIN_RTU_ADU_LEN {
778 let data_len = len - RTU_CRC_SIZE;
779 let received_crc = u16::from_le_bytes([frame[data_len], frame[data_len + 1]]);
780 checksum::crc16(&frame[..data_len]) == received_crc
781 } else {
782 false
783 }
784 };
785
786 get_byte_count_from_frame(frame, fc, check_crc)
787 }
788 TransportType::StdSerial(SerialMode::Ascii)
789 | TransportType::CustomSerial(SerialMode::Ascii) => {
790 if frame.len() < MIN_ASCII_ADU_LEN {
793 return None;
794 }
795
796 frame.iter().position(|&b| b == b'\n').map(|pos| pos + 1)
798 }
799 }
800}
801
802fn get_byte_count_from_frame(
803 frame: &[u8],
804 fc: u8,
805 check_crc: impl Fn(usize) -> bool,
806) -> Option<usize> {
807 let mut candidates = heapless::Vec::<usize, 4>::new();
808 let mut min_needed = usize::MAX;
809
810 let mut add_dyn = |cands: &mut heapless::Vec<usize, 4>, offset: usize, base: usize| {
814 if frame.len() > offset {
815 let _ = cands.push(base + frame[offset] as usize);
816 } else {
817 min_needed = core::cmp::min(min_needed, offset + 1);
818 }
819 };
820
821 match fc {
823 1..=4 => {
824 let _ = candidates.push(8);
825 add_dyn(&mut candidates, 2, 5);
826 }
827 5 | 6 | 8 => {
828 let _ = candidates.push(8);
829 }
830 7 => {
831 let _ = candidates.push(4);
832 let _ = candidates.push(5);
833 }
834 11 => {
835 let _ = candidates.push(4);
836 let _ = candidates.push(8);
837 }
838 12 | 17 => {
839 let _ = candidates.push(4);
840 add_dyn(&mut candidates, 2, 5);
841 }
842 15 | 16 => {
843 let _ = candidates.push(8);
844 add_dyn(&mut candidates, 6, 9);
845 }
846 20 | 21 => add_dyn(&mut candidates, 2, 5),
847 22 => {
848 let _ = candidates.push(10);
849 }
850 23 => {
851 add_dyn(&mut candidates, 2, 5);
852 add_dyn(&mut candidates, 10, 13);
853 }
854 24 => {
855 let _ = candidates.push(6);
856 if frame.len() >= 4 {
857 let byte_count = u16::from_be_bytes([frame[2], frame[3]]) as usize;
858 let _ = candidates.push(6 + byte_count);
859 } else {
860 min_needed = core::cmp::min(min_needed, 4);
861 }
862 }
863 43 => {
864 if check_crc(7) {
865 return Some(7);
866 }
867 for len in MIN_RTU_ADU_LEN..=frame.len() {
869 if check_crc(len) {
870 return Some(len);
871 }
872 }
873 return None;
874 }
875 _ => {
876 for len in MIN_RTU_ADU_LEN..=frame.len() {
877 if check_crc(len) {
878 return Some(len);
879 }
880 }
881 return None;
882 }
883 }
884
885 for &len in &candidates {
887 if check_crc(len) {
888 return Some(len);
889 }
890 }
891
892 let max_candidate = candidates.iter().copied().max().unwrap_or(0);
894 let target = if min_needed != usize::MAX {
895 core::cmp::max(min_needed, max_candidate)
896 } else {
897 max_candidate
898 };
899
900 if target > 0 { Some(target) } else { None }
901}
902
903fn nibble_to_hex(nibble: u8) -> u8 {
905 match nibble {
906 0..=9 => b'0' + nibble,
907 10..=15 => b'A' + (nibble - 10),
908 _ => b'?', }
910}
911
912fn hex_char_to_nibble(c: u8) -> Result<u8, MbusError> {
914 match c {
915 b'0'..=b'9' => Ok(c - b'0'),
916 b'A'..=b'F' => Ok(c - b'A' + 10),
917 b'a'..=b'f' => Ok(c - b'a' + 10),
918 _ => Err(MbusError::BasicParseError),
919 }
920}
921
922fn hex_pair_to_byte(high: u8, low: u8) -> Result<u8, MbusError> {
924 let h = hex_char_to_nibble(high)?;
925 let l = hex_char_to_nibble(low)?;
926 Ok((h << 4) | l)
927}
928
929#[cfg(test)]
930mod tests {
931 use super::*;
932 use crate::function_codes::public::FunctionCode;
933 use heapless::Vec;
934
935 #[test]
941 fn test_pdu_from_bytes_invalid_no_data() {
942 let bytes = [0x11];
943 let err = Pdu::from_bytes(&bytes).expect_err("Should return error for PDU with only FC");
944 assert_eq!(err, MbusError::InvalidPduLength);
945 }
946
947 #[test]
953 fn test_pdu_from_bytes_valid_read_coils_request() {
954 let bytes = [0x01, 0x00, 0x00, 0x00, 0x0A]; let pdu = Pdu::from_bytes(&bytes).expect("Should successfully parse Read Coils request");
958
959 assert_eq!(pdu.function_code, FunctionCode::ReadCoils);
960 assert_eq!(pdu.data_len, 4);
961 assert_eq!(pdu.data.as_slice(), &[0x00, 0x00, 0x00, 0x0A]);
962 }
963
964 #[test]
970 fn test_pdu_from_bytes_valid_read_holding_registers_response() {
971 let bytes = [0x03, 0x04, 0x12, 0x34, 0x56, 0x78]; let pdu = Pdu::from_bytes(&bytes)
975 .expect("Should successfully parse Read Holding Registers response");
976
977 assert_eq!(pdu.function_code, FunctionCode::ReadHoldingRegisters);
978 assert_eq!(pdu.data_len, 5); assert_eq!(pdu.data.as_slice(), &[0x04, 0x12, 0x34, 0x56, 0x78]);
980 }
981
982 #[test]
988 fn test_pdu_from_bytes_valid_max_data_length() {
989 let mut bytes_vec: Vec<u8, 253> = Vec::new();
991 let _ = bytes_vec.push(0x03); for i in 0..252 {
993 let _ = bytes_vec.push(i as u8);
994 }
995 let bytes = bytes_vec.as_slice();
996 let pdu = Pdu::from_bytes(bytes).expect("Should parse valid PDU with max data");
997
998 assert_eq!(pdu.function_code, FunctionCode::ReadHoldingRegisters);
999 assert_eq!(pdu.data_len, 252);
1000 assert_eq!(pdu.data.as_slice(), &bytes[1..]);
1001 }
1002
1003 #[test]
1007 fn test_pdu_from_bytes_empty_slice_error() {
1008 let bytes = [];
1009 let err = Pdu::from_bytes(&bytes).expect_err("Should return error for empty slice");
1010 assert_eq!(err, MbusError::InvalidPduLength);
1011 }
1012
1013 #[test]
1018 fn test_pdu_from_bytes_invalid_function_code_error() {
1019 let bytes = [0x00, 0x01, 0x02];
1021 let err = Pdu::from_bytes(&bytes).expect_err("Should return error for invalid FC 0x00");
1022 assert_eq!(err, MbusError::UnsupportedFunction(0x00));
1023
1024 let bytes = [0xFF, 0x01, 0x02];
1026 let err = Pdu::from_bytes(&bytes).expect_err("Should return error for invalid FC 0xFF");
1027 assert_eq!(err, MbusError::UnsupportedFunction(0x7F));
1028 }
1029
1030 #[test]
1036 fn test_pdu_from_bytes_data_too_long_error() {
1037 let mut bytes_vec: Vec<u8, 254> = Vec::new();
1039 let _ = bytes_vec.push(0x03); for i in 0..253 {
1041 let _ = bytes_vec.push(i as u8);
1043 }
1044 let bytes = bytes_vec.as_slice();
1045 let err = Pdu::from_bytes(bytes).expect_err("Should return error for too much data");
1046 assert_eq!(err, MbusError::InvalidPduLength);
1047 }
1048
1049 #[test]
1057 fn test_pdu_to_bytes_no_data() {
1058 let pdu = Pdu::new(FunctionCode::ReportServerId, Vec::new(), 0);
1059 let bytes = pdu.to_bytes().expect("Should convert PDU to bytes");
1060 assert_eq!(bytes.as_slice(), &[0x11]);
1061 }
1062
1063 #[test]
1069 fn test_pdu_to_bytes_with_data() {
1070 let mut data_vec = Vec::new();
1071 data_vec
1072 .extend_from_slice(&[0x00, 0x00, 0x00, 0x0A])
1073 .unwrap(); let pdu = Pdu::new(FunctionCode::ReadCoils, data_vec, 4);
1076 let bytes = pdu.to_bytes().expect("Should convert PDU to bytes");
1077 assert_eq!(bytes.as_slice(), &[0x01, 0x00, 0x00, 0x00, 0x0A]);
1078 }
1079
1080 #[test]
1086 fn test_pdu_to_bytes_max_data() {
1087 let mut data_vec = Vec::new();
1088 for i in 0..252 {
1089 data_vec.push(i as u8).unwrap();
1090 }
1091
1092 let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data_vec, 252);
1093 let bytes = pdu
1094 .to_bytes()
1095 .expect("Should convert PDU to bytes with max data");
1096 let mut expected_bytes_vec: Vec<u8, 253> = Vec::new();
1097 let _ = expected_bytes_vec.push(0x03);
1098 for i in 0..252 {
1099 let _ = expected_bytes_vec.push(i as u8);
1100 }
1101 assert_eq!(bytes.as_slice(), expected_bytes_vec.as_slice());
1102 }
1103
1104 #[test]
1109 fn test_modbus_message_to_bytes_tcp() {
1110 let mbap_header = MbapHeader {
1111 transaction_id: 0x1234,
1112 protocol_id: 0x0000,
1113 length: 0x0005, unit_id: 0x01,
1115 };
1116
1117 let mut pdu_data_vec: Vec<u8, MAX_PDU_DATA_LEN> = Vec::new();
1118 pdu_data_vec.extend_from_slice(&[0x00, 0x00, 0x00]).unwrap();
1119
1120 let pdu = Pdu::new(
1121 FunctionCode::ReadHoldingRegisters,
1122 pdu_data_vec,
1123 3, );
1125
1126 let modbus_message = ModbusMessage::new(AdditionalAddress::MbapHeader(mbap_header), pdu);
1127 let adu_bytes = modbus_message
1128 .to_bytes()
1129 .expect("Failed to serialize ModbusMessage");
1130
1131 #[rustfmt::skip]
1132 let expected_adu: [u8; 11] = [
1133 0x12, 0x34, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x00, 0x00, 0x00, ];
1140
1141 assert_eq!(adu_bytes.as_slice(), &expected_adu);
1142 }
1143
1144 #[test]
1150 fn test_function_code_try_from_valid() {
1151 assert_eq!(
1152 FunctionCode::try_from(0x01).unwrap(),
1153 FunctionCode::ReadCoils
1154 );
1155 assert_eq!(
1156 FunctionCode::try_from(0x08).unwrap(),
1157 FunctionCode::Diagnostics
1158 );
1159 assert_eq!(
1160 FunctionCode::try_from(0x2B).unwrap(),
1161 FunctionCode::EncapsulatedInterfaceTransport
1162 );
1163 assert_eq!(
1164 FunctionCode::try_from(0x18).unwrap(),
1165 FunctionCode::ReadFifoQueue
1166 );
1167 assert_eq!(
1168 FunctionCode::try_from(0x11).unwrap(),
1169 FunctionCode::ReportServerId
1170 );
1171 }
1172
1173 #[test]
1178 fn test_function_code_try_from_invalid() {
1179 let err = FunctionCode::try_from(0x00).expect_err("Should error for invalid FC 0x00");
1180 assert_eq!(err, MbusError::UnsupportedFunction(0x00));
1181
1182 let err =
1183 FunctionCode::try_from(0x09).expect_err("Should error for invalid FC 0x09 (reserved)");
1184 assert_eq!(err, MbusError::UnsupportedFunction(0x09));
1185
1186 let err = FunctionCode::try_from(0x64)
1187 .expect_err("Should error for invalid FC 0x64 (private range, not public)");
1188 assert_eq!(err, MbusError::UnsupportedFunction(0x64));
1189 }
1190
1191 #[test]
1198 fn test_pdu_round_trip_with_data() {
1199 let original_bytes = [0x01, 0x00, 0x00, 0x00, 0x0A]; let pdu = Pdu::from_bytes(&original_bytes).expect("from_bytes failed");
1201 let new_bytes = pdu.to_bytes().expect("to_bytes failed");
1202 assert_eq!(original_bytes.as_slice(), new_bytes.as_slice());
1203 }
1204
1205 #[test]
1210 fn test_pdu_round_trip_max_data() {
1211 let mut original_bytes_vec: Vec<u8, 253> = Vec::new();
1212 let _ = original_bytes_vec.push(0x03); for i in 0..252 {
1214 let _ = original_bytes_vec.push(i as u8);
1215 }
1216 let original_bytes = original_bytes_vec.as_slice();
1217
1218 let pdu = Pdu::from_bytes(original_bytes).expect("from_bytes failed");
1219 let new_bytes = pdu.to_bytes().expect("to_bytes failed");
1220 assert_eq!(original_bytes, new_bytes.as_slice());
1221 }
1222
1223 #[test]
1233 fn test_modbus_message_to_ascii_bytes_valid() {
1234 let slave_addr = SlaveAddress::new(1).unwrap();
1235 let mut data = Vec::new();
1236 data.extend_from_slice(&[0x00, 0x00, 0x00, 0x0A]).unwrap();
1237 let pdu = Pdu::new(FunctionCode::ReadCoils, data, 4);
1238 let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
1239
1240 let ascii_bytes = message
1241 .to_ascii_bytes()
1242 .expect("Failed to convert to ASCII");
1243
1244 let expected = b":01010000000AF4\r\n";
1245 assert_eq!(ascii_bytes.as_slice(), expected);
1246 }
1247
1248 #[test]
1253 fn test_modbus_message_to_ascii_bytes_max_capacity() {
1254 let slave_addr = SlaveAddress::new(1).unwrap();
1255 let mut data = Vec::new();
1256 for _ in 0..125 {
1257 data.push(0xAA).unwrap();
1258 }
1259 let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data, 125);
1260 let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
1261
1262 let ascii_bytes = message.to_ascii_bytes().expect("Should fit in buffer");
1263 assert_eq!(ascii_bytes.len(), 259);
1264 }
1265
1266 #[test]
1271 fn test_modbus_message_to_ascii_bytes_large_payload() {
1272 let slave_addr = SlaveAddress::new(1).unwrap();
1273 let mut data = Vec::new();
1274 for _ in 0..126 {
1275 data.push(0xAA).unwrap();
1276 }
1277 let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data, 126);
1278 let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
1279
1280 let ascii_bytes = message.to_ascii_bytes().expect("Should fit in buffer");
1281 assert_eq!(ascii_bytes.len(), 261);
1282 }
1283
1284 #[test]
1287 fn test_decompile_adu_frame_tcp_valid() {
1288 let frame = [
1289 0x12, 0x34, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x02, ];
1296 let msg = decompile_adu_frame(&frame, TransportType::StdTcp)
1297 .expect("Should decode valid TCP frame");
1298 assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
1299 if let AdditionalAddress::MbapHeader(header) = msg.additional_address {
1300 assert_eq!(header.transaction_id, 0x1234);
1301 } else {
1302 panic!("Expected MbapHeader");
1303 }
1304 }
1305
1306 #[test]
1307 fn test_decompile_adu_frame_tcp_invalid() {
1308 let frame = [0x00]; let err = decompile_adu_frame(&frame, TransportType::StdTcp).expect_err("Should fail");
1310 assert_eq!(err, MbusError::InvalidAduLength);
1311 }
1312
1313 #[test]
1314 fn test_decompile_adu_frame_rtu_valid() {
1315 let frame = [0x01, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x74, 0x17];
1317 let msg = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
1318 .expect("Valid RTU");
1319 assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
1320 }
1321
1322 #[test]
1323 fn test_decompile_adu_frame_rtu_too_short() {
1324 let frame = [0x01, 0x02, 0x03];
1325 let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
1326 .expect_err("Too short");
1327 assert_eq!(err, MbusError::InvalidAduLength);
1328 }
1329
1330 #[test]
1331 fn test_decompile_adu_frame_rtu_crc_mismatch() {
1332 let frame = [0x01, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x00, 0x00]; let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
1334 .expect_err("CRC mismatch");
1335 assert_eq!(err, MbusError::ChecksumError);
1336 }
1337
1338 #[test]
1339 fn test_decompile_adu_frame_ascii_valid() {
1340 let frame = b":010300000001FB\r\n";
1342 let msg = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
1343 .expect("Valid ASCII");
1344 assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
1345 }
1346
1347 #[test]
1348 fn test_decompile_adu_frame_ascii_too_short() {
1349 let frame = b":123\r\n";
1350 let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
1351 .expect_err("Too short");
1352 assert_eq!(err, MbusError::InvalidAduLength);
1353 }
1354
1355 #[test]
1356 fn test_decompile_adu_frame_ascii_missing_start() {
1357 let frame = b"010300000001FB\r\n";
1358 let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
1359 .expect_err("Missing start");
1360 assert_eq!(err, MbusError::BasicParseError);
1361 }
1362
1363 #[test]
1364 fn test_decompile_adu_frame_ascii_missing_end() {
1365 let frame = b":010300000001FB\r"; let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
1367 .expect_err("Missing end");
1368 assert_eq!(err, MbusError::BasicParseError);
1369 }
1370
1371 #[test]
1372 fn test_decompile_adu_frame_ascii_odd_hex() {
1373 let frame = b":010300000001F\r\n"; let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
1375 .expect_err("Odd hex");
1376 assert_eq!(err, MbusError::BasicParseError);
1377 }
1378
1379 #[test]
1380 fn test_decompile_adu_frame_ascii_lrc_mismatch() {
1381 let frame = b":01030000000100\r\n"; let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
1383 .expect_err("LRC mismatch");
1384 assert_eq!(err, MbusError::ChecksumError);
1385 }
1386
1387 #[test]
1388 fn test_decompile_adu_frame_ascii_buffer_overflow() {
1389 let mut frame = Vec::<u8, 600>::new();
1391 frame.push(b':').unwrap();
1392 for _ in 0..261 {
1393 frame.extend_from_slice(b"00").unwrap();
1394 }
1395 frame.extend_from_slice(b"\r\n").unwrap();
1396 let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Ascii))
1397 .expect_err("Buffer overflow");
1398 assert_eq!(err, MbusError::BufferTooSmall);
1399 }
1400
1401 #[test]
1404 fn test_derive_length_tcp() {
1405 let short_frame = [0x00, 0x01, 0x00, 0x00, 0x00];
1407 assert_eq!(
1408 derive_length_from_bytes(&short_frame, TransportType::StdTcp),
1409 None
1410 );
1411
1412 let full_frame = [
1414 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01,
1415 ];
1416 assert_eq!(
1417 derive_length_from_bytes(&full_frame, TransportType::StdTcp),
1418 Some(12)
1419 );
1420
1421 let garbage_frame = [
1423 0x00, 0x01, 0xAA, 0xBB, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01,
1424 ];
1425 assert_eq!(
1426 derive_length_from_bytes(&garbage_frame, TransportType::StdTcp),
1427 Some(usize::MAX)
1428 );
1429 }
1430
1431 #[test]
1432 fn test_derive_length_rtu_fixed() {
1433 let request = [0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, 0x00, 0x00];
1435 assert_eq!(
1436 derive_length_from_bytes(&request, TransportType::StdSerial(SerialMode::Rtu)),
1437 Some(8)
1438 );
1439 }
1440
1441 #[test]
1442 fn test_derive_length_rtu_dynamic() {
1443 let mut resp = [0x01, 0x03, 0x02, 0x12, 0x34, 0x00, 0x00];
1446 let crc = checksum::crc16(&resp[..5]);
1447 let crc_bytes = crc.to_le_bytes();
1448 resp[5] = crc_bytes[0];
1449 resp[6] = crc_bytes[1];
1450
1451 assert_eq!(
1453 derive_length_from_bytes(&resp, TransportType::StdSerial(SerialMode::Rtu)),
1454 Some(7)
1455 );
1456
1457 assert_eq!(
1459 derive_length_from_bytes(&resp[..4], TransportType::StdSerial(SerialMode::Rtu)),
1460 Some(8)
1461 );
1462 }
1463
1464 #[test]
1465 fn test_derive_length_rtu_exception() {
1466 let exception = [0x01, 0x81, 0x02, 0x00, 0x00];
1468 assert_eq!(
1469 derive_length_from_bytes(&exception, TransportType::StdSerial(SerialMode::Rtu)),
1470 Some(5)
1471 );
1472 }
1473
1474 #[test]
1475 fn test_derive_length_rtu_forward_scan() {
1476 let mut custom_frame = [0x01, 0x44, 0xAA, 0xBB, 0x00, 0x00];
1478 let crc = checksum::crc16(&custom_frame[..4]);
1479 let crc_bytes = crc.to_le_bytes();
1480 custom_frame[4] = crc_bytes[0];
1481 custom_frame[5] = crc_bytes[1];
1482
1483 assert_eq!(
1484 derive_length_from_bytes(&custom_frame, TransportType::StdSerial(SerialMode::Rtu)),
1485 Some(6)
1486 );
1487
1488 assert_eq!(
1490 derive_length_from_bytes(
1491 &custom_frame[..4],
1492 TransportType::StdSerial(SerialMode::Rtu)
1493 ),
1494 None
1495 );
1496 }
1497
1498 #[test]
1499 fn test_derive_length_ascii() {
1500 let frame = b":010300000001FB\r\n";
1501 assert_eq!(
1502 derive_length_from_bytes(frame, TransportType::StdSerial(SerialMode::Ascii)),
1503 Some(17)
1504 );
1505
1506 let partial = b":010300000001F";
1507 assert_eq!(
1508 derive_length_from_bytes(partial, TransportType::StdSerial(SerialMode::Ascii)),
1509 None
1510 );
1511 }
1512}