DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
zigbee.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file zigbee.cpp
6 * @brief Zigbee EZSP / ASH framing codec - implementation.
7 *
8 * ASH (UG101): [control | payload | CRC16] byte-stuffed and Flag-terminated. The CRC is
9 * CRC-16/CCITT (poly 0x1021, init 0xFFFF); the reserved bytes 0x7E / 0x7D / 0x11 / 0x13 /
10 * 0x18 / 0x1A are escaped as 0x7D, (byte XOR 0x20).
11 */
12
14
15#if DETWS_ENABLE_ZIGBEE
16
17namespace
18{
19bool is_reserved(uint8_t b)
20{
21 return b == 0x7E || b == 0x7D || b == 0x11 || b == 0x13 || b == 0x18 || b == 0x1A;
22}
23
24uint16_t crc16_byte(uint16_t crc, uint8_t b)
25{
26 crc ^= (uint16_t)b << 8;
27 for (uint8_t i = 0; i < 8; i++)
28 crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1);
29 return crc;
30}
31
32// Append a byte to out with ASH stuffing; return false if it would overflow cap.
33bool put_stuffed(uint8_t *out, uint16_t *p, uint16_t cap, uint8_t b)
34{
35 if (is_reserved(b))
36 {
37 if (*p + 2 > cap)
38 return false;
39 out[(*p)++] = Ash::ASH_ESCAPE;
40 out[(*p)++] = (uint8_t)(b ^ 0x20);
41 }
42 else
43 {
44 if (*p + 1 > cap)
45 return false;
46 out[(*p)++] = b;
47 }
48 return true;
49}
50} // namespace
51
52uint16_t ash_crc16(const uint8_t *buf, uint16_t len)
53{
54 uint16_t crc = 0xFFFF;
55 for (uint16_t i = 0; i < len; i++)
56 crc = crc16_byte(crc, buf[i]);
57 return crc;
58}
59
60uint16_t ash_frame_encode(uint8_t control, const uint8_t *payload, uint16_t len, uint8_t *out, uint16_t cap)
61{
62 if (!out || len > DETWS_ZIGBEE_MAX_DATA || (payload == nullptr && len > 0))
63 return 0;
64 // CRC over control + payload.
65 uint16_t crc = crc16_byte(0xFFFF, control);
66 for (uint16_t i = 0; i < len; i++)
67 crc = crc16_byte(crc, payload[i]);
68
69 uint16_t p = 0;
70 if (!put_stuffed(out, &p, cap, control))
71 return 0;
72 for (uint16_t i = 0; i < len; i++)
73 if (!put_stuffed(out, &p, cap, payload[i]))
74 return 0;
75 if (!put_stuffed(out, &p, cap, (uint8_t)(crc >> 8)) || !put_stuffed(out, &p, cap, (uint8_t)(crc & 0xFF)))
76 return 0;
77 if (p + 1 > cap)
78 return 0;
79 out[p++] = Ash::ASH_FLAG; // the delimiter is never stuffed
80 return p;
81}
82
83int ash_frame_decode(const uint8_t *raw, uint16_t len, uint8_t *control, uint8_t *payload, uint16_t pay_cap,
84 uint16_t *pay_len)
85{
86 if (!raw)
87 return 0;
88 // Find the frame delimiter.
89 uint16_t flag = 0;
90 while (flag < len && raw[flag] != Ash::ASH_FLAG)
91 flag++;
92 if (flag >= len)
93 return 0; // no complete frame yet
94
95 // Remove the byte-stuffing from raw[0, flag) into a fixed scratch: control + payload + CRC(2).
96 uint8_t un[DETWS_ZIGBEE_MAX_DATA + 3];
97 uint16_t n = 0;
98 for (uint16_t i = 0; i < flag; i++)
99 {
100 uint8_t b = raw[i];
101 if (b == Ash::ASH_ESCAPE)
102 {
103 if (++i >= flag)
104 return -1; // dangling escape
105 b = (uint8_t)(raw[i] ^ 0x20);
106 }
107 if (n >= sizeof(un))
108 return -1; // frame longer than we accept
109 un[n++] = b;
110 }
111 if (n < 3)
112 return -1; // need at least control + CRC(2)
113 uint16_t body = (uint16_t)(n - 2);
114 uint16_t crc = ash_crc16(un, body);
115 if ((uint16_t)((un[n - 2] << 8) | un[n - 1]) != crc)
116 return -1; // CRC mismatch
117
118 uint16_t plen = (uint16_t)(body - 1); // minus the control byte
119 if (plen > pay_cap)
120 return -1; // caller buffer too small
121 if (control)
122 *control = un[0];
123 for (uint16_t i = 0; i < plen; i++)
124 payload[i] = un[1 + i];
125 if (pay_len)
126 *pay_len = plen;
127 return (int)(flag + 1); // consume up to and including the flag
128}
129
130#endif // DETWS_ENABLE_ZIGBEE
#define DETWS_ZIGBEE_MAX_DATA
Max ASH payload bytes (an EZSP frame; the ASH data field caps near 128).
Zigbee EZSP / ASH framing codec (DETWS_ENABLE_ZIGBEE) - Silicon Labs NCP.