Skip to main content

stackforge_core/layer/modbus/
crc.rs

1//! Modbus CRC-16 and LRC checksum functions.
2//!
3//! - **CRC-16/MODBUS**: Used by Modbus RTU frames. Polynomial 0xA001 (reflected),
4//!   init 0xFFFF, no final XOR.
5//! - **LRC**: Longitudinal Redundancy Check used by Modbus ASCII frames.
6//!   Two's complement of the sum of all bytes (mod 256).
7
8/// Compute the Modbus CRC-16 checksum.
9///
10/// Uses the reflected polynomial 0xA001, initial value 0xFFFF.
11/// The result is in little-endian byte order when appended to an RTU frame
12/// (low byte first, high byte second).
13#[must_use]
14pub fn modbus_crc16(data: &[u8]) -> u16 {
15    let mut crc: u16 = 0xFFFF;
16    for &byte in data {
17        crc ^= u16::from(byte);
18        for _ in 0..8 {
19            if crc & 0x0001 != 0 {
20                crc = (crc >> 1) ^ 0xA001;
21            } else {
22                crc >>= 1;
23            }
24        }
25    }
26    crc
27}
28
29/// Verify the CRC-16 of a complete Modbus RTU frame (data + 2-byte CRC).
30///
31/// The last two bytes of `frame` are the CRC in little-endian order.
32/// Returns true if the CRC is correct.
33#[must_use]
34pub fn verify_crc16(frame: &[u8]) -> bool {
35    if frame.len() < 3 {
36        return false;
37    }
38    let data = &frame[..frame.len() - 2];
39    let expected = u16::from_le_bytes([frame[frame.len() - 2], frame[frame.len() - 1]]);
40    modbus_crc16(data) == expected
41}
42
43/// Compute the Modbus LRC (Longitudinal Redundancy Check).
44///
45/// LRC is the two's complement of the 8-bit sum of all bytes.
46/// Used in Modbus ASCII mode: the LRC byte is transmitted as two ASCII hex chars.
47#[must_use]
48pub fn modbus_lrc(data: &[u8]) -> u8 {
49    let sum: u8 = data.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
50    sum.wrapping_neg()
51}
52
53/// Verify the LRC of a data slice where the last byte is the LRC.
54///
55/// Returns true if the LRC is correct (sum of all bytes including LRC == 0).
56#[must_use]
57pub fn verify_lrc(data: &[u8]) -> bool {
58    if data.is_empty() {
59        return false;
60    }
61    let sum: u8 = data.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
62    sum == 0
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_crc16_check_value() {
71        // Standard CRC-16/MODBUS check value: "123456789" -> 0x4B37
72        assert_eq!(modbus_crc16(b"123456789"), 0x4B37);
73    }
74
75    #[test]
76    fn test_crc16_known_vector() {
77        // Slave=0x01, FC=0x03, Addr=0x0000, Qty=0x000A
78        // Verify round-trip consistency
79        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
80        let crc = modbus_crc16(&data);
81        let mut frame = data.to_vec();
82        frame.push((crc & 0xFF) as u8);
83        frame.push((crc >> 8) as u8);
84        assert!(verify_crc16(&frame));
85    }
86
87    #[test]
88    fn test_crc16_empty() {
89        assert_eq!(modbus_crc16(&[]), 0xFFFF);
90    }
91
92    #[test]
93    fn test_crc16_single_byte() {
94        // CRC of [0x00]: init=0xFFFF, XOR with 0x00 -> process 8 bits
95        let crc = modbus_crc16(&[0x00]);
96        // Self-consistency: verify round-trip
97        let mut frame = vec![0x00u8];
98        frame.push((crc & 0xFF) as u8);
99        frame.push((crc >> 8) as u8);
100        assert!(verify_crc16(&frame));
101    }
102
103    #[test]
104    fn test_verify_crc16_valid() {
105        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
106        let crc = modbus_crc16(&data);
107        let mut frame = data.to_vec();
108        frame.push((crc & 0xFF) as u8); // low byte
109        frame.push((crc >> 8) as u8); // high byte
110        assert!(verify_crc16(&frame));
111    }
112
113    #[test]
114    fn test_verify_crc16_corrupted() {
115        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
116        let crc = modbus_crc16(&data);
117        let mut frame = data.to_vec();
118        frame.push((crc & 0xFF) as u8);
119        frame.push(((crc >> 8) as u8) ^ 0xFF); // corrupt high byte
120        assert!(!verify_crc16(&frame));
121    }
122
123    #[test]
124    fn test_verify_crc16_too_short() {
125        assert!(!verify_crc16(&[0x01, 0x02]));
126        assert!(!verify_crc16(&[]));
127    }
128
129    #[test]
130    fn test_lrc_known_vector() {
131        // Slave=0x01, FC=0x03, Addr=0x0000, Qty=0x000A
132        // Sum = 0x01 + 0x03 + 0x00 + 0x00 + 0x00 + 0x0A = 0x0E
133        // LRC = -(0x0E) mod 256 = 0xF2
134        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
135        assert_eq!(modbus_lrc(&data), 0xF2);
136    }
137
138    #[test]
139    fn test_lrc_empty() {
140        assert_eq!(modbus_lrc(&[]), 0x00);
141    }
142
143    #[test]
144    fn test_lrc_self_check() {
145        // sum of data + LRC should be 0
146        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
147        let lrc = modbus_lrc(&data);
148        let mut with_lrc = data.to_vec();
149        with_lrc.push(lrc);
150        assert!(verify_lrc(&with_lrc));
151    }
152
153    #[test]
154    fn test_verify_lrc_valid() {
155        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xF2];
156        assert!(verify_lrc(&data));
157    }
158
159    #[test]
160    fn test_verify_lrc_invalid() {
161        let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xFF];
162        assert!(!verify_lrc(&data));
163    }
164
165    #[test]
166    fn test_verify_lrc_empty() {
167        assert!(!verify_lrc(&[]));
168    }
169}