modbus_core/codec/rtu/
client.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 client (master) specific functions.
5use super::*;
6
7/// Encode and RTU request.
8pub fn encode_request(adu: RequestAdu, buf: &mut [u8]) -> Result<usize> {
9    let RequestAdu { hdr, pdu } = adu;
10    if buf.len() < 2 {
11        return Err(Error::BufferSize);
12    }
13    let len = pdu.encode(&mut buf[1..])?;
14    if buf.len() < len + 3 {
15        return Err(Error::BufferSize);
16    }
17    buf[0] = hdr.slave;
18    let crc = crc16(&buf[0..=len]);
19    BigEndian::write_u16(&mut buf[len + 1..], crc);
20    Ok(len + 3)
21}
22
23/// Decode an RTU response.
24pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu<'_>>> {
25    if buf.is_empty() {
26        return Ok(None);
27    }
28    decode(DecoderType::Response, buf)
29        .and_then(|frame| {
30            let Some((DecodedFrame { slave, pdu }, _frame_pos)) = frame else {
31                return Ok(None);
32            };
33            let hdr = Header { slave };
34            // Decoding of the PDU should are unlikely to fail due
35            // to transmission errors, because the frame's bytes
36            // have already been verified with the CRC.
37
38            let response = ExceptionResponse::try_from(pdu)
39                .map(|er| ResponsePdu(Err(er)))
40                .or_else(|_| Response::try_from(pdu).map(|r| ResponsePdu(Ok(r))))
41                .map(|pdu| Some(ResponseAdu { hdr, pdu }));
42            #[cfg(feature = "log")]
43            if let Err(error) = response {
44                // Unrecoverable error
45                log::error!("Failed to decode Response PDU: {error}");
46            }
47            response
48        })
49        .map_err(|_| {
50            // Decoding the transport frame is non-destructive and must
51            // never fail!
52            unreachable!();
53        })
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn decode_empty_response() {
62        let req = decode_response(&[]).unwrap();
63        assert!(req.is_none());
64    }
65
66    #[test]
67    fn decode_partly_received_response() {
68        let buf = &[
69            0x12, // slave address
70            0x16, // function code
71        ];
72        let req = decode_response(buf).unwrap();
73        assert!(req.is_none());
74    }
75
76    #[test]
77    fn encode_write_single_register_request() {
78        let mut buf = [0u8; 255];
79        let sz = encode_request(
80            RequestAdu {
81                hdr: Header { slave: 0x12 },
82                pdu: RequestPdu(Request::WriteSingleRegister(0x2222, 0xABCD)),
83            },
84            &mut buf,
85        )
86        .expect("Error encoding request");
87
88        let req = &buf[..sz];
89        assert_eq!(
90            req,
91            &[
92                0x12, // slave address
93                0x06, // function code
94                0x22, // addr
95                0x22, // addr
96                0xAB, // value
97                0xCD, // value
98                0x9F, // crc
99                0xBE, // crc
100            ]
101        );
102    }
103
104    #[test]
105    fn decode_write_single_register_response() {
106        use crate::frame::Response;
107        let rsp = &[0x12, 0x06, 0x22, 0x22, 0xAB, 0xCD, 0x9F, 0xBE];
108
109        assert!(matches!(
110            decode_response(rsp),
111            Ok(Some(ResponseAdu {
112                hdr: Header { slave: 0x12 },
113                pdu: ResponsePdu(Ok(Response::WriteSingleRegister(0x2222, 0xABCD)))
114            }))
115        ));
116    }
117
118    #[test]
119    fn decode_malformed_write_single_register_response() {
120        let rsp = &[0x12, 0x06, 0x22, 0x22, 0xAB, 0x65, 0x9E];
121
122        assert!(matches!(decode_response(rsp), Ok(None)));
123    }
124
125    #[test]
126    fn decode_bad_crc_write_single_register_response() {
127        let rsp = &[0x12, 0x06, 0x22, 0x22, 0xAB, 0xCD, 0x5F, 0xBE];
128
129        assert!(matches!(decode_response(rsp), Ok(None)));
130    }
131}