ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
edge_cache_sd.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_cache_sd.h
6 * @brief CDN edge-cache tier - L2 SD persistence (PROTOCORE_ENABLE_EDGE_CACHE && PROTOCORE_ENABLE_DBM).
7 *
8 * The persistent second tier behind the bounded L1 RAM store (edge_cache): an evicted L1 entry is
9 * written back to a dbm key-value store on the WAL (services/storage/dbm, SD-card backed on device, a RAM WalDev
10 * for host tests), and an L1 miss is served by promoting the entry back from L2. Because the store is
11 * log-structured on the WAL, the cached set survives a reboot (dbm rebuilds its index by replaying the
12 * log on open).
13 *
14 * The L2 key is the entry's 32-byte SHA-256 digest (== PROTOCORE_DBM_KEY_MAX), so no key is re-derived. The
15 * value is a compact, versioned, little-endian serialization of the entry's response metadata + body.
16 *
17 * These are pure functions over a caller-owned dbm handle and a caller-owned scratch buffer (no
18 * file-scope state); the proxy glue (edge_cache_proxy) owns the dbm and the buffer and installs the
19 * write-back / promote wiring.
20 *
21 * Reboot note: the monotonic insert time is meaningless across a reboot (no wall clock), so a promoted
22 * entry is always treated as stale by the caller and revalidated - which is why only entries carrying a
23 * validator (ETag / Last-Modified) are spilled: they are exactly the ones a cheap 304 can refresh.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_EDGE_CACHE_SD_H
30#define PROTOCORE_EDGE_CACHE_SD_H
31
32#include "protocore_config.h" // the entry point: protocore_types.h for the widths
33
34#if PROTOCORE_ENABLE_EDGE_CACHE
35
37
38// This module holds nothing between calls, so it carves no borrow and states none. An entry
39// takes one all the same, and never reads it, so every namespace in the tree is invoked the
40// same way.
41
42/** @brief EdgeEntry, as the caller already knows it. */
43struct EdgeEntry;
44
45/** @brief What serialize takes: e, out, cap. */
46typedef struct
47{
48 const struct EdgeEntry *e;
49 uint8_t *out;
50 size_t cap;
51} EdgeCacheSdSerializeArgs;
52
53/** @brief What deserialize takes: entry_buf, buf, len, e. */
54typedef struct
55{
56 uint8_t *entry_buf; ///< scratch the entry is decoded into
57 const uint8_t *buf;
58 size_t len;
59 struct EdgeEntry *e;
60} EdgeCacheSdDeserializeArgs;
61
62/** @brief What put takes: db, e, scratch, scratch_cap. */
63typedef struct
64{
65 struct protocore_dbm *db;
66 const struct EdgeEntry *e;
67 uint8_t *scratch;
68 size_t scratch_cap;
69} EdgeCacheSdPutArgs;
70
71/** @brief What get takes: entry_buf, db, digest, e, scratch, scratch_cap. */
72typedef struct
73{
74 uint8_t *entry_buf; ///< scratch the entry is decoded into
75 struct protocore_dbm *db;
76 const uint8_t *digest; ///< 32 bytes.
77 struct EdgeEntry *e;
78 uint8_t *scratch;
79 size_t scratch_cap;
80} EdgeCacheSdGetArgs;
81
82/** @brief What del takes: db, digest. */
83typedef struct
84{
85 struct protocore_dbm *db;
86 const uint8_t *digest; ///< 32 bytes.
87} EdgeCacheSdDelArgs;
88
89/** @brief What purge_prefix takes: db, path_prefix, scratch, ... */
90typedef struct
91{
92 struct protocore_dbm *db;
93 const char *path_prefix;
94 uint8_t *scratch;
95 size_t scratch_cap;
96} EdgeCacheSdPurgePrefixArgs;
97
98/** @brief What purge_all takes: db. */
99typedef struct
100{
101 struct protocore_dbm *db;
102} EdgeCacheSdPurgeAllArgs;
103
104/**
105 * @brief CDN edge-cache tier - L2 SD persistence (PROTOCORE_ENABLE_EDGE_CACHE && PROTOCORE_ENABLE_DBM). The persistent
106 * ...
107 *
108 * A caller sets the members a call takes, invokes it through ::EdgeCacheSd with the bytes it runs
109 * out of, and reads the outcome off the same handle.
110 *
111 * EdgeCacheSd.serialize_args.e = ...;
112 * EdgeCacheSd.serialize_args.out = ...;
113 * EdgeCacheSd.serialize_args.cap = ...;
114 * EdgeCacheSd.serialize(work);
115 * // EdgeCacheSd.n is what the call reports
116 *
117 * @var EdgeCacheSdNs::serialize_args what serialize takes: e, out, cap
118 * @var EdgeCacheSdNs::deserialize_args what deserialize takes: entry_buf, buf, len, e
119 * @var EdgeCacheSdNs::put_args what put takes: db, e, scratch, scratch_cap
120 * @var EdgeCacheSdNs::get_args what get takes: entry_buf, db, digest, e, scratch, scratch_cap
121 * @var EdgeCacheSdNs::del_args what del takes: db, digest
122 * @var EdgeCacheSdNs::purge_prefix_args what purge_prefix takes: db, path_prefix, scratch,
123 * @var EdgeCacheSdNs::purge_all_args what purge_all takes: db
124 * @var EdgeCacheSdNs::ok false on a short/corrupt/oversized buffer or a version mismatch ...
125 * @var EdgeCacheSdNs::n the byte length written, or 0 if it would not fit cap. ...
126 * @var EdgeCacheSdNs::count what a call reports
127 * @var EdgeCacheSdNs::serialize serialize e's response metadata + body into out (little-endian, ...
128 * @var EdgeCacheSdNs::deserialize rehydrate an entry from buf (as produced by ::edge_sd_serialize) ...
129 * @var EdgeCacheSdNs::put write e back to the L2 store (keyed by its digest), using scratch ...
130 * @var EdgeCacheSdNs::get promote the entry stored under digest from L2 into e (via scratch)
131 * @var EdgeCacheSdNs::del drop the L2 entry stored under digest. true if one existed
132 * @var EdgeCacheSdNs::purge_prefix drop every L2 entry whose stored request path begins with prefix ...
133 * @var EdgeCacheSdNs::purge_all drop every L2 entry. the number purged
134 *
135 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
136 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
137 * a caller drives every namespace the same way.
138 */
139typedef struct
140{
141 EdgeCacheSdSerializeArgs serialize_args;
142 EdgeCacheSdDeserializeArgs deserialize_args;
143 EdgeCacheSdPutArgs put_args;
144 EdgeCacheSdGetArgs get_args;
145 EdgeCacheSdDelArgs del_args;
146 EdgeCacheSdPurgePrefixArgs purge_prefix_args;
147 EdgeCacheSdPurgeAllArgs purge_all_args;
148 proto_bool ok;
149 size_t n;
150 uint32_t count;
151 // The store operations are the only part that needs a key/value database behind it, so they
152 // are the only part the flag gates. The codec above is pure and is always here.
153#if PROTOCORE_ENABLE_DBM
154#endif
155} EdgeCacheSdVars;
156
157/** @brief The operands and the outcome. */
158extern EdgeCacheSdVars EdgeCacheSdV;
159
160/** @brief The entries. */
161typedef struct
162{
163 void (*const serialize)(uint8_t *work);
164 void (*const deserialize)(uint8_t *work);
165 void (*const put)(uint8_t *work);
166 void (*const get)(uint8_t *work);
167 void (*const del)(uint8_t *work);
168 void (*const purge_prefix)(uint8_t *work);
169 void (*const purge_all)(uint8_t *work);
170} EdgeCacheSdNs;
171
172// What the table binds, defined once in the .c and taking one parameter each: everything
173// else an entry needs is an operand in EdgeCacheSdV or a region of the borrow at a fixed offset.
174void protocore_edge_cache_sd_serialize(uint8_t *work);
175void protocore_edge_cache_sd_deserialize(uint8_t *work);
176#if PROTOCORE_ENABLE_DBM
177void protocore_edge_cache_sd_put(uint8_t *work);
178void protocore_edge_cache_sd_get(uint8_t *work);
179void protocore_edge_cache_sd_del(uint8_t *work);
180void protocore_edge_cache_sd_purge_prefix(uint8_t *work);
181void protocore_edge_cache_sd_purge_all(uint8_t *work);
182#endif
183
184// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
185// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
186// `EdgeCacheSd.serialize(work)` resolves to a named function and becomes a DIRECT call. An extern table
187// leaves the call indirect and the symbol live at every level, -O2 -flto included.
188static const EdgeCacheSdNs EdgeCacheSd __attribute__((unused)) = {
189 .serialize = protocore_edge_cache_sd_serialize,
190 .deserialize = protocore_edge_cache_sd_deserialize,
191#if PROTOCORE_ENABLE_DBM
192 .put = protocore_edge_cache_sd_put,
193 .get = protocore_edge_cache_sd_get,
194 .del = protocore_edge_cache_sd_del,
195 .purge_prefix = protocore_edge_cache_sd_purge_prefix,
196 .purge_all = protocore_edge_cache_sd_purge_all,
197#endif
198};
199
201
202#endif // PROTOCORE_ENABLE_EDGE_CACHE
203
204#endif // PROTOCORE_EDGE_CACHE_SD_H
#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