Skip to main content

pamoja_modbus/
crc.rs

1//! CRC-16/MODBUS, the integrity check every Modbus RTU frame carries.
2
3/// Computes the CRC-16/MODBUS of a byte slice.
4///
5/// This is the checksum a Modbus RTU frame ends with, and the reason a receiver can
6/// trust a frame that arrived over a long, electrically noisy cable: the polynomial is
7/// `0xA001` (the reflected form of `0x8005`), the initial value is `0xFFFF`, input and
8/// output are reflected, and there is no final inversion. A frame appends the result
9/// low byte first.
10///
11/// # Arguments
12///
13/// * `data` - the bytes to check: the unit address through the end of the PDU, that is,
14///   the whole frame except the two CRC bytes themselves.
15///
16/// # Returns
17///
18/// The 16-bit CRC.
19///
20/// # Examples
21///
22/// ```
23/// use pamoja_modbus::crc16;
24///
25/// // The standard CRC-16/MODBUS check value over the ASCII digits "123456789".
26/// assert_eq!(crc16(b"123456789"), 0x4B37);
27/// ```
28pub fn crc16(data: &[u8]) -> u16 {
29    let mut crc: u16 = 0xFFFF;
30    for &byte in data {
31        crc ^= u16::from(byte);
32        for _ in 0..8 {
33            if crc & 1 != 0 {
34                crc = (crc >> 1) ^ 0xA001;
35            } else {
36                crc >>= 1;
37            }
38        }
39    }
40    crc
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn matches_the_standard_check_value() {
49        assert_eq!(crc16(b"123456789"), 0x4B37);
50    }
51
52    #[test]
53    fn matches_a_known_read_request_frame() {
54        // The classic read-holding-registers example: the CRC of the frame body
55        // 01 03 00 00 00 02 is 0x0BC4, which the wire carries as C4 0B.
56        assert_eq!(crc16(&[0x01, 0x03, 0x00, 0x00, 0x00, 0x02]), 0x0BC4);
57    }
58
59    #[test]
60    fn matches_the_spec_read_request_frame() {
61        // The Modbus specification's read-holding-registers example, unit 0x11.
62        assert_eq!(crc16(&[0x11, 0x03, 0x00, 0x6B, 0x00, 0x03]), 0x8776);
63    }
64
65    #[test]
66    fn an_empty_slice_is_the_initial_value() {
67        assert_eq!(crc16(&[]), 0xFFFF);
68    }
69}