rustmod_core/frame/
rtu.rs1use crate::encoding::Writer;
2use crate::{DecodeError, EncodeError, UnitId};
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 {
28 let mut crc = 0xFFFFu16;
29 for byte in data {
30 let idx = ((crc ^ (*byte as u16)) & 0x00FF) as usize;
31 crc = (crc >> 8) ^ CRC16_TABLE[idx];
32 }
33 crc
34}
35
36pub fn encode_frame(w: &mut Writer<'_>, address: UnitId, pdu: &[u8]) -> Result<(), EncodeError> {
38 if pdu.is_empty() {
39 return Err(EncodeError::InvalidLength);
40 }
41
42 let addr = address.as_u8();
43 w.write_u8(addr)?;
44 w.write_all(pdu)?;
45
46 let mut tmp = [0u8; 254];
47 if pdu.len() > 253 {
48 return Err(EncodeError::ValueOutOfRange);
49 }
50 tmp[0] = addr;
51 tmp[1..1 + pdu.len()].copy_from_slice(pdu);
52 let crc = crc16(&tmp[..1 + pdu.len()]);
53 w.write_all(&crc.to_le_bytes())?;
54 Ok(())
55}
56
57pub fn decode_frame(data: &[u8]) -> Result<(UnitId, &[u8]), DecodeError> {
59 if data.len() < 4 {
60 return Err(DecodeError::InvalidLength);
61 }
62
63 let payload = &data[..data.len() - 2];
64 let expected = crc16(payload);
65 let got = u16::from_le_bytes([data[data.len() - 2], data[data.len() - 1]]);
66 if expected != got {
67 return Err(DecodeError::InvalidCrc);
68 }
69
70 let address = UnitId::new(payload[0]);
71 let pdu = &payload[1..];
72 if pdu.is_empty() {
73 return Err(DecodeError::InvalidLength);
74 }
75 Ok((address, pdu))
76}
77
78#[cfg(test)]
79mod tests {
80 use super::{crc16, decode_frame, encode_frame};
81 use crate::encoding::Writer;
82 use crate::{DecodeError, UnitId};
83
84 #[test]
85 fn crc16_known_vector() {
86 let frame_wo_crc = [0x01u8, 0x03, 0x00, 0x00, 0x00, 0x0A];
87 assert_eq!(crc16(&frame_wo_crc), 0xCDC5);
88 }
89
90 #[test]
91 fn rtu_roundtrip() {
92 let mut buf = [0u8; 32];
93 let mut w = Writer::new(&mut buf);
94 encode_frame(&mut w, UnitId::new(0x11), &[0x03, 0x00, 0x6B, 0x00, 0x03]).unwrap();
95 let written = w.as_written();
96
97 let (address, pdu) = decode_frame(written).unwrap();
98 assert_eq!(address, UnitId::new(0x11));
99 assert_eq!(pdu, &[0x03, 0x00, 0x6B, 0x00, 0x03]);
100 }
101
102 #[test]
103 fn detects_bad_crc() {
104 let bad = [0x11u8, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x00, 0x00];
105 assert_eq!(decode_frame(&bad).unwrap_err(), DecodeError::InvalidCrc);
106 }
107}