Skip to main content

donglora_protocol/
crc.rs

1//! CRC-16/CCITT-FALSE — the wire-level integrity check used by DongLoRa Protocol.
2//!
3//! Polynomial `0x1021`, initial value `0xFFFF`, no reflection, XOR-out `0x0000`.
4//! This is the same variant also known as CRC-16/AUTOSAR or
5//! CRC-16/IBM-3740. It is **NOT** the same as CRC-16/KERMIT,
6//! CRC-16/XMODEM, or plain "CRC-16/CCITT" — those use different initial
7//! values or reflect their inputs.
8//!
9//! Check value: `crc16("123456789") == 0x29B1`. A module-level test pins
10//! this, and `tests/vectors.rs` anchors every Appendix C wire example.
11
12/// CRC-16/CCITT-FALSE of `data`.
13pub fn crc16(data: &[u8]) -> u16 {
14    let mut crc: u16 = 0xFFFF;
15    for &byte in data {
16        crc ^= (byte as u16) << 8;
17        for _ in 0..8 {
18            if crc & 0x8000 != 0 {
19                crc = (crc << 1) ^ 0x1021;
20            } else {
21                crc <<= 1;
22            }
23        }
24    }
25    crc
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn check_value_matches_spec() {
34        // PROTOCOL.md §2.2: CRC-16/CCITT-FALSE of ASCII "123456789" is 0x29B1.
35        assert_eq!(crc16(b"123456789"), 0x29B1);
36    }
37
38    #[test]
39    fn empty_input_is_initial_value() {
40        // No reflection, no XOR-out: empty input leaves the init value untouched.
41        assert_eq!(crc16(&[]), 0xFFFF);
42    }
43
44    #[test]
45    fn single_zero_byte() {
46        // 0xFFFF XOR 0x0000 = 0xFFFF, then 8 shift-and-maybe-xor rounds.
47        // Worked out by hand against the reference impl in Appendix B.
48        assert_eq!(crc16(&[0x00]), 0xE1F0);
49    }
50
51    #[test]
52    fn differs_from_kermit_variant() {
53        // CRC-16/KERMIT of "123456789" is 0x2189 — if we ever accidentally
54        // swap init or reflection, this vector catches it.
55        assert_ne!(crc16(b"123456789"), 0x2189);
56    }
57}