Skip to content

Commit f0822a2

Browse files
authored
Fix: Encoding of RTU CRC on Big Endian platforms (#347)
1 parent 05df161 commit f0822a2

File tree

3 files changed

+53
-50
lines changed

3 files changed

+53
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
## Unreleased
77

8+
- Fix: Encoding of RTU CRC on Big Endian platforms.
89
- Server: Remove unneeded `Unpin` trait bounds for abort signal.
910

1011
## v0.16.4 (2025-09-17)

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ all-features = true
2525
async-trait = "0.1.77"
2626
byteorder = "1.5.0"
2727
bytes = "1.5.0"
28+
crc = "3.3.0"
2829
futures-core = { version = "0.3.30", optional = true, default-features = false }
2930
futures-util = { version = "0.3.30", optional = true, default-features = false }
3031
log = "0.4.20"

src/codec/rtu.rs

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// SPDX-FileCopyrightText: Copyright (c) 2017-2025 slowtec GmbH <post@slowtec.de>
22
// SPDX-License-Identifier: MIT OR Apache-2.0
33

4-
use std::io::{Cursor, Error, ErrorKind, Result};
4+
use std::io;
55

6-
use byteorder::{BigEndian, ReadBytesExt as _};
6+
use byteorder::{LittleEndian, ReadBytesExt as _};
77
use smallvec::SmallVec;
88
use tokio_util::codec::{Decoder, Encoder};
99

@@ -15,6 +15,8 @@ use crate::{
1515

1616
use super::{encode_request_pdu, request_pdu_size, RequestPdu};
1717

18+
const MODBUS_CRC: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_MODBUS);
19+
1820
// [Modbus over Serial Line Specification and Implementation Guide V1.02](http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf), page 13
1921
// "The maximum size of a Modbus RTU frame is 256 bytes."
2022
const MAX_FRAME_LEN: usize = 256;
@@ -39,7 +41,7 @@ impl FrameDecoder {
3941
&mut self,
4042
buf: &mut BytesMut,
4143
pdu_len: usize,
42-
) -> Result<Option<(SlaveId, Bytes)>> {
44+
) -> io::Result<Option<(SlaveId, Bytes)>> {
4345
const CRC_BYTE_COUNT: usize = 2;
4446

4547
let adu_len = 1 + pdu_len;
@@ -53,9 +55,8 @@ impl FrameDecoder {
5355
let crc_buf = buf.split_to(CRC_BYTE_COUNT);
5456

5557
// Read trailing CRC and verify ADU
56-
let crc_result = Cursor::new(&crc_buf)
57-
.read_u16::<BigEndian>()
58-
.and_then(|crc| check_crc(&adu_buf, crc));
58+
let crc_result =
59+
read_crc(&mut io::Cursor::new(&crc_buf)).and_then(|crc| check_crc(&adu_buf, crc));
5960

6061
if let Err(err) = crc_result {
6162
// CRC is invalid - restore the input buffer
@@ -126,7 +127,7 @@ pub(crate) struct ServerCodec {
126127
}
127128

128129
#[cfg(any(feature = "rtu-over-tcp-server", feature = "rtu-server"))]
129-
fn get_request_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
130+
fn get_request_pdu_len(adu_buf: &BytesMut) -> io::Result<Option<usize>> {
130131
if let Some(fn_code) = adu_buf.get(1) {
131132
let len = match fn_code {
132133
0x01..=0x06 => 5,
@@ -150,8 +151,8 @@ fn get_request_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
150151
4
151152
}
152153
_ => {
153-
return Err(Error::new(
154-
ErrorKind::InvalidData,
154+
return Err(io::Error::new(
155+
io::ErrorKind::InvalidData,
155156
format!("Invalid function code: 0x{fn_code:0>2X}"),
156157
));
157158
}
@@ -162,7 +163,7 @@ fn get_request_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
162163
}
163164
}
164165

165-
fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
166+
fn get_response_pdu_len(adu_buf: &BytesMut) -> io::Result<Option<usize>> {
166167
if let Some(fn_code) = adu_buf.get(1) {
167168
#[allow(clippy::match_same_arms)]
168169
let len = match fn_code {
@@ -215,8 +216,8 @@ fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
215216
offset - 1 // remove slave address byte
216217
}
217218
_ => {
218-
return Err(Error::new(
219-
ErrorKind::InvalidData,
219+
return Err(io::Error::new(
220+
io::ErrorKind::InvalidData,
220221
format!("Invalid function code: 0x{fn_code:0>2X}"),
221222
));
222223
}
@@ -228,50 +229,45 @@ fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
228229
}
229230

230231
fn calc_crc(data: &[u8]) -> u16 {
231-
let mut crc = 0xFFFF;
232-
for x in data {
233-
crc ^= u16::from(*x);
234-
for _ in 0..8 {
235-
let crc_odd = (crc & 0x0001) != 0;
236-
crc >>= 1;
237-
if crc_odd {
238-
crc ^= 0xA001;
239-
}
240-
}
241-
}
242-
// In contrast to all other 16-bit data the CRC is stored in
243-
// little-endian instead of big-endian byte order. We account for
244-
// this oddity right here in the calculation and read/write all
245-
// 16-bit values consistently in big-endian byte order.
246-
crc.rotate_right(8)
232+
MODBUS_CRC.checksum(data)
247233
}
248234

249-
fn check_crc(adu_data: &[u8], expected_crc: u16) -> Result<()> {
235+
fn check_crc(adu_data: &[u8], expected_crc: u16) -> io::Result<()> {
250236
let actual_crc = calc_crc(adu_data);
251237
if expected_crc != actual_crc {
252-
return Err(Error::new(
253-
ErrorKind::InvalidData,
238+
return Err(io::Error::new(
239+
io::ErrorKind::InvalidData,
254240
format!("Invalid CRC: expected = 0x{expected_crc:0>4X}, actual = 0x{actual_crc:0>4X}"),
255241
));
256242
}
257243
Ok(())
258244
}
259245

246+
fn write_crc(buf: &mut BytesMut, crc: u16) {
247+
// The CRC is encoded with Little Endian byte order.
248+
buf.put_u16_le(crc);
249+
}
250+
251+
fn read_crc(read: &mut impl io::Read) -> io::Result<u16> {
252+
// The CRC is encoded with Little Endian byte order.
253+
read.read_u16::<LittleEndian>()
254+
}
255+
260256
#[cfg(any(feature = "rtu-over-tcp-server", feature = "rtu-server"))]
261257
impl Decoder for RequestDecoder {
262258
type Item = (SlaveId, Bytes);
263-
type Error = Error;
259+
type Error = io::Error;
264260

265-
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<(SlaveId, Bytes)>> {
261+
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<(SlaveId, Bytes)>> {
266262
decode("request", &mut self.frame_decoder, get_request_pdu_len, buf)
267263
}
268264
}
269265

270266
impl Decoder for ResponseDecoder {
271267
type Item = (SlaveId, Bytes);
272-
type Error = Error;
268+
type Error = io::Error;
273269

274-
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<(SlaveId, Bytes)>> {
270+
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<(SlaveId, Bytes)>> {
275271
decode(
276272
"response",
277273
&mut self.frame_decoder,
@@ -286,9 +282,9 @@ fn decode<F>(
286282
frame_decoder: &mut FrameDecoder,
287283
get_pdu_len: F,
288284
buf: &mut BytesMut,
289-
) -> Result<Option<(SlaveId, Bytes)>>
285+
) -> io::Result<Option<(SlaveId, Bytes)>>
290286
where
291-
F: Fn(&BytesMut) -> Result<Option<usize>>,
287+
F: Fn(&BytesMut) -> io::Result<Option<usize>>,
292288
{
293289
const MAX_RETRIES: usize = 20;
294290

@@ -313,14 +309,17 @@ where
313309

314310
// Maximum number of retries exceeded.
315311
log::error!("Giving up to decode frame after {MAX_RETRIES} retries");
316-
Err(Error::new(ErrorKind::InvalidData, "Too many retries"))
312+
Err(io::Error::new(
313+
io::ErrorKind::InvalidData,
314+
"Too many retries",
315+
))
317316
}
318317

319318
impl Decoder for ClientCodec {
320319
type Item = ResponseAdu;
321-
type Error = Error;
320+
type Error = io::Error;
322321

323-
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<ResponseAdu>> {
322+
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<ResponseAdu>> {
324323
let Some((slave_id, pdu_data)) = self.decoder.decode(buf)? else {
325324
return Ok(None);
326325
};
@@ -343,9 +342,9 @@ impl Decoder for ClientCodec {
343342
#[cfg(any(feature = "rtu-over-tcp-server", feature = "rtu-server"))]
344343
impl Decoder for ServerCodec {
345344
type Item = RequestAdu<'static>;
346-
type Error = Error;
345+
type Error = io::Error;
347346

348-
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<RequestAdu<'static>>> {
347+
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<RequestAdu<'static>>> {
349348
let Some((slave_id, pdu_data)) = self.decoder.decode(buf)? else {
350349
return Ok(None);
351350
};
@@ -366,9 +365,9 @@ impl Decoder for ServerCodec {
366365
}
367366

368367
impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
369-
type Error = Error;
368+
type Error = io::Error;
370369

371-
fn encode(&mut self, adu: RequestAdu<'a>, buf: &mut BytesMut) -> Result<()> {
370+
fn encode(&mut self, adu: RequestAdu<'a>, buf: &mut BytesMut) -> io::Result<()> {
372371
let RequestAdu {
373372
hdr,
374373
pdu: RequestPdu(request),
@@ -379,16 +378,16 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
379378
buf.put_u8(hdr.slave_id);
380379
encode_request_pdu(buf, &request);
381380
let crc = calc_crc(&buf[buf_offset..]);
382-
buf.put_u16(crc);
381+
write_crc(buf, crc);
383382
Ok(())
384383
}
385384
}
386385

387386
#[cfg(any(feature = "rtu-over-tcp-server", feature = "rtu-server"))]
388387
impl Encoder<ResponseAdu> for ServerCodec {
389-
type Error = Error;
388+
type Error = io::Error;
390389

391-
fn encode(&mut self, adu: ResponseAdu, buf: &mut BytesMut) -> Result<()> {
390+
fn encode(&mut self, adu: ResponseAdu, buf: &mut BytesMut) -> io::Result<()> {
392391
let ResponseAdu {
393392
hdr,
394393
pdu: super::ResponsePdu(pdu_res),
@@ -399,7 +398,7 @@ impl Encoder<ResponseAdu> for ServerCodec {
399398
buf.put_u8(hdr.slave_id);
400399
super::encode_response_result_pdu(buf, &pdu_res);
401400
let crc = calc_crc(&buf[buf_offset..]);
402-
buf.put_u16(crc);
401+
write_crc(buf, crc);
403402
Ok(())
404403
}
405404
}
@@ -411,11 +410,13 @@ mod tests {
411410

412411
#[test]
413412
fn test_calc_crc() {
413+
// See also: <https://crccalc.com/>
414+
414415
let msg = [0x01, 0x03, 0x08, 0x2B, 0x00, 0x02];
415-
assert_eq!(calc_crc(&msg), 0xB663);
416+
assert_eq!(calc_crc(&msg), 0x63B6);
416417

417418
let msg = [0x01, 0x03, 0x04, 0x00, 0x20, 0x00, 0x00];
418-
assert_eq!(calc_crc(&msg), 0xFBF9);
419+
assert_eq!(calc_crc(&msg), 0xF9FB);
419420
}
420421

421422
#[test]

0 commit comments

Comments
 (0)