modbus_core/codec/rtu/
mod.rs

1// SPDX-FileCopyrightText: Copyright (c) 2018-2025 slowtec GmbH <post@slowtec.de>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Modbus RTU
5
6use super::*;
7use byteorder::{BigEndian, ByteOrder};
8
9pub mod client;
10pub mod server;
11pub use crate::frame::rtu::*;
12
13// [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf), page 13
14// "The maximum size of a MODBUS RTU frame is 256 bytes."
15pub const MAX_FRAME_LEN: usize = 256;
16
17/// An extracted RTU PDU frame.
18#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct DecodedFrame<'a> {
21    pub slave: SlaveId,
22    pub pdu: &'a [u8],
23}
24
25/// The location of all bytes that belong to the frame.
26#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct FrameLocation {
29    /// The index where the frame starts
30    pub start: usize,
31    /// Number of bytes that belong to the frame
32    pub size: usize,
33}
34
35/// Decode RTU PDU frames from a buffer.
36pub fn decode(
37    decoder_type: DecoderType,
38    buf: &[u8],
39) -> Result<Option<(DecodedFrame<'_>, FrameLocation)>> {
40    use DecoderType::{Request, Response};
41    let mut drop_cnt = 0;
42
43    if buf.is_empty() {
44        return Err(Error::BufferSize);
45    }
46
47    loop {
48        let mut retry = false;
49        if drop_cnt + 1 >= buf.len() {
50            return Ok(None);
51        }
52        let raw_frame = &buf[drop_cnt..];
53        let res = match decoder_type {
54            Request => request_pdu_len(raw_frame),
55            Response => response_pdu_len(raw_frame),
56        }
57        .and_then(|pdu_len| {
58            retry = false;
59            let Some(pdu_len) = pdu_len else {
60                // Incomplete frame
61                return Ok(None);
62            };
63            extract_frame(raw_frame, pdu_len).map(|x| {
64                x.map(|res| {
65                    let frame_location = FrameLocation {
66                        start: drop_cnt,
67                        size: pdu_len + 3, // TODO: use 'const FOO:usize = 3;'
68                    };
69                    (res, frame_location)
70                })
71            })
72        })
73        .or_else(|err| {
74            if drop_cnt + 1 >= MAX_FRAME_LEN {
75                #[cfg(feature = "log")]
76                log::error!(
77                    "Giving up to decode frame after dropping {drop_cnt} byte(s): {:X?}",
78                    &buf[0..drop_cnt]
79                );
80                return Err(err);
81            }
82            #[cfg(feature = "log")]
83            log::warn!(
84                "Failed to decode {} frame: {err}",
85                match decoder_type {
86                    Request => "request",
87                    Response => "response",
88                }
89            );
90            drop_cnt += 1;
91            retry = true;
92            Ok(None)
93        });
94
95        if !retry {
96            return res;
97        }
98    }
99}
100
101/// Extract a PDU frame out of a buffer.
102#[allow(clippy::similar_names)]
103pub fn extract_frame(buf: &[u8], pdu_len: usize) -> Result<Option<DecodedFrame<'_>>> {
104    if buf.is_empty() {
105        return Err(Error::BufferSize);
106    }
107
108    let adu_len = 1 + pdu_len;
109    if buf.len() >= adu_len + 2 {
110        let (adu_buf, buf) = buf.split_at(adu_len);
111        let (crc_buf, _) = buf.split_at(2);
112        // Read trailing CRC and verify ADU
113        let expected_crc = BigEndian::read_u16(crc_buf);
114        let actual_crc = crc16(adu_buf);
115        if expected_crc != actual_crc {
116            return Err(Error::Crc(expected_crc, actual_crc));
117        }
118        let (slave_id, pdu_data) = adu_buf.split_at(1);
119        let slave_id = slave_id[0];
120        return Ok(Some(DecodedFrame {
121            slave: slave_id,
122            pdu: pdu_data,
123        }));
124    }
125    // Incomplete frame
126    Ok(None)
127}
128
129/// Calculate the CRC (Cyclic Redundancy Check) sum.
130#[must_use]
131pub fn crc16(data: &[u8]) -> u16 {
132    let mut crc = 0xFFFF;
133    for x in data {
134        crc ^= u16::from(*x);
135        for _ in 0..8 {
136            // if we followed clippy's suggestion to move out the crc >>= 1, the condition may not be met any more
137            // the recommended action therefore makes no sense and it is better to allow this lint
138            #[allow(clippy::branches_sharing_code)]
139            if (crc & 0x0001) != 0 {
140                crc >>= 1;
141                crc ^= 0xA001;
142            } else {
143                crc >>= 1;
144            }
145        }
146    }
147    crc.rotate_right(8)
148}
149
150/// Extract the PDU length out of the ADU request buffer.
151pub const fn request_pdu_len(adu_buf: &[u8]) -> Result<Option<usize>> {
152    if adu_buf.len() < 2 {
153        return Ok(None);
154    }
155    let fn_code = adu_buf[1];
156    let len = match fn_code {
157        0x01..=0x06 => Some(5),
158        0x07 | 0x0B | 0x0C | 0x11 => Some(1),
159        0x0F | 0x10 => {
160            if adu_buf.len() > 4 {
161                Some(6 + adu_buf[4] as usize)
162            } else {
163                // incomplete frame
164                None
165            }
166        }
167        0x16 => Some(7),
168        0x18 => Some(3),
169        0x17 => {
170            if adu_buf.len() > 10 {
171                Some(10 + adu_buf[10] as usize)
172            } else {
173                // incomplete frame
174                None
175            }
176        }
177        _ => {
178            return Err(Error::FnCode(fn_code));
179        }
180    };
181    Ok(len)
182}
183
184/// Extract the PDU length out of the ADU response buffer.
185pub fn response_pdu_len(adu_buf: &[u8]) -> Result<Option<usize>> {
186    if adu_buf.len() < 2 {
187        return Ok(None);
188    }
189    let fn_code = adu_buf[1];
190    let len = match fn_code {
191        0x01..=0x04 | 0x0C | 0x17 => {
192            if adu_buf.len() > 2 {
193                Some(2 + adu_buf[2] as usize)
194            } else {
195                // incomplete frame
196                None
197            }
198        }
199        0x05 | 0x06 | 0x0B | 0x0F | 0x10 => Some(5),
200        0x07 | 0x81..=0xAB => Some(2),
201        0x16 => Some(7),
202        0x18 => {
203            if adu_buf.len() > 3 {
204                Some(3 + BigEndian::read_u16(&adu_buf[2..=3]) as usize)
205            } else {
206                // incomplete frame
207                None
208            }
209        }
210        _ => return Err(Error::FnCode(fn_code)),
211    };
212    Ok(len)
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_calc_crc16() {
221        let msg = &[0x01, 0x03, 0x08, 0x2B, 0x00, 0x02];
222        assert_eq!(crc16(msg), 0xB663);
223
224        let msg = &[0x01, 0x03, 0x04, 0x00, 0x20, 0x00, 0x00];
225        assert_eq!(crc16(msg), 0xFBF9);
226    }
227
228    #[test]
229    fn test_request_pdu_len() {
230        let buf = &mut [0x66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
231        assert!(request_pdu_len(buf).is_err());
232
233        buf[1] = 0x01;
234        assert_eq!(request_pdu_len(buf).unwrap(), Some(5));
235
236        buf[1] = 0x02;
237        assert_eq!(request_pdu_len(buf).unwrap(), Some(5));
238
239        buf[1] = 0x03;
240        assert_eq!(request_pdu_len(buf).unwrap(), Some(5));
241
242        buf[1] = 0x04;
243        assert_eq!(request_pdu_len(buf).unwrap(), Some(5));
244
245        buf[1] = 0x05;
246        assert_eq!(request_pdu_len(buf).unwrap(), Some(5));
247
248        buf[1] = 0x06;
249        assert_eq!(request_pdu_len(buf).unwrap(), Some(5));
250
251        buf[1] = 0x07;
252        assert_eq!(request_pdu_len(buf).unwrap(), Some(1));
253
254        // TODO: 0x08
255
256        buf[1] = 0x0B;
257        assert_eq!(request_pdu_len(buf).unwrap(), Some(1));
258
259        buf[1] = 0x0C;
260        assert_eq!(request_pdu_len(buf).unwrap(), Some(1));
261
262        buf[1] = 0x0F;
263        buf[4] = 99;
264        assert_eq!(request_pdu_len(buf).unwrap(), Some(105));
265
266        buf[1] = 0x10;
267        buf[4] = 99;
268        assert_eq!(request_pdu_len(buf).unwrap(), Some(105));
269
270        buf[1] = 0x11;
271        assert_eq!(request_pdu_len(buf).unwrap(), Some(1));
272
273        // TODO: 0x14
274
275        // TODO: 0x15
276
277        buf[1] = 0x16;
278        assert_eq!(request_pdu_len(buf).unwrap(), Some(7));
279
280        buf[1] = 0x17;
281        buf[10] = 99; // write byte count
282        assert_eq!(request_pdu_len(buf).unwrap(), Some(109));
283
284        buf[1] = 0x18;
285        assert_eq!(request_pdu_len(buf).unwrap(), Some(3));
286
287        // TODO: 0x2B
288    }
289
290    #[test]
291    fn test_get_response_pdu_len() {
292        let buf = &mut [0x66, 0x01, 99];
293        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
294
295        let buf = &mut [0x66, 0x00, 99, 0x00];
296        assert_eq!(response_pdu_len(buf).err().unwrap(), Error::FnCode(0));
297
298        let buf = &mut [0x66, 0xee, 99, 0x00];
299        assert_eq!(response_pdu_len(buf).err().unwrap(), Error::FnCode(0xee));
300
301        buf[1] = 0x01;
302        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
303
304        buf[1] = 0x02;
305        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
306
307        buf[1] = 0x03;
308        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
309
310        buf[1] = 0x04;
311        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
312
313        buf[1] = 0x05;
314        assert_eq!(response_pdu_len(buf).unwrap(), Some(5));
315
316        buf[1] = 0x06;
317        assert_eq!(response_pdu_len(buf).unwrap(), Some(5));
318
319        buf[1] = 0x07;
320        assert_eq!(response_pdu_len(buf).unwrap(), Some(2));
321
322        // TODO: 0x08
323
324        buf[1] = 0x0B;
325        assert_eq!(response_pdu_len(buf).unwrap(), Some(5));
326
327        buf[1] = 0x0C;
328        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
329
330        buf[1] = 0x0F;
331        assert_eq!(response_pdu_len(buf).unwrap(), Some(5));
332
333        buf[1] = 0x10;
334        assert_eq!(response_pdu_len(buf).unwrap(), Some(5));
335
336        // TODO: 0x11
337
338        // TODO: 0x14
339
340        // TODO: 0x15
341
342        buf[1] = 0x16;
343        assert_eq!(response_pdu_len(buf).unwrap(), Some(7));
344
345        buf[1] = 0x17;
346        assert_eq!(response_pdu_len(buf).unwrap(), Some(101));
347
348        buf[1] = 0x18;
349        buf[2] = 0x01; // byte count Hi
350        buf[3] = 0x00; // byte count Lo
351        assert_eq!(response_pdu_len(buf).unwrap(), Some(259));
352
353        // TODO: 0x2B
354
355        for i in 0x81..0xAB {
356            buf[1] = i;
357            assert_eq!(response_pdu_len(buf).unwrap(), Some(2));
358        }
359    }
360
361    mod frame_decoder {
362
363        use super::*;
364
365        #[test]
366        fn extract_partly_received_rtu_frame() {
367            let buf = &[
368                0x12, // slave address
369                0x02, // function code
370                0x03, // byte count
371                0x00, // data
372                0x00, // data
373                0x00, // data
374                0x00, // CRC first byte
375                      // missing crc second byte
376            ];
377            let pdu_len = request_pdu_len(buf).unwrap().unwrap();
378            let res = extract_frame(buf, pdu_len).unwrap();
379            assert!(res.is_none());
380        }
381
382        #[test]
383        fn extract_usual_rtu_response_frame() {
384            let buf = &[
385                0x01, // slave address
386                0x03, // function code
387                0x04, // byte count
388                0x89, //
389                0x02, //
390                0x42, //
391                0xC7, //
392                0x00, // crc
393                0x9D, // crc
394                0x03, // -- start of next frame
395            ];
396            let pdu_len = response_pdu_len(buf).unwrap().unwrap();
397            let DecodedFrame { slave, pdu } = extract_frame(buf, pdu_len).unwrap().unwrap();
398            assert_eq!(slave, 0x01);
399            assert_eq!(pdu.len(), 6);
400        }
401
402        #[test]
403        fn decode_rtu_response_drop_invalid_bytes() {
404            let buf = &[
405                0x42, // dropped byte
406                0x43, // dropped byte
407                0x01, // slave address
408                0x03, // function code
409                0x04, // byte count
410                0x89, //
411                0x02, //
412                0x42, //
413                0xC7, //
414                0x00, // crc
415                0x9D, // crc
416                0x00,
417            ];
418            let (frame, location) = decode(DecoderType::Response, buf).unwrap().unwrap();
419            assert_eq!(frame.slave, 0x01);
420            assert_eq!(frame.pdu.len(), 6);
421            assert_eq!(location.start, 2);
422            assert_eq!(location.size, 9);
423        }
424
425        #[test]
426        fn decode_rtu_response_with_max_drops() {
427            let buf = &[0x42; 10];
428            assert!(decode(DecoderType::Response, buf).unwrap().is_none());
429
430            let buf = &mut [0x42; MAX_FRAME_LEN * 2];
431            buf[256] = 0x01; // slave address
432            buf[257] = 0x03; // function code
433            buf[258] = 0x04; // byte count
434            buf[259] = 0x89; //
435            buf[260] = 0x02; //
436            buf[261] = 0x42; //
437            buf[262] = 0xC7; //
438            buf[263] = 0x00; // crc
439            buf[264] = 0x9D; // crc
440            assert!(decode(DecoderType::Response, buf).is_err());
441        }
442    }
443}