rusty_modbus_frame/
rtu.rs1use bytes::{BufMut, BytesMut};
8use rusty_modbus_types::{MAX_PDU_SIZE, MAX_RTU_ADU_SIZE};
9use tokio_util::codec::{Decoder, Encoder};
10
11use crate::crc::{crc16, verify_crc};
12use crate::error::FrameError;
13use crate::frame::{Frame, FrameHeader};
14
15const MIN_RTU_FRAME: usize = 4;
17const MIN_PDU_LENGTH: usize = 1;
18
19#[derive(Debug, Default)]
24pub struct RtuCodec;
25
26impl Decoder for RtuCodec {
27 type Item = Frame;
28 type Error = FrameError;
29
30 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
31 if src.len() < MIN_RTU_FRAME {
33 return Ok(None);
34 }
35 if src.len() > MAX_RTU_ADU_SIZE {
36 return Err(FrameError::PduLengthOverflow {
37 length: src.len() - 3,
38 maximum: MAX_PDU_SIZE,
39 });
40 }
41
42 if !verify_crc(src) {
46 let data_end = src.len() - 2;
47 let expected = crc16(&src[..data_end]);
48 let actual = u16::from_le_bytes([src[data_end], src[data_end + 1]]);
49 return Err(FrameError::CrcMismatch { expected, actual });
50 }
51
52 let unit_id = src[0];
53
54 let adu = src.split_to(src.len()).freeze();
56
57 let pdu = adu.slice(1..adu.len() - 2);
59
60 Ok(Some(Frame {
61 header: FrameHeader::Rtu { unit_id },
62 pdu,
63 }))
64 }
65}
66
67impl Encoder<Frame> for RtuCodec {
68 type Error = FrameError;
69
70 fn encode(&mut self, item: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
71 let unit_id = match item.header {
72 FrameHeader::Rtu { unit_id } => unit_id,
73 FrameHeader::Mbap(h) => h.unit_id,
74 };
75 validate_outgoing_pdu(item.pdu.len())?;
76
77 dst.reserve(1 + item.pdu.len() + 2);
79
80 dst.put_u8(unit_id);
81 dst.put_slice(&item.pdu);
82
83 let crc_start = dst.len() - 1 - item.pdu.len();
86 let crc = crc16(&dst[crc_start..]);
87 dst.put_u16_le(crc);
88
89 Ok(())
90 }
91}
92
93fn validate_outgoing_pdu(pdu_len: usize) -> Result<(), FrameError> {
94 if pdu_len < MIN_PDU_LENGTH {
95 return Err(FrameError::InvalidPduLength {
96 length: pdu_len,
97 minimum: MIN_PDU_LENGTH,
98 });
99 }
100 if pdu_len > MAX_PDU_SIZE {
101 return Err(FrameError::PduLengthOverflow {
102 length: pdu_len,
103 maximum: MAX_PDU_SIZE,
104 });
105 }
106 Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 fn make_rtu_frame(unit_id: u8, pdu: &[u8]) -> Vec<u8> {
115 let mut buf = vec![unit_id];
116 buf.extend_from_slice(pdu);
117 let crc = crc16(&buf);
118 buf.extend_from_slice(&crc.to_le_bytes());
119 buf
120 }
121
122 #[test]
123 fn decode_valid_frame() {
124 let raw = make_rtu_frame(0x01, &[0x03, 0x00, 0x00, 0x00, 0x0A]);
125 let mut buf = BytesMut::from(&raw[..]);
126 let mut codec = RtuCodec;
127
128 let frame = codec.decode(&mut buf).unwrap().unwrap();
129 assert_eq!(frame.unit_id(), 0x01);
130 assert_eq!(&frame.pdu[..], &[0x03, 0x00, 0x00, 0x00, 0x0A]);
131 assert!(buf.is_empty());
132 }
133
134 #[test]
135 fn decode_too_short() {
136 let mut buf = BytesMut::from(&[0x01, 0x03, 0xCD][..]);
137 let mut codec = RtuCodec;
138
139 assert!(codec.decode(&mut buf).unwrap().is_none());
140 }
141
142 #[test]
143 fn decode_bad_crc() {
144 let mut raw = make_rtu_frame(0x01, &[0x03, 0x00]);
145 let last = raw.len() - 1;
147 raw[last] ^= 0xFF;
148
149 let mut buf = BytesMut::from(&raw[..]);
150 let mut codec = RtuCodec;
151
152 let err = codec.decode(&mut buf).unwrap_err();
153 assert!(matches!(err, FrameError::CrcMismatch { .. }));
154 }
155
156 #[test]
157 fn encode_roundtrip() {
158 let original_pdu = vec![0x03, 0x00, 0x00, 0x00, 0x0A];
159 let frame = Frame {
160 header: FrameHeader::Rtu { unit_id: 0x01 },
161 pdu: bytes::Bytes::from(original_pdu.clone()),
162 };
163
164 let mut dst = BytesMut::new();
165 let mut codec = RtuCodec;
166 codec.encode(frame, &mut dst).unwrap();
167
168 let decoded = codec.decode(&mut dst).unwrap().unwrap();
170 assert_eq!(decoded.unit_id(), 0x01);
171 assert_eq!(&decoded.pdu[..], &original_pdu[..]);
172 }
173
174 #[test]
175 fn encode_rejects_empty_pdu() {
176 let frame = Frame {
177 header: FrameHeader::Rtu { unit_id: 0x01 },
178 pdu: bytes::Bytes::new(),
179 };
180
181 let mut dst = BytesMut::new();
182 let mut codec = RtuCodec;
183
184 let err = codec.encode(frame, &mut dst).unwrap_err();
185 assert!(matches!(err, FrameError::InvalidPduLength { .. }));
186 }
187
188 #[test]
189 fn encode_rejects_oversized_pdu() {
190 let frame = Frame {
191 header: FrameHeader::Rtu { unit_id: 0x01 },
192 pdu: bytes::Bytes::from(vec![0x03; MAX_PDU_SIZE + 1]),
193 };
194
195 let mut dst = BytesMut::new();
196 let mut codec = RtuCodec;
197
198 let err = codec.encode(frame, &mut dst).unwrap_err();
199 assert!(matches!(err, FrameError::PduLengthOverflow { .. }));
200 }
201
202 #[test]
203 fn decode_rejects_oversized_frame_even_with_valid_crc() {
204 let raw = make_rtu_frame(0x01, &vec![0x03; MAX_PDU_SIZE + 1]);
205 let mut buf = BytesMut::from(&raw[..]);
206 let mut codec = RtuCodec;
207
208 let err = codec.decode(&mut buf).unwrap_err();
209 assert!(matches!(err, FrameError::PduLengthOverflow { .. }));
210 }
211}