1use super::types::ModbusFunction;
4
5const MBAP_HEADER_LEN: usize = 7;
6
7#[derive(Debug, Clone)]
9pub struct ModbusTcpPacket<'a> { buf: &'a [u8] }
10
11impl<'a> ModbusTcpPacket<'a> {
12 pub fn new(buf: &'a [u8]) -> Option<Self> {
13 if buf.len() < MBAP_HEADER_LEN { return None; }
14 Some(Self { buf })
15 }
16
17 pub fn transaction_id(&self) -> u16 { u16::from_be_bytes([self.buf[0], self.buf[1]]) }
18 pub fn protocol_id(&self) -> u16 { u16::from_be_bytes([self.buf[2], self.buf[3]]) }
19 pub fn length(&self) -> u16 { u16::from_be_bytes([self.buf[4], self.buf[5]]) }
20 pub fn unit_id(&self) -> u8 { self.buf[6] }
21 pub fn function_code(&self) -> u8 { self.buf.get(7).copied().unwrap_or(0) }
22 pub fn pdu_data(&self) -> &'a [u8] { &self.buf[MBAP_HEADER_LEN..] }
23
24 pub fn function(&self) -> ModbusFunction<'a> {
25 let pdu = self.pdu_data();
26 if pdu.is_empty() { return ModbusFunction::Unknown { function_code: 0, data: &[] }; }
27 let fc = pdu[0];
28 let data = &pdu[1..];
29 match fc {
30 1 => ModbusFunction::ReadCoils {
31 starting_address: self._u16(data, 0), quantity: self._u16(data, 2),
32 },
33 2 => ModbusFunction::ReadDiscreteInputs {
34 starting_address: self._u16(data, 0), quantity: self._u16(data, 2),
35 },
36 3 => {
37 if data.len() >= 4 && data.len() >= 5 + data[2] as usize {
38 ModbusFunction::ReadHoldingRegisters { starting_address: self._u16(data, 0), quantity: self._u16(data, 2) }
40 } else {
41 ModbusFunction::ReadHoldingRegisters { starting_address: self._u16(data, 0), quantity: self._u16(data, 2) }
42 }
43 }
44 4 => ModbusFunction::ReadInputRegisters {
45 starting_address: self._u16(data, 0), quantity: self._u16(data, 2),
46 },
47 5 => ModbusFunction::WriteSingleCoil {
48 output_address: self._u16(data, 0),
49 value: data.len() >= 3 && data[2] == 0xFF,
50 },
51 6 => ModbusFunction::WriteSingleRegister {
52 register_address: self._u16(data, 0), value: self._u16(data, 2),
53 },
54 15 => ModbusFunction::WriteMultipleCoils {
55 starting_address: self._u16(data, 0), quantity: self._u16(data, 2),
56 byte_count: *data.get(4).unwrap_or(&0), values: &data[5..],
57 },
58 16 => ModbusFunction::WriteMultipleRegisters {
59 starting_address: self._u16(data, 0), quantity: self._u16(data, 2),
60 byte_count: *data.get(4).unwrap_or(&0), values: &data[5..],
61 },
62 _ => ModbusFunction::Unknown { function_code: fc, data },
63 }
64 }
65
66 fn _u16(&self, data: &[u8], off: usize) -> u16 {
67 if off + 2 <= data.len() { u16::from_be_bytes([data[off], data[off+1]]) } else { 0 }
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn parse_read_coils() {
77 let data = &[0x00u8, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0A];
78 let pkt = ModbusTcpPacket::new(data).unwrap();
80 assert_eq!(pkt.transaction_id(), 1);
81 assert_eq!(pkt.function_code(), 1);
82 match pkt.function() {
83 ModbusFunction::ReadCoils { starting_address, quantity } => {
84 assert_eq!(starting_address, 0); assert_eq!(quantity, 10);
85 }
86 _ => panic!("expected ReadCoils"),
87 }
88 }
89
90 #[test]
91 fn parse_read_holding_registers_response() {
92 let data = &[0x00u8, 0x01, 0x00, 0x00, 0x00, 0x09, 0x01, 0x03, 0x06,
94 0x00, 0x01, 0x00, 0x02, 0x00, 0x03];
95 let pkt = ModbusTcpPacket::new(data).unwrap();
96 assert_eq!(pkt.function_code(), 3);
97 }
98
99 #[test]
100 fn parse_write_single_register() {
101 let data = &[0x00u8, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x00, 0x2A];
102 let pkt = ModbusTcpPacket::new(data).unwrap();
103 match pkt.function() {
104 ModbusFunction::WriteSingleRegister { register_address, value } => {
105 assert_eq!(register_address, 1); assert_eq!(value, 42);
106 }
107 _ => panic!("expected WriteSingleRegister"),
108 }
109 }
110
111 #[test]
112 fn parse_too_short() {
113 assert!(ModbusTcpPacket::new(&[]).is_none());
114 assert!(ModbusTcpPacket::new(&[0u8; 6]).is_none());
115 assert!(ModbusTcpPacket::new(&[0u8; 7]).is_some());
116 }
117}