DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
thread.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 thread.cpp
6 * @brief Thread spinel / HDLC-lite framing codec - implementation.
7 *
8 * HDLC-lite: [payload | FCS(lo,hi)] byte-stuffed and Flag-terminated. The FCS is CRC-16/X-25
9 * (poly 0x1021 reflected = 0x8408, init 0xFFFF, reflected, final XOR 0xFFFF); the reserved
10 * bytes 0x7E / 0x7D / 0x11 / 0x13 are escaped as 0x7D, (byte XOR 0x20).
11 */
12
14
15#if DETWS_ENABLE_THREAD
16
17namespace
18{
19bool is_reserved(uint8_t b)
20{
21 return b == 0x7E || b == 0x7D || b == 0x11 || b == 0x13;
22}
23
24// Append a byte with HDLC stuffing; return false if it would overflow cap.
25bool put_stuffed(uint8_t *out, uint16_t *p, uint16_t cap, uint8_t b)
26{
27 if (is_reserved(b))
28 {
29 if (*p + 2 > cap)
30 return false;
31 out[(*p)++] = ThreadHdlc::HDLC_ESCAPE;
32 out[(*p)++] = (uint8_t)(b ^ 0x20);
33 }
34 else
35 {
36 if (*p + 1 > cap)
37 return false;
38 out[(*p)++] = b;
39 }
40 return true;
41}
42} // namespace
43
44uint8_t spinel_pack_uint(uint32_t value, uint8_t *out, uint8_t cap)
45{
46 if (!out)
47 return 0;
48 uint8_t n = 0;
49 do
50 {
51 if (n >= cap)
52 return 0;
53 uint8_t byte = (uint8_t)(value & 0x7F);
54 value >>= 7;
55 if (value)
56 byte |= 0x80; // more bytes follow
57 out[n++] = byte;
58 } while (value);
59 return n;
60}
61
62int spinel_unpack_uint(const uint8_t *raw, uint8_t len, uint32_t *value)
63{
64 if (!raw)
65 return 0;
66 uint32_t v = 0;
67 uint8_t shift = 0;
68 for (uint8_t n = 0; n < len; n++)
69 {
70 uint8_t b = raw[n];
71 v |= (uint32_t)(b & 0x7F) << shift;
72 if (!(b & 0x80))
73 {
74 if (value)
75 *value = v;
76 return n + 1;
77 }
78 shift += 7;
79 if (shift >= 32)
80 return -1; // does not fit a uint32
81 }
82 return 0; // truncated - need more bytes
83}
84
85uint16_t spinel_command_build(uint8_t header, uint32_t cmd, uint32_t prop, const uint8_t *value, uint16_t value_len,
86 uint8_t *out, uint16_t cap)
87{
88 if (!out || cap < 1 || (value == nullptr && value_len > 0))
89 return 0;
90 uint16_t p = 0;
91 out[p++] = header;
92 uint8_t n = spinel_pack_uint(cmd, out + p, (uint8_t)(cap - p));
93 if (n == 0)
94 return 0;
95 p += n;
96 n = spinel_pack_uint(prop, out + p, (uint8_t)(cap > p ? cap - p : 0));
97 if (n == 0)
98 return 0;
99 p += n;
100 if ((uint32_t)p + value_len > cap)
101 return 0;
102 for (uint16_t i = 0; i < value_len; i++)
103 out[p + i] = value[i];
104 return (uint16_t)(p + value_len);
105}
106
107int spinel_command_parse(const uint8_t *payload, uint16_t len, uint8_t *header, uint32_t *cmd, uint32_t *prop,
108 const uint8_t **value, uint16_t *value_len)
109{
110 if (!payload || len < 1)
111 return -1;
112 uint16_t p = 0;
113 uint8_t h = payload[p++];
114 uint32_t c = 0;
115 uint32_t pr = 0;
116 int n = spinel_unpack_uint(payload + p, (uint8_t)((len - p) > 255 ? 255 : (len - p)), &c);
117 if (n <= 0)
118 return -1;
119 p += (uint16_t)n;
120 n = spinel_unpack_uint(payload + p, (uint8_t)((len - p) > 255 ? 255 : (len - p)), &pr);
121 if (n <= 0)
122 return -1;
123 p += (uint16_t)n;
124 if (header)
125 *header = h;
126 if (cmd)
127 *cmd = c;
128 if (prop)
129 *prop = pr;
130 if (value)
131 *value = payload + p;
132 if (value_len)
133 *value_len = (uint16_t)(len - p);
134 return (int)p;
135}
136
137uint16_t spinel_fcs(const uint8_t *buf, uint16_t len)
138{
139 uint16_t crc = 0xFFFF;
140 for (uint16_t i = 0; i < len; i++)
141 {
142 crc ^= buf[i];
143 for (uint8_t b = 0; b < 8; b++)
144 crc = (crc & 1) ? (uint16_t)((crc >> 1) ^ 0x8408) : (uint16_t)(crc >> 1);
145 }
146 return (uint16_t)(crc ^ 0xFFFF);
147}
148
149uint16_t spinel_frame_encode(const uint8_t *payload, uint16_t len, uint8_t *out, uint16_t cap)
150{
151 if (!out || len > DETWS_THREAD_MAX_DATA || (payload == nullptr && len > 0))
152 return 0;
153 uint16_t fcs = spinel_fcs(payload, len);
154 uint16_t p = 0;
155 for (uint16_t i = 0; i < len; i++)
156 if (!put_stuffed(out, &p, cap, payload[i]))
157 return 0;
158 if (!put_stuffed(out, &p, cap, (uint8_t)(fcs & 0xFF)) || // FCS low byte first
159 !put_stuffed(out, &p, cap, (uint8_t)(fcs >> 8)))
160 return 0;
161 if (p + 1 > cap)
162 return 0;
163 out[p++] = ThreadHdlc::HDLC_FLAG;
164 return p;
165}
166
167int spinel_frame_decode(const uint8_t *raw, uint16_t len, uint8_t *payload, uint16_t pay_cap, uint16_t *pay_len)
168{
169 if (!raw)
170 return 0;
171 uint16_t flag = 0;
172 while (flag < len && raw[flag] != ThreadHdlc::HDLC_FLAG)
173 flag++;
174 if (flag >= len)
175 return 0; // no complete frame yet
176
177 // Remove the byte-stuffing from raw[0, flag) into a scratch: payload + FCS(2).
178 uint8_t un[DETWS_THREAD_MAX_DATA + 2];
179 uint16_t n = 0;
180 for (uint16_t i = 0; i < flag; i++)
181 {
182 uint8_t b = raw[i];
183 if (b == ThreadHdlc::HDLC_ESCAPE)
184 {
185 if (++i >= flag)
186 return -1; // dangling escape
187 b = (uint8_t)(raw[i] ^ 0x20);
188 }
189 if (n >= sizeof(un))
190 return -1;
191 un[n++] = b;
192 }
193 if (n < 2)
194 return -1; // need at least the FCS
195 uint16_t plen = (uint16_t)(n - 2);
196 uint16_t fcs = spinel_fcs(un, plen);
197 if ((uint16_t)(un[plen] | (un[plen + 1] << 8)) != fcs)
198 return -1; // FCS mismatch (transmitted low byte first)
199 if (plen > pay_cap)
200 return -1;
201 for (uint16_t i = 0; i < plen; i++)
202 payload[i] = un[i];
203 if (pay_len)
204 *pay_len = plen;
205 return (int)(flag + 1);
206}
207
208#endif // DETWS_ENABLE_THREAD
#define DETWS_THREAD_MAX_DATA
Max spinel payload bytes carried in one HDLC-lite frame.
Thread spinel / HDLC-lite framing codec (DETWS_ENABLE_THREAD) - OpenThread RCP.