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}