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