ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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#include "shared_primitives/strbuf.h" // pc_sb frame builder
11
12#if PC_ENABLE_WISUN
13
14#include <string.h>
15
16namespace
17{
18// Emit one CoAP option (RFC 7252 sec 3.1): the (delta,length) nibble header + extended bytes + value.
19bool emit_option(uint8_t *out, size_t *o, size_t cap, uint16_t delta, const uint8_t *val, uint16_t vlen)
20{
21 size_t start = *o;
22 if (start + 1 > cap)
23 {
24 return false;
25 }
26 uint8_t dn;
27 uint8_t ln;
28 uint8_t dext[2];
29 uint8_t lext[2];
30 int dexn = 0;
31 int lexn = 0;
32 if (delta < 13) // GCOVR_EXCL_BR_LINE false arm unreachable: see the delta note below
33 {
34 dn = (uint8_t)delta;
35 }
36 // GCOVR_EXCL_START delta is always 11 (first Uri-Path option) or 0 (later ones) from the sole caller
37 // pc_wisun_build_coap, so an option delta never reaches the 13/14 extended-encoding branches.
38 else if (delta < 269)
39 {
40 dn = 13;
41 dext[0] = (uint8_t)(delta - 13);
42 dexn = 1;
43 }
44 else
45 {
46 dn = 14;
47 uint16_t x = (uint16_t)(delta - 269);
48 dext[0] = (uint8_t)(x >> 8);
49 dext[1] = (uint8_t)x;
50 dexn = 2;
51 }
52 // GCOVR_EXCL_STOP
53 if (vlen < 13)
54 {
55 ln = (uint8_t)vlen;
56 }
57 else if (vlen < 269)
58 {
59 ln = 13;
60 lext[0] = (uint8_t)(vlen - 13);
61 lexn = 1;
62 }
63 else
64 {
65 ln = 14;
66 uint16_t x = (uint16_t)(vlen - 269);
67 lext[0] = (uint8_t)(x >> 8);
68 lext[1] = (uint8_t)x;
69 lexn = 2;
70 }
71 if (start + 1 + dexn + lexn + vlen > cap)
72 {
73 return false;
74 }
75 out[(*o)++] = (uint8_t)((dn << 4) | ln);
76 for (int i = 0; i < dexn; i++) // GCOVR_EXCL_BR_LINE dexn is always 0 (see the delta note above); loop never enters
77 {
78 out[(*o)++] = dext[i]; // GCOVR_EXCL_LINE dexn is always 0 (see the delta note above)
79 }
80 for (int i = 0; i < lexn; i++)
81 {
82 out[(*o)++] = lext[i];
83 }
84 for (uint16_t i = 0; i < vlen; i++)
85 {
86 out[(*o)++] = val[i];
87 }
88 return true;
89}
90} // namespace
91
92size_t pc_wisun_build_coap(uint8_t type, uint8_t code, uint16_t msg_id, const uint8_t *token, uint8_t tkl,
93 const char *uri_path, const uint8_t *payload, size_t plen, uint8_t *out, size_t cap)
94{
95 if (!out || tkl > 8 || (tkl && !token) || (plen && !payload))
96 {
97 return 0;
98 }
99 size_t o = 0;
100 if (cap < (size_t)(4 + tkl))
101 {
102 return 0;
103 }
104 out[o++] = (uint8_t)(0x40 | ((type & 0x03) << 4) | (tkl & 0x0F)); // version 1
105 out[o++] = code;
106 out[o++] = (uint8_t)(msg_id >> 8);
107 out[o++] = (uint8_t)msg_id;
108 for (uint8_t i = 0; i < tkl; i++)
109 {
110 out[o++] = token[i];
111 }
112
113 // Uri-Path (option 11), one option per '/'-separated segment; delta 11 then 0.
114 uint16_t last = 0;
115 const char *p = uri_path;
116 while (p && *p)
117 {
118 if (*p == '/')
119 {
120 p++;
121 continue;
122 }
123 const char *seg = p;
124 while (*p && *p != '/')
125 {
126 p++;
127 }
128 uint16_t seglen = (uint16_t)(p - seg);
129 uint16_t delta = (uint16_t)(11 - last);
130 if (!emit_option(out, &o, cap, delta, (const uint8_t *)seg, seglen))
131 {
132 return 0;
133 }
134 last = 11;
135 }
136
137 if (plen)
138 {
139 if (o + 1 + plen > cap)
140 {
141 return 0;
142 }
143 out[o++] = 0xFF; // payload marker
144 for (size_t i = 0; i < plen; i++)
145 {
146 out[o++] = payload[i];
147 }
148 }
149 return o;
150}
151
152void pc_wisun_init(WisunFan *fan, const pc_ip *border_router, WisunNode *storage, size_t cap)
153{
154 if (!fan)
155 {
156 return;
157 }
158 if (border_router)
159 {
160 fan->border_router = *border_router;
161 }
162 else
163 {
164 memset(&fan->border_router, 0, sizeof(fan->border_router));
165 }
166 fan->nodes = storage;
167 fan->cap = storage ? cap : 0;
168 fan->count = 0;
169}
170
171int pc_wisun_node_register(WisunFan *fan, const pc_ip *addr, uint32_t now)
172{
173 if (!fan || !fan->nodes || !addr)
174 {
175 return -1;
176 }
177 size_t idx = 0;
178 if (pc_wisun_node_find(fan, addr, &idx))
179 {
180 fan->nodes[idx].joined = true;
181 fan->nodes[idx].last_seen = now;
182 return (int)idx;
183 }
184 if (fan->count >= fan->cap)
185 {
186 return -1;
187 }
188 fan->nodes[fan->count].addr = *addr;
189 fan->nodes[fan->count].joined = true;
190 fan->nodes[fan->count].last_seen = now;
191 return (int)fan->count++;
192}
193
194bool pc_wisun_node_find(const WisunFan *fan, const pc_ip *addr, size_t *idx)
195{
196 if (!fan || !fan->nodes || !addr)
197 {
198 return false;
199 }
200 for (size_t i = 0; i < fan->count; i++)
201 {
202 if (pc_ip_equal(&fan->nodes[i].addr, addr))
203 {
204 if (idx)
205 {
206 *idx = i;
207 }
208 return true;
209 }
210 }
211 return false;
212}
213
214size_t pc_wisun_joined_count(const WisunFan *fan)
215{
216 if (!fan || !fan->nodes)
217 {
218 return 0;
219 }
220 size_t c = 0;
221 for (size_t i = 0; i < fan->count; i++)
222 {
223 if (fan->nodes[i].joined)
224 {
225 c++;
226 }
227 }
228 return c;
229}
230
231size_t pc_wisun_nodes_json(const WisunFan *fan, char *out, size_t cap)
232{
233 if (!fan || !out || cap == 0)
234 {
235 return 0;
236 }
237 pc_sb b = {out, cap, 0, true};
238 pc_sb_put(&b, "[");
239 for (size_t i = 0; i < fan->count; i++)
240 {
241 if (i)
242 {
243 pc_sb_put(&b, ",");
244 }
245 char astr[PC_IP_STR_MAX];
246 pc_ip_format(&fan->nodes[i].addr, astr, sizeof(astr));
247 pc_sb_put(&b, "{\"addr\":\"");
248 pc_sb_put(&b, astr);
249 pc_sb_put(&b, "\",\"joined\":");
250 pc_sb_put(&b, fan->nodes[i].joined ? "true" : "false");
251 pc_sb_put(&b, "}");
252 }
253 pc_sb_put(&b, "]");
254 if (!b.ok)
255 {
256 return 0;
257 }
258 out[b.len] = '\0';
259 return b.len;
260}
261
262#endif // PC_ENABLE_WISUN
bool pc_ip_equal(const pc_ip *a, const pc_ip *b)
True if a and b are the same family and address.
Definition ip.cpp:585
size_t pc_ip_format(const pc_ip *ip, char *out, size_t cap)
Format ip into out as its RFC 5952 canonical text.
Definition ip.cpp:488
#define PC_IP_STR_MAX
Longest text an pc_ip_format can produce, including the NUL (RFC 5952 v4-mapped).
Definition ip.h:58
Bounded no-heap string builder that fails closed on overflow (one shared copy).
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30
size_t len
Definition strbuf.h:33
bool ok
Definition strbuf.h:34
Wi-SUN FAN border-router connector (PC_ENABLE_WISUN).