ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
edge_mesh.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file edge_mesh.h
6 * @brief CDN edge-cache tier - mesh (sibling-cache) wire codec + async peer-query engine
7 * (PROTOCORE_ENABLE_EDGE_MESH).
8 *
9 * Lets a fleet of edge nodes share one warm cache. On a full local miss a node queries its sibling peers
10 * (over a plaintext ProtoConn::PROTO_MESH TCP link) with a content-addressed request and pulls a fresh copy
11 * from whichever peer has it, instead of re-fetching the origin. Pull (read-through) only: no push, no
12 * invalidation. The transfer carries the object plus its freshness/age, so a sibling-fresh object serves for
13 * its remaining lifetime with zero origin contact (RFC 9111 age propagation).
14 *
15 * This file is the pure, host-testable half: the request/response frame codec, the freshness-carrying entry
16 * frame (the shared ::edge_sd_serialize body plus a fixed timing trailer), and the async requester engine
17 * over the same EdgeFetchTransport seam the origin fetch uses (protocore_client on device, a mock in host tests).
18 * The server glue (the peer table, the pre-origin query phase, and the PROTO_MESH serving listener) lives in
19 * edge_cache_proxy. Zero heap; fixed buffers.
20 *
21 * Wire format (little-endian, versioned; magic 'E','M'):
22 * request : 'E' 'M' | ver=1 | op=GET(1) | digest[32] | u16 key_len + key | u16 hdrs_len + req_hdrs
23 * response: 'E' 'M' | ver=1 | status (MISS=0 / HIT=1) | [ u16 entry_len + entry_frame ] (entry on HIT)
24 * entry : timing trailer (i64 date | i64 expires | u32 lifetime_s | u32 age_hdr | u32 current_age)
25 * followed by an ::edge_sd_serialize body (content: key/status/ct/validators/encoding/Vary/body)
26 *
27 * @author Douglas Quigg (dstroy0)
28 * @date 2026
29 */
30
31#ifndef PROTOCORE_EDGE_MESH_H
32#define PROTOCORE_EDGE_MESH_H
33
34#include "protocore_config.h" // the entry point: protocore_types.h for the widths
35
36#if PROTOCORE_ENABLE_EDGE_MESH
37
39
40// This module holds nothing between calls, so it carves no borrow and states none. An entry
41// takes one all the same, and never reads it, so every namespace in the tree is invoked the
42// same way.
43
44#define PROTOCORE_EDGE_MESH_MAGIC0 ('E')
45
46#define PROTOCORE_EDGE_MESH_MAGIC1 ('M')
47
48#define PROTOCORE_EDGE_MESH_VERSION 1
49
50#define PROTOCORE_EDGE_MESH_OP_GET 1 ///< the only request opcode: fetch by content address
51
52#define PROTOCORE_EDGE_MESH_REQ_MAX (2 + 1 + 1 + 32 + 2 + PROTOCORE_EDGE_KEY_MAX + 2 + PROTOCORE_MESH_HDRS_MAX)
53
54/** @brief Tri-state parse result for the length-delimited frames (partial reads accumulate to complete). */
55typedef enum PROTO_ENUM_PACKED
56{
57 EDGE_MESH_PARSE_MALFORMED = -1, ///< bad magic/version/opcode, or a field that cannot fit the destination
58 EDGE_MESH_PARSE_INCOMPLETE = 0, ///< a valid prefix so far - need more bytes
59 EDGE_MESH_PARSE_MISS = 1, ///< a complete response with no object
60 EDGE_MESH_PARSE_HIT = 2, ///< a complete request (outputs filled) / a complete response carrying an entry
61} EdgeMeshParse;
62
63/** @brief Peer-query progress. */
64typedef enum PROTO_ENUM_PACKED
65{
66 EDGE_MESH_STATUS_PENDING, ///< still connecting / receiving
67 EDGE_MESH_STATUS_HIT, ///< a complete entry frame arrived (entry_off / entry_len valid)
68 EDGE_MESH_STATUS_MISS, ///< the peer does not have (a fresh copy of) the object
69 EDGE_MESH_STATUS_FAILED, ///< connect / send / timeout / closed-before-complete / malformed
70} EdgeMeshStatus;
71
72/**
73 * @brief One in-flight peer query (zero-heap). The response accumulates into a caller-owned @c buf (>=
74 * PROTOCORE_EDGE_MESH_RESP_MAX) supplied at begin - a fetch slot reuses its origin buffer, since the mesh and
75 * origin phases never run at once.
76 */
77typedef struct
78{
79 EdgeMeshStatus st;
80 int cid;
81 uint32_t start_ms;
82 size_t got; ///< response bytes accumulated
83 size_t entry_off; ///< offset of the entry frame within buf (valid on HIT)
84 size_t entry_len; ///< length of the entry frame (valid on HIT)
85 uint8_t *buf; ///< caller-owned accumulation buffer
86 size_t cap; ///< its capacity (must be >= PROTOCORE_EDGE_MESH_RESP_MAX)
87} EdgeMeshFetch;
88
89/** @brief EdgeEntry, as the caller already knows it. */
90struct EdgeEntry;
91
92/** @brief EdgeFetchTransport, as the caller already knows it. */
94
95/** @brief What build_request takes: digest, canon, req_hdrs, out, cap. */
96typedef struct
97{
98 const uint8_t *digest; ///< 32 bytes.
99 const char *canon;
100 const char
101 *req_hdrs; ///< the requester's header snapshot (name RS value US ...) so the peer can match Vary variants; ...
102 uint8_t *out;
103 size_t cap;
104} EdgeMeshBuildRequestArgs;
105
106/** @brief What parse_request takes: buf, len, digest_out, canon_out, ... */
107typedef struct
108{
109 const uint8_t *buf;
110 size_t len;
111 uint8_t *digest_out; ///< 32 bytes.
112 char *canon_out;
113 size_t canon_cap;
114 char *hdrs_out;
115 size_t hdrs_cap;
116} EdgeMeshParseRequestArgs;
117
118/** @brief What serialize_entry takes: e, current_age, out, cap. */
119typedef struct
120{
121 const struct EdgeEntry *e;
122 long current_age; ///< the sender's corrected age for e at send time (RFC 9111 sec 4.2.3), clamped >= 0
123 uint8_t *out;
124 size_t cap;
125} EdgeMeshSerializeEntryArgs;
126
127/** @brief What deserialize_entry takes: entry_buf, buf, len, e, now_ms. */
128typedef struct
129{
130 uint8_t *entry_buf;
131 const uint8_t *buf;
132 size_t len;
133 struct EdgeEntry *e;
134 uint32_t now_ms;
135} EdgeMeshDeserializeEntryArgs;
136
137/** @brief What build_response takes: hit, entry, entry_len, out, cap. */
138typedef struct
139{
140 proto_bool hit;
141 const uint8_t *entry;
142 size_t entry_len;
143 uint8_t *out;
144 size_t cap;
145} EdgeMeshBuildResponseArgs;
146
147/** @brief What parse_response takes: buf, len, entry_off, entry_len. */
148typedef struct
149{
150 const uint8_t *buf;
151 size_t len;
152 size_t *entry_off;
153 size_t *entry_len;
154} EdgeMeshParseResponseArgs;
155
156/** @brief What fetch_begin takes: m, t, host, port, request, req_len, ... */
157typedef struct
158{
159 EdgeMeshFetch *m;
160 const struct EdgeFetchTransport *t;
161 const char *host;
162 uint16_t port;
163 const uint8_t *request;
164 size_t req_len;
165 uint8_t *buf;
166 size_t cap;
167 uint32_t now_ms;
168} EdgeMeshFetchBeginArgs;
169
170/** @brief What fetch_pump takes: m, t, now_ms. */
171typedef struct
172{
173 EdgeMeshFetch *m;
174 const struct EdgeFetchTransport *t;
175 uint32_t now_ms;
176} EdgeMeshFetchPumpArgs;
177
178/** @brief What fetch_end takes: m, t. */
179typedef struct
180{
181 EdgeMeshFetch *m;
182 const struct EdgeFetchTransport *t;
183} EdgeMeshFetchEndArgs;
184
185/**
186 * @brief CDN edge-cache tier - mesh (sibling-cache) wire codec + async peer-query engine (PROTOCORE_ENABLE_EDGE_MESH).
187 * ...
188 *
189 * A caller sets the members a call takes, invokes it through ::EdgeMesh with the bytes it runs
190 * out of, and reads the outcome off the same handle.
191 *
192 * EdgeMesh.build_request_args.digest = ...;
193 * EdgeMesh.build_request_args.canon = ...;
194 * EdgeMesh.build_request_args.req_hdrs = ...;
195 * EdgeMesh.build_request_args.out = ...;
196 * EdgeMesh.build_request_args.cap = ...;
197 * EdgeMesh.build_request(work);
198 * // EdgeMesh.n is what the call reports
199 *
200 * @var EdgeMeshNs::build_request_args what build_request takes: digest, canon, req_hdrs, out, cap
201 * @var EdgeMeshNs::parse_request_args what parse_request takes: buf, len, digest_out, canon_out,
202 * @var EdgeMeshNs::serialize_entry_args what serialize_entry takes: e, current_age, out, cap
203 * @var EdgeMeshNs::deserialize_entry_args what deserialize_entry takes: entry_buf, buf, len, e, now_ms
204 * @var EdgeMeshNs::build_response_args what build_response takes: hit, entry, entry_len, out, cap
205 * @var EdgeMeshNs::parse_response_args what parse_response takes: buf, len, entry_off, entry_len
206 * @var EdgeMeshNs::fetch_begin_args what fetch_begin takes: m, t, host, port, request, req_len,
207 * @var EdgeMeshNs::fetch_pump_args what fetch_pump takes: m, t, now_ms
208 * @var EdgeMeshNs::fetch_end_args what fetch_end takes: m, t
209 * @var EdgeMeshNs::ok a call's true/false outcome
210 * @var EdgeMeshNs::n the entry-frame length, or 0 if it would not fit cap
211 * @var EdgeMeshNs::parse EDGE_MESH_PARSE_HIT with digest_out / canon_out / hdrs_out filled ...
212 * @var EdgeMeshNs::status what a call reports
213 * @var EdgeMeshNs::build_request build a GET request for digest / canon into out
214 * @var EdgeMeshNs::parse_request parse an accumulated request buffer
215 * @var EdgeMeshNs::serialize_entry serialize e (content via the shared ::edge_sd_serialize) plus a ...
216 * @var EdgeMeshNs::deserialize_entry rehydrate e from an entry frame: content via ::edge_sd_deserialize, ...
217 * @var EdgeMeshNs::build_response build a response (hit -> carry entry / entry_len; else a MISS). ...
218 * @var EdgeMeshNs::parse_response parse an accumulated response buffer
219 * @var EdgeMeshNs::fetch_begin dial host:port, send request, begin receiving into buf (cap >= ...
220 * @var EdgeMeshNs::fetch_pump drain available bytes and advance; honors PROTOCORE_MESH_QUERY_MS. ...
221 * @var EdgeMeshNs::fetch_end release the peer connection (idempotent)
222 *
223 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
224 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
225 * a caller drives every namespace the same way.
226 */
227typedef struct
228{
229 EdgeMeshBuildRequestArgs build_request_args;
230 EdgeMeshParseRequestArgs parse_request_args;
231 EdgeMeshSerializeEntryArgs serialize_entry_args;
232 EdgeMeshDeserializeEntryArgs deserialize_entry_args;
233 EdgeMeshBuildResponseArgs build_response_args;
234 EdgeMeshParseResponseArgs parse_response_args;
235 EdgeMeshFetchBeginArgs fetch_begin_args;
236 EdgeMeshFetchPumpArgs fetch_pump_args;
237 EdgeMeshFetchEndArgs fetch_end_args;
238 proto_bool ok;
239 size_t n;
240 EdgeMeshParse parse;
241 EdgeMeshStatus status;
242} EdgeMeshVars;
243
244/** @brief The operands and the outcome. */
245extern EdgeMeshVars EdgeMeshV;
246
247/** @brief The entries. */
248typedef struct
249{
250 void (*const build_request)(uint8_t *work);
251 void (*const parse_request)(uint8_t *work);
252 void (*const serialize_entry)(uint8_t *work);
253 void (*const deserialize_entry)(uint8_t *work);
254 void (*const build_response)(uint8_t *work);
255 void (*const parse_response)(uint8_t *work);
256 void (*const fetch_begin)(uint8_t *work);
257 void (*const fetch_pump)(uint8_t *work);
258 void (*const fetch_end)(uint8_t *work);
259} EdgeMeshNs;
260
261// What the table binds, defined once in the .c and taking one parameter each: everything
262// else an entry needs is an operand in EdgeMeshV or a region of the borrow at a fixed offset.
263void protocore_edge_mesh_build_request(uint8_t *work);
264void protocore_edge_mesh_parse_request(uint8_t *work);
265void protocore_edge_mesh_serialize_entry(uint8_t *work);
266void protocore_edge_mesh_deserialize_entry(uint8_t *work);
267void protocore_edge_mesh_build_response(uint8_t *work);
268void protocore_edge_mesh_parse_response(uint8_t *work);
269void protocore_edge_mesh_fetch_begin(uint8_t *work);
270void protocore_edge_mesh_fetch_pump(uint8_t *work);
271void protocore_edge_mesh_fetch_end(uint8_t *work);
272
273// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
274// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
275// `EdgeMesh.build_request(work)` resolves to a named function and becomes a DIRECT call. An extern table
276// leaves the call indirect and the symbol live at every level, -O2 -flto included.
277static const EdgeMeshNs EdgeMesh __attribute__((unused)) = {
278 .build_request = protocore_edge_mesh_build_request,
279 .parse_request = protocore_edge_mesh_parse_request,
280 .serialize_entry = protocore_edge_mesh_serialize_entry,
281 .deserialize_entry = protocore_edge_mesh_deserialize_entry,
282 .build_response = protocore_edge_mesh_build_response,
283 .parse_response = protocore_edge_mesh_parse_response,
284 .fetch_begin = protocore_edge_mesh_fetch_begin,
285 .fetch_pump = protocore_edge_mesh_fetch_pump,
286 .fetch_end = protocore_edge_mesh_fetch_end,
287};
288
290
291#endif // PROTOCORE_ENABLE_EDGE_MESH
292
293#endif // PROTOCORE_EDGE_MESH_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
The origin transport, bound to protocore_client on the device and a mock in host tests.
Definition edge_fetch.h:35
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97