DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
wisun.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 wisun.cpp
6 * @brief Wi-SUN FAN border-router connector (see wisun.h).
7 */
8
10
11#if DETWS_ENABLE_WISUN
12
13#include <string.h>
14
15namespace
16{
17// Emit one CoAP option (RFC 7252 sec 3.1): the (delta,length) nibble header + extended bytes + value.
18bool emit_option(uint8_t *out, size_t *o, size_t cap, uint16_t delta, const uint8_t *val, uint16_t vlen)
19{
20 size_t start = *o;
21 if (start + 1 > cap)
22 return false;
23 uint8_t dn;
24 uint8_t ln;
25 uint8_t dext[2];
26 uint8_t lext[2];
27 int dexn = 0;
28 int lexn = 0;
29 if (delta < 13)
30 dn = (uint8_t)delta;
31 // GCOVR_EXCL_START delta is always 11 (first Uri-Path option) or 0 (later ones) from the sole caller
32 // wisun_build_coap, so an option delta never reaches the 13/14 extended-encoding branches.
33 else if (delta < 269)
34 {
35 dn = 13;
36 dext[0] = (uint8_t)(delta - 13);
37 dexn = 1;
38 }
39 else
40 {
41 dn = 14;
42 uint16_t x = (uint16_t)(delta - 269);
43 dext[0] = (uint8_t)(x >> 8);
44 dext[1] = (uint8_t)x;
45 dexn = 2;
46 }
47 // GCOVR_EXCL_STOP
48 if (vlen < 13)
49 ln = (uint8_t)vlen;
50 else if (vlen < 269)
51 {
52 ln = 13;
53 lext[0] = (uint8_t)(vlen - 13);
54 lexn = 1;
55 }
56 else
57 {
58 ln = 14;
59 uint16_t x = (uint16_t)(vlen - 269);
60 lext[0] = (uint8_t)(x >> 8);
61 lext[1] = (uint8_t)x;
62 lexn = 2;
63 }
64 if (start + 1 + dexn + lexn + vlen > cap)
65 return false;
66 out[(*o)++] = (uint8_t)((dn << 4) | ln);
67 for (int i = 0; i < dexn; i++)
68 out[(*o)++] = dext[i]; // GCOVR_EXCL_LINE dexn is always 0 (see the delta note above)
69 for (int i = 0; i < lexn; i++)
70 out[(*o)++] = lext[i];
71 for (uint16_t i = 0; i < vlen; i++)
72 out[(*o)++] = val[i];
73 return true;
74}
75} // namespace
76
77size_t wisun_build_coap(uint8_t type, uint8_t code, uint16_t msg_id, const uint8_t *token, uint8_t tkl,
78 const char *uri_path, const uint8_t *payload, size_t plen, uint8_t *out, size_t cap)
79{
80 if (!out || tkl > 8 || (tkl && !token) || (plen && !payload))
81 return 0;
82 size_t o = 0;
83 if (cap < (size_t)(4 + tkl))
84 return 0;
85 out[o++] = (uint8_t)(0x40 | ((type & 0x03) << 4) | (tkl & 0x0F)); // version 1
86 out[o++] = code;
87 out[o++] = (uint8_t)(msg_id >> 8);
88 out[o++] = (uint8_t)msg_id;
89 for (uint8_t i = 0; i < tkl; i++)
90 out[o++] = token[i];
91
92 // Uri-Path (option 11), one option per '/'-separated segment; delta 11 then 0.
93 uint16_t last = 0;
94 const char *p = uri_path;
95 while (p && *p)
96 {
97 if (*p == '/')
98 {
99 p++;
100 continue;
101 }
102 const char *seg = p;
103 while (*p && *p != '/')
104 p++;
105 uint16_t seglen = (uint16_t)(p - seg);
106 uint16_t delta = (uint16_t)(11 - last);
107 if (!emit_option(out, &o, cap, delta, (const uint8_t *)seg, seglen))
108 return 0;
109 last = 11;
110 }
111
112 if (plen)
113 {
114 if (o + 1 + plen > cap)
115 return 0;
116 out[o++] = 0xFF; // payload marker
117 for (size_t i = 0; i < plen; i++)
118 out[o++] = payload[i];
119 }
120 return o;
121}
122
123void wisun_init(WisunFan *fan, const DetIp *border_router, WisunNode *storage, size_t cap)
124{
125 if (!fan)
126 return;
127 if (border_router)
128 fan->border_router = *border_router;
129 else
130 memset(&fan->border_router, 0, sizeof(fan->border_router));
131 fan->nodes = storage;
132 fan->cap = storage ? cap : 0;
133 fan->count = 0;
134}
135
136int wisun_node_register(WisunFan *fan, const DetIp *addr, uint32_t now)
137{
138 if (!fan || !fan->nodes || !addr)
139 return -1;
140 size_t idx = 0;
141 if (wisun_node_find(fan, addr, &idx))
142 {
143 fan->nodes[idx].joined = true;
144 fan->nodes[idx].last_seen = now;
145 return (int)idx;
146 }
147 if (fan->count >= fan->cap)
148 return -1;
149 fan->nodes[fan->count].addr = *addr;
150 fan->nodes[fan->count].joined = true;
151 fan->nodes[fan->count].last_seen = now;
152 return (int)fan->count++;
153}
154
155bool wisun_node_find(const WisunFan *fan, const DetIp *addr, size_t *idx)
156{
157 if (!fan || !fan->nodes || !addr)
158 return false;
159 for (size_t i = 0; i < fan->count; i++)
160 if (det_ip_equal(&fan->nodes[i].addr, addr))
161 {
162 if (idx)
163 *idx = i;
164 return true;
165 }
166 return false;
167}
168
169size_t wisun_joined_count(const WisunFan *fan)
170{
171 if (!fan || !fan->nodes)
172 return 0;
173 size_t c = 0;
174 for (size_t i = 0; i < fan->count; i++)
175 if (fan->nodes[i].joined)
176 c++;
177 return c;
178}
179
180namespace
181{
182struct Buf
183{
184 char *p;
185 size_t cap;
186 size_t len;
187 bool ok;
188};
189
190void put(Buf *b, const char *s)
191{
192 if (!b->ok)
193 return;
194 size_t sl = strnlen(s, b->cap + 1);
195 if (b->len + sl >= b->cap)
196 {
197 b->ok = false;
198 return;
199 }
200 memcpy(b->p + b->len, s, sl);
201 b->len += sl;
202}
203} // namespace
204
205size_t wisun_nodes_json(const WisunFan *fan, char *out, size_t cap)
206{
207 if (!fan || !out || cap == 0)
208 return 0;
209 Buf b = {out, cap, 0, true};
210 put(&b, "[");
211 for (size_t i = 0; i < fan->count; i++)
212 {
213 if (i)
214 put(&b, ",");
215 char astr[DET_IP_STR_MAX];
216 det_ip_format(&fan->nodes[i].addr, astr, sizeof(astr));
217 put(&b, "{\"addr\":\"");
218 put(&b, astr);
219 put(&b, "\",\"joined\":");
220 put(&b, fan->nodes[i].joined ? "true" : "false");
221 put(&b, "}");
222 }
223 put(&b, "]");
224 if (!b.ok)
225 return 0;
226 out[b.len] = '\0';
227 return b.len;
228}
229
230#endif // DETWS_ENABLE_WISUN
size_t det_ip_format(const DetIp *ip, char *out, size_t cap)
Format ip into out as its RFC 5952 canonical text.
Definition ip.cpp:388
bool det_ip_equal(const DetIp *a, const DetIp *b)
True if a and b are the same family and address.
Definition ip.cpp:459
#define DET_IP_STR_MAX
Longest text an det_ip_format can produce, including the NUL (RFC 5952 v4-mapped).
Definition ip.h:58
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52
Wi-SUN FAN border-router connector (DETWS_ENABLE_WISUN).