Skip to main content

rustmod_core/frame/
rtu.rs

1use crate::encoding::Writer;
2use crate::{DecodeError, EncodeError};
3
4const fn build_crc16_table() -> [u16; 256] {
5    let mut table = [0u16; 256];
6    let mut i = 0;
7    while i < 256 {
8        let mut crc = i as u16;
9        let mut bit = 0;
10        while bit < 8 {
11            if (crc & 0x0001) != 0 {
12                crc = (crc >> 1) ^ 0xA001;
13            } else {
14                crc >>= 1;
15            }
16            bit += 1;
17        }
18        table[i] = crc;
19        i += 1;
20    }
21    table
22}
23
24const CRC16_TABLE: [u16; 256] = build_crc16_table();
25
26pub fn crc16(data: &[u8]) -> u16 {
27    let mut crc = 0xFFFFu16;
28    for byte in data {
29        let idx = ((crc ^ (*byte as u16)) & 0x00FF) as usize;
30        crc = (crc >> 8) ^ CRC16_TABLE[idx];
31    }
32    crc
33}
34
35pub fn encode_frame(w: &mut Writer<'_>, address: u8, pdu: &[u8]) -> Result<(), EncodeError> {
36    if pdu.is_empty() {
37        return Err(EncodeError::InvalidLength);
38    }
39
40    w.write_u8(address)?;
41    w.write_all(pdu)?;
42
43    let mut tmp = [0u8; 254];
44    if pdu.len() > 253 {
45        return Err(EncodeError::ValueOutOfRange);
46    }
47    tmp[0] = address;
48    tmp[1..1 + pdu.len()].copy_from_slice(pdu);
49    let crc = crc16(&tmp[..1 + pdu.len()]);
50    w.write_all(&crc.to_le_bytes())?;
51    Ok(())
52}
53
54pub fn decode_frame(data: &[u8]) -> Result<(u8, &[u8]), DecodeError> {
55    if data.len() < 4 {
56        return Err(DecodeError::InvalidLength);
57    }
58
59    let payload = &data[..data.len() - 2];
60    let expected = crc16(payload);
61    let got = u16::from_le_bytes([data[data.len() - 2], data[data.len() - 1]]);
62    if expected != got {
63        return Err(DecodeError::InvalidCrc);
64    }
65
66    let address = payload[0];
67    let pdu = &payload[1..];
68    if pdu.is_empty() {
69        return Err(DecodeError::InvalidLength);
70    }
71    Ok((address, pdu))
72}
73
74#[cfg(test)]
75mod tests {
76    use super::{crc16, decode_frame, encode_frame};
77    use crate::encoding::Writer;
78    use crate::DecodeError;
79
80    #[test]
81    fn crc16_known_vector() {
82        let frame_wo_crc = [0x01u8, 0x03, 0x00, 0x00, 0x00, 0x0A];
83        assert_eq!(crc16(&frame_wo_crc), 0xCDC5);
84    }
85
86    #[test]
87    fn rtu_roundtrip() {
88        let mut buf = [0u8; 32];
89        let mut w = Writer::new(&mut buf);
90        encode_frame(&mut w, 0x11, &[0x03, 0x00, 0x6B, 0x00, 0x03]).unwrap();
91        let written = w.as_written();
92
93        let (address, pdu) = decode_frame(written).unwrap();
94        assert_eq!(address, 0x11);
95        assert_eq!(pdu, &[0x03, 0x00, 0x6B, 0x00, 0x03]);
96    }
97
98    #[test]
99    fn detects_bad_crc() {
100        let bad = [0x11u8, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x00, 0x00];
101        assert_eq!(decode_frame(&bad).unwrap_err(), DecodeError::InvalidCrc);
102    }
103}