rusty_modbus_frame/
rtu_tcp.rs1use bytes::{BufMut, BytesMut};
10use rusty_modbus_types::{MAX_PDU_SIZE, MAX_RTU_ADU_SIZE};
11use tokio_util::codec::{Decoder, Encoder};
12
13use crate::crc::{crc16, crc16_update};
14use crate::error::FrameError;
15use crate::frame::{Frame, FrameHeader};
16
17const MIN_RTU_FRAME: usize = 4;
19const MIN_PDU_LENGTH: usize = 1;
20
21#[derive(Debug, Default)]
28pub struct RtuOverTcpCodec;
29
30impl Decoder for RtuOverTcpCodec {
31 type Item = Frame;
32 type Error = FrameError;
33
34 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
35 if src.len() < MIN_RTU_FRAME {
36 return Ok(None);
37 }
38
39 let max_len = src.len().min(MAX_RTU_ADU_SIZE);
40
41 let mut crc = 0xFFFF;
45 crc = crc16_update(crc, src[0]);
46 crc = crc16_update(crc, src[1]);
47 for candidate_len in MIN_RTU_FRAME..=max_len {
48 let data_end = candidate_len - 2;
49 let actual = u16::from_le_bytes([src[data_end], src[data_end + 1]]);
50 if crc == actual {
51 let unit_id = src[0];
52
53 let adu = src.split_to(candidate_len).freeze();
54 let pdu = adu.slice(1..adu.len() - 2);
55
56 return Ok(Some(Frame {
57 header: FrameHeader::Rtu { unit_id },
58 pdu,
59 }));
60 }
61
62 if candidate_len < max_len {
63 crc = crc16_update(crc, src[data_end]);
64 }
65 }
66
67 if src.len() > MAX_RTU_ADU_SIZE {
70 return Err(FrameError::Truncated);
71 }
72
73 Ok(None)
75 }
76}
77
78impl Encoder<Frame> for RtuOverTcpCodec {
79 type Error = FrameError;
80
81 fn encode(&mut self, item: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
82 let unit_id = match item.header {
83 FrameHeader::Rtu { unit_id } => unit_id,
84 FrameHeader::Mbap(h) => h.unit_id,
85 };
86 validate_outgoing_pdu(item.pdu.len())?;
87
88 dst.reserve(1 + item.pdu.len() + 2);
90
91 dst.put_u8(unit_id);
92 dst.put_slice(&item.pdu);
93
94 let crc_start = dst.len() - 1 - item.pdu.len();
96 let crc = crc16(&dst[crc_start..]);
97 dst.put_u16_le(crc);
98
99 Ok(())
100 }
101}
102
103fn validate_outgoing_pdu(pdu_len: usize) -> Result<(), FrameError> {
104 if pdu_len < MIN_PDU_LENGTH {
105 return Err(FrameError::InvalidPduLength {
106 length: pdu_len,
107 minimum: MIN_PDU_LENGTH,
108 });
109 }
110 if pdu_len > MAX_PDU_SIZE {
111 return Err(FrameError::PduLengthOverflow {
112 length: pdu_len,
113 maximum: MAX_PDU_SIZE,
114 });
115 }
116 Ok(())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::crc::verify_crc;
123
124 fn make_rtu_frame(unit_id: u8, pdu: &[u8]) -> Vec<u8> {
126 let mut buf = vec![unit_id];
127 buf.extend_from_slice(pdu);
128 let crc = crc16(&buf);
129 buf.extend_from_slice(&crc.to_le_bytes());
130 buf
131 }
132
133 #[test]
134 fn decode_single_frame() {
135 let raw = make_rtu_frame(0x01, &[0x03, 0x00, 0x00, 0x00, 0x0A]);
136 let mut buf = BytesMut::from(&raw[..]);
137 let mut codec = RtuOverTcpCodec;
138
139 let frame = codec.decode(&mut buf).unwrap().unwrap();
140 assert_eq!(frame.unit_id(), 0x01);
141 assert_eq!(&frame.pdu[..], &[0x03, 0x00, 0x00, 0x00, 0x0A]);
142 assert!(buf.is_empty());
143 }
144
145 #[test]
146 fn decode_two_back_to_back_frames() {
147 let frame1 = make_rtu_frame(0x01, &[0x03, 0x02, 0x00, 0x64]);
148 let frame2 = make_rtu_frame(0x02, &[0x06, 0x00, 0x01, 0x00, 0x03]);
149
150 let mut buf = BytesMut::new();
151 buf.extend_from_slice(&frame1);
152 buf.extend_from_slice(&frame2);
153
154 let mut codec = RtuOverTcpCodec;
155
156 let f1 = codec.decode(&mut buf).unwrap().unwrap();
157 assert_eq!(f1.unit_id(), 0x01);
158
159 let f2 = codec.decode(&mut buf).unwrap().unwrap();
160 assert_eq!(f2.unit_id(), 0x02);
161
162 assert!(buf.is_empty());
163 }
164
165 #[test]
166 fn decode_incomplete_returns_none() {
167 let raw = make_rtu_frame(0x01, &[0x03, 0x00]);
168 let mut buf = BytesMut::from(&raw[..3]);
170 let mut codec = RtuOverTcpCodec;
171
172 assert!(codec.decode(&mut buf).unwrap().is_none());
173 }
174
175 #[test]
176 fn decode_partial_then_complete() {
177 let raw = make_rtu_frame(0x01, &[0x03, 0x02, 0xAB, 0xCD]);
178 let mut buf = BytesMut::new();
179 let mut codec = RtuOverTcpCodec;
180
181 buf.extend_from_slice(&raw[..4]);
183 assert!(codec.decode(&mut buf).unwrap().is_none());
184
185 buf.extend_from_slice(&raw[4..]);
187 let frame = codec.decode(&mut buf).unwrap().unwrap();
188 assert_eq!(frame.unit_id(), 0x01);
189 assert_eq!(&frame.pdu[..], &[0x03, 0x02, 0xAB, 0xCD]);
190 }
191
192 #[test]
193 fn encode_roundtrip() {
194 let original_pdu = vec![0x03, 0x02, 0x00, 0x64];
195 let frame = Frame {
196 header: FrameHeader::Rtu { unit_id: 0x01 },
197 pdu: bytes::Bytes::from(original_pdu.clone()),
198 };
199
200 let mut dst = BytesMut::new();
201 let mut codec = RtuOverTcpCodec;
202 codec.encode(frame, &mut dst).unwrap();
203
204 let decoded = codec.decode(&mut dst).unwrap().unwrap();
206 assert_eq!(decoded.unit_id(), 0x01);
207 assert_eq!(&decoded.pdu[..], &original_pdu[..]);
208 }
209
210 #[test]
211 fn encode_rejects_empty_pdu() {
212 let frame = Frame {
213 header: FrameHeader::Rtu { unit_id: 0x01 },
214 pdu: bytes::Bytes::new(),
215 };
216
217 let mut dst = BytesMut::new();
218 let mut codec = RtuOverTcpCodec;
219
220 let err = codec.encode(frame, &mut dst).unwrap_err();
221 assert!(matches!(err, FrameError::InvalidPduLength { .. }));
222 }
223
224 #[test]
225 fn encode_rejects_oversized_pdu() {
226 let frame = Frame {
227 header: FrameHeader::Rtu { unit_id: 0x01 },
228 pdu: bytes::Bytes::from(vec![0x03; MAX_PDU_SIZE + 1]),
229 };
230
231 let mut dst = BytesMut::new();
232 let mut codec = RtuOverTcpCodec;
233
234 let err = codec.encode(frame, &mut dst).unwrap_err();
235 assert!(matches!(err, FrameError::PduLengthOverflow { .. }));
236 }
237
238 #[test]
239 fn decode_exception_response() {
240 let raw = make_rtu_frame(0x01, &[0x83, 0x02]);
242 let mut buf = BytesMut::from(&raw[..]);
243 let mut codec = RtuOverTcpCodec;
244
245 let frame = codec.decode(&mut buf).unwrap().unwrap();
246 assert_eq!(frame.unit_id(), 0x01);
247 assert_eq!(&frame.pdu[..], &[0x83, 0x02]);
248 }
249
250 #[test]
251 fn overflow_returns_error() {
252 let mut buf = BytesMut::new();
255 buf.extend_from_slice(&vec![0xAA; MAX_RTU_ADU_SIZE + 1]);
256 let mut codec = RtuOverTcpCodec;
257
258 let err = codec.decode(&mut buf).unwrap_err();
259 assert!(matches!(err, FrameError::Truncated));
260 }
261
262 #[test]
263 fn max_len_crc_miss_keeps_buffering() {
264 let raw = crc_miss_buffer(MAX_RTU_ADU_SIZE);
265 let mut buf = BytesMut::from(&raw[..]);
266 let mut codec = RtuOverTcpCodec;
267
268 assert!(codec.decode(&mut buf).unwrap().is_none());
269 assert_eq!(buf.len(), MAX_RTU_ADU_SIZE);
270 }
271
272 fn crc_miss_buffer(len: usize) -> Vec<u8> {
273 for salt in 0u8..=u8::MAX {
274 let candidate: Vec<u8> = (0..len)
275 .map(|i| {
276 let byte = u8::try_from(i % 251).expect("modulo 251 fits u8");
277 byte.wrapping_mul(37).wrapping_add(0xA5 ^ salt)
278 })
279 .collect();
280 if (MIN_RTU_FRAME..=len).all(|candidate_len| !verify_crc(&candidate[..candidate_len])) {
281 return candidate;
282 }
283 }
284 unreachable!("salted deterministic buffers should produce a CRC-miss case");
285 }
286}