ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
edge_mesh.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 edge_mesh.cpp
6 * @brief CDN edge-cache tier - mesh sibling-cache wire codec + async peer-query engine. See edge_mesh.h.
7 */
8
10
11#if PC_ENABLE_EDGE_MESH
12
13#include <string.h>
14
15namespace
16{
17void put_u16(uint8_t *p, uint16_t v)
18{
19 p[0] = (uint8_t)(v & 0xFF);
20 p[1] = (uint8_t)(v >> 8);
21}
22uint16_t get_u16(const uint8_t *p)
23{
24 return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
25}
26void put_u32(uint8_t *p, uint32_t v)
27{
28 p[0] = (uint8_t)v;
29 p[1] = (uint8_t)(v >> 8);
30 p[2] = (uint8_t)(v >> 16);
31 p[3] = (uint8_t)(v >> 24);
32}
33uint32_t get_u32(const uint8_t *p)
34{
35 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
36}
37void put_i64(uint8_t *p, int64_t v)
38{
39 uint64_t u = (uint64_t)v;
40 for (int i = 0; i < 8; i++)
41 {
42 p[i] = (uint8_t)(u >> (8 * i));
43 }
44}
45int64_t get_i64(const uint8_t *p)
46{
47 uint64_t u = 0;
48 for (int i = 0; i < 8; i++)
49 {
50 u |= (uint64_t)p[i] << (8 * i);
51 }
52 return (int64_t)u;
53}
54
55// True (and stops) once buf[0..min(len,4)] diverges from the fixed request/response magic+version prefix.
56bool magic_bad(const uint8_t *buf, size_t len)
57{
58 if (len >= 1 && buf[0] != PC_EDGE_MESH_MAGIC0)
59 {
60 return true;
61 }
62 if (len >= 2 && buf[1] != PC_EDGE_MESH_MAGIC1)
63 {
64 return true;
65 }
66 if (len >= 3 && buf[2] != PC_EDGE_MESH_VERSION)
67 {
68 return true;
69 }
70 return false;
71}
72} // namespace
73
74// --- frame codec ---------------------------------------------------------------------------------
75
76size_t edge_mesh_build_request(const uint8_t digest[32], const char *canon, const char *req_hdrs, uint8_t *out,
77 size_t cap)
78{
79 if (!digest || !canon || !out)
80 {
81 return 0;
82 }
83 const char *hdrs = req_hdrs ? req_hdrs : "";
84 size_t kl = strnlen(canon, PC_EDGE_KEY_MAX);
85 size_t hl = strnlen(hdrs, PC_MESH_HDRS_MAX);
86 // Both lengths are strnlen-capped to PC_EDGE_KEY_MAX (128) and PC_MESH_HDRS_MAX, neither of
87 // which is within three orders of magnitude of the 16-bit wire limit, so neither arm can be
88 // taken in any build this library is sized for. The guard is what keeps the u16 length prefixes
89 // below honest if either cap is ever raised.
90 if (kl > 0xFFFFu || hl > 0xFFFFu) // GCOVR_EXCL_LINE - caps are far below 0xFFFF, see above
91 {
92 return 0; // GCOVR_EXCL_LINE - unreachable body of the guard above
93 }
94 size_t need = 2 + 1 + 1 + 32 + 2 + kl + 2 + hl;
95 if (need > cap)
96 {
97 return 0;
98 }
99 size_t pos = 0;
100 out[pos++] = PC_EDGE_MESH_MAGIC0;
101 out[pos++] = PC_EDGE_MESH_MAGIC1;
102 out[pos++] = PC_EDGE_MESH_VERSION;
103 out[pos++] = PC_EDGE_MESH_OP_GET;
104 memcpy(out + pos, digest, 32);
105 pos += 32;
106 put_u16(out + pos, (uint16_t)kl);
107 pos += 2;
108 memcpy(out + pos, canon, kl);
109 pos += kl;
110 put_u16(out + pos, (uint16_t)hl);
111 pos += 2;
112 memcpy(out + pos, hdrs, hl);
113 pos += hl;
114 return pos;
115}
116
117EdgeMeshParse edge_mesh_parse_request(const uint8_t *buf, size_t len, uint8_t digest_out[32], char *canon_out,
118 size_t canon_cap, char *hdrs_out, size_t hdrs_cap)
119{
120 if (magic_bad(buf, len))
121 {
122 return EdgeMeshParse::MALFORMED;
123 }
124 if (len < 4)
125 {
126 return EdgeMeshParse::INCOMPLETE;
127 }
128 if (buf[3] != PC_EDGE_MESH_OP_GET)
129 {
130 return EdgeMeshParse::MALFORMED;
131 }
132 size_t pos = 4;
133 if (pos + 32 > len)
134 {
135 return EdgeMeshParse::INCOMPLETE;
136 }
137 size_t digest_off = pos;
138 pos += 32;
139 if (pos + 2 > len)
140 {
141 return EdgeMeshParse::INCOMPLETE;
142 }
143 uint16_t kl = get_u16(buf + pos);
144 pos += 2;
145 if (kl >= canon_cap)
146 {
147 return EdgeMeshParse::MALFORMED; // cannot fit the destination key buffer
148 }
149 if (pos + kl > len)
150 {
151 return EdgeMeshParse::INCOMPLETE;
152 }
153 size_t key_off = pos;
154 pos += kl;
155 if (pos + 2 > len)
156 {
157 return EdgeMeshParse::INCOMPLETE;
158 }
159 uint16_t hl = get_u16(buf + pos);
160 pos += 2;
161 if (hl >= hdrs_cap)
162 {
163 return EdgeMeshParse::MALFORMED;
164 }
165 if (pos + hl > len)
166 {
167 return EdgeMeshParse::INCOMPLETE;
168 }
169 size_t hdrs_off = pos;
170 if (digest_out)
171 {
172 memcpy(digest_out, buf + digest_off, 32);
173 }
174 if (canon_out)
175 {
176 memcpy(canon_out, buf + key_off, kl);
177 canon_out[kl] = '\0';
178 }
179 if (hdrs_out)
180 {
181 memcpy(hdrs_out, buf + hdrs_off, hl);
182 hdrs_out[hl] = '\0';
183 }
184 return EdgeMeshParse::HIT; // a complete, valid request
185}
186
187size_t edge_mesh_serialize_entry(const EdgeEntry *e, long current_age, uint8_t *out, size_t cap)
188{
189 if (!e || !out || cap < PC_EDGE_MESH_TRAILER)
190 {
191 return 0;
192 }
193 if (current_age < 0)
194 {
195 current_age = 0;
196 }
197 put_i64(out + 0, e->date_epoch);
198 put_i64(out + 8, e->expires_epoch);
199 put_u32(out + 16, (uint32_t)(e->lifetime_s < 0 ? 0 : e->lifetime_s));
200 put_u32(out + 20, (uint32_t)(e->age_hdr < 0 ? 0 : e->age_hdr));
201 put_u32(out + 24, (uint32_t)current_age);
202 size_t n = edge_sd_serialize(e, out + PC_EDGE_MESH_TRAILER, cap - PC_EDGE_MESH_TRAILER);
203 if (n == 0)
204 {
205 return 0;
206 }
207 return PC_EDGE_MESH_TRAILER + n;
208}
209
210bool edge_mesh_deserialize_entry(const uint8_t *buf, size_t len, EdgeEntry *e, uint32_t now_ms)
211{
212 if (!buf || !e || len < PC_EDGE_MESH_TRAILER)
213 {
214 return false;
215 }
216 int64_t date = get_i64(buf + 0);
217 int64_t expires = get_i64(buf + 8);
218 uint32_t lifetime = get_u32(buf + 16);
219 uint32_t age_hdr = get_u32(buf + 20);
220 uint32_t current_age = get_u32(buf + 24);
221 if (!edge_sd_deserialize(buf + PC_EDGE_MESH_TRAILER, len - PC_EDGE_MESH_TRAILER, e))
222 {
223 return false;
224 }
225 e->date_epoch = date;
226 e->expires_epoch = expires;
227 e->lifetime_s = (long)lifetime;
228 e->age_hdr = (int32_t)age_hdr;
229 e->initial_age = (long)current_age; // the sender's age at transfer -> receiver keeps propagating it
230 e->insert_ms = now_ms;
231 e->last_used_ms = now_ms;
232 return true;
233}
234
235size_t edge_mesh_build_response(bool hit, const uint8_t *entry, size_t entry_len, uint8_t *out, size_t cap)
236{
237 if (!out || cap < 4)
238 {
239 return 0;
240 }
241 size_t pos = 0;
242 out[pos++] = PC_EDGE_MESH_MAGIC0;
243 out[pos++] = PC_EDGE_MESH_MAGIC1;
244 out[pos++] = PC_EDGE_MESH_VERSION;
245 out[pos++] = hit ? 1 : 0;
246 if (hit)
247 {
248 if (!entry || entry_len == 0 || entry_len > 0xFFFFu || pos + 2 + entry_len > cap)
249 {
250 return 0;
251 }
252 put_u16(out + pos, (uint16_t)entry_len);
253 pos += 2;
254 memcpy(out + pos, entry, entry_len);
255 pos += entry_len;
256 }
257 return pos;
258}
259
260EdgeMeshParse edge_mesh_parse_response(const uint8_t *buf, size_t len, size_t *entry_off, size_t *entry_len)
261{
262 if (magic_bad(buf, len))
263 {
264 return EdgeMeshParse::MALFORMED;
265 }
266 if (len < 4)
267 {
268 return EdgeMeshParse::INCOMPLETE;
269 }
270 uint8_t status = buf[3];
271 if (status == 0)
272 {
273 return EdgeMeshParse::MISS;
274 }
275 if (status != 1)
276 {
277 return EdgeMeshParse::MALFORMED;
278 }
279 if (len < 6)
280 {
281 return EdgeMeshParse::INCOMPLETE;
282 }
283 uint16_t el = get_u16(buf + 4);
284 if (el == 0)
285 {
286 return EdgeMeshParse::MALFORMED;
287 }
288 if (len < (size_t)6 + el)
289 {
290 return EdgeMeshParse::INCOMPLETE;
291 }
292 if (entry_off)
293 {
294 *entry_off = 6;
295 }
296 if (entry_len)
297 {
298 *entry_len = el;
299 }
300 return EdgeMeshParse::HIT;
301}
302
303// --- async requester engine ----------------------------------------------------------------------
304
305void edge_mesh_fetch_begin(EdgeMeshFetch *m, const EdgeFetchTransport *t, const char *host, uint16_t port,
306 const uint8_t *request, size_t req_len, uint8_t *buf, size_t cap, uint32_t now_ms)
307{
308 m->st = EdgeMeshStatus::PENDING;
309 m->cid = -1;
310 m->start_ms = now_ms;
311 m->got = 0;
312 m->entry_off = 0;
313 m->entry_len = 0;
314 m->buf = buf;
315 m->cap = cap;
316 if (!t || !host || !request || req_len == 0 || !buf || cap < PC_EDGE_MESH_RESP_MAX)
317 {
318 m->st = EdgeMeshStatus::FAILED;
319 return;
320 }
321 int cid = t->open(t->ctx, host, port, PC_MESH_QUERY_MS); // blocking connect (LAN sibling), bounded by the timeout
322 if (cid < 0)
323 {
324 m->st = EdgeMeshStatus::FAILED;
325 return;
326 }
327 m->cid = cid;
328 if (!t->send(t->ctx, cid, request, req_len))
329 {
330 t->close(t->ctx, cid);
331 m->cid = -1;
332 m->st = EdgeMeshStatus::FAILED;
333 }
334}
335
336EdgeMeshStatus edge_mesh_fetch_pump(EdgeMeshFetch *m, const EdgeFetchTransport *t, uint32_t now_ms)
337{
338 if (m->st != EdgeMeshStatus::PENDING)
339 {
340 return m->st;
341 }
342 if (!t || m->cid < 0)
343 {
344 m->st = EdgeMeshStatus::FAILED;
345 return m->st;
346 }
347 if (now_ms - m->start_ms > PC_MESH_QUERY_MS)
348 {
349 m->st = EdgeMeshStatus::FAILED; // query deadline
350 return m->st;
351 }
352
353 if (m->got < m->cap)
354 {
355 m->got += t->read(t->ctx, m->cid, m->buf + m->got, m->cap - m->got);
356 }
357
358 size_t eoff = 0;
359 size_t elen = 0;
360 EdgeMeshParse p = edge_mesh_parse_response(m->buf, m->got, &eoff, &elen);
361 if (p == EdgeMeshParse::HIT)
362 {
363 m->entry_off = eoff;
364 m->entry_len = elen;
365 m->st = EdgeMeshStatus::HIT;
366 }
367 else if (p == EdgeMeshParse::MISS)
368 {
369 m->st = EdgeMeshStatus::MISS;
370 }
371 else if (p == EdgeMeshParse::MALFORMED)
372 {
373 m->st = EdgeMeshStatus::FAILED;
374 }
375 else if (m->got >= m->cap || t->closed(t->ctx, m->cid))
376 {
377 m->st = EdgeMeshStatus::FAILED; // buffer full still short, or peer closed before a complete frame
378 }
379 return m->st;
380}
381
382void edge_mesh_fetch_end(EdgeMeshFetch *m, const EdgeFetchTransport *t)
383{
384 if (m->cid >= 0 && t)
385 {
386 t->close(t->ctx, m->cid);
387 }
388 m->cid = -1;
389}
390
391#endif // PC_ENABLE_EDGE_MESH
CDN edge-cache tier - mesh (sibling-cache) wire codec + async peer-query engine (PC_ENABLE_EDGE_MESH)...
#define PC_EDGE_MESH_TRAILER
Fixed timing trailer prepended to a mesh entry frame (age propagation).
#define PC_EDGE_MESH_RESP_MAX
Worst-case mesh response frame (header + entry on a HIT).
#define PC_EDGE_KEY_MAX
#define PC_MESH_HDRS_MAX
#define PC_MESH_QUERY_MS