DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
edge_cache_sd.h
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_cache_sd.h
6 * @brief CDN edge-cache tier - L2 SD persistence (DETWS_ENABLE_EDGE_CACHE && DETWS_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/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 (== DETWS_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 DETERMINISTICESPASYNCWEBSERVER_EDGE_CACHE_SD_H
30#define DETERMINISTICESPASYNCWEBSERVER_EDGE_CACHE_SD_H
31
32#include "ServerConfig.h"
33
34#if DETWS_ENABLE_EDGE_CACHE && DETWS_ENABLE_DBM
35
36#include "services/dbm/dbm.h"
38#include <stddef.h>
39#include <stdint.h>
40
41/**
42 * @brief Worst-case serialized value size of one entry (every metadata string full + a max-size body).
43 *
44 * Size the scratch buffer with this. A serialized entry only fits in dbm when this (or the actual, often
45 * smaller, size) is <= DETWS_DBM_VAL_MAX - otherwise the entry stays L1-only (::edge_sd_put returns false).
46 */
47static constexpr size_t EDGE_SD_VALUE_MAX = 1 /*version*/ + 2 /*status*/ + 2 /*body_len*/ + 7u * 2u /*str lengths*/
48 + sizeof(EdgeEntry::key) + sizeof(EdgeEntry::content_type) +
49 sizeof(EdgeEntry::etag) + sizeof(EdgeEntry::last_modified) +
50 sizeof(EdgeEntry::content_encoding) + sizeof(EdgeEntry::vary_names) +
51 sizeof(EdgeEntry::vary_vals) + DETWS_EDGE_BODY_MAX;
52
53/**
54 * @brief Serialize @p e's response metadata + body into @p out (little-endian, versioned).
55 * @return the byte length written, or 0 if it would not fit @p cap. Freshness/age fields are intentionally
56 * not persisted (a promoted entry is force-revalidated, so they are always recomputed).
57 */
58size_t edge_sd_serialize(const EdgeEntry *e, uint8_t *out, size_t cap);
59
60/**
61 * @brief Rehydrate an entry from @p buf (as produced by ::edge_sd_serialize) into @p e.
62 *
63 * Fills only the content fields (key, digest, status, content-type, validators, encoding, Vary, body); the
64 * caller owns @p e's LRU linkage, used flag, and freshness (which it sets to force a revalidation).
65 * @return false on a short/corrupt/oversized buffer or a version mismatch (fails closed, no partial write
66 * of the body).
67 */
68bool edge_sd_deserialize(const uint8_t *buf, size_t len, EdgeEntry *e);
69
70/**
71 * @brief Write @p e back to the L2 store (keyed by its digest), using @p scratch to serialize.
72 * @return true if it was spilled; false if @p e carries no validator, or its serialization does not fit
73 * @p scratch / DETWS_DBM_VAL_MAX, or the dbm write fails (the entry simply stays L1-only).
74 */
75bool edge_sd_put(DetwsDbm *db, const EdgeEntry *e, uint8_t *scratch, size_t scratch_cap);
76
77/**
78 * @brief Promote the entry stored under @p digest from L2 into @p e (via @p scratch).
79 * @return true on a hit that deserialized cleanly; false on an L2 miss or a corrupt value.
80 */
81bool edge_sd_get(DetwsDbm *db, const uint8_t digest[32], EdgeEntry *e, uint8_t *scratch, size_t scratch_cap);
82
83/** @brief Drop the L2 entry stored under @p digest. @return true if one existed. */
84bool edge_sd_del(DetwsDbm *db, const uint8_t digest[32]);
85
86/**
87 * @brief Drop every L2 entry whose stored request path begins with @p prefix (via @p scratch to read each
88 * value's canonical key). @return the number purged.
89 */
90uint32_t edge_sd_purge_prefix(DetwsDbm *db, const char *path_prefix, uint8_t *scratch, size_t scratch_cap);
91
92/** @brief Drop every L2 entry. @return the number purged. */
93uint32_t edge_sd_purge_all(DetwsDbm *db);
94
95#endif // DETWS_ENABLE_EDGE_CACHE && DETWS_ENABLE_DBM
96
97#endif // DETERMINISTICESPASYNCWEBSERVER_EDGE_CACHE_SD_H
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_EDGE_BODY_MAX
Log-structured hash key-value store on the WAL (DETWS_ENABLE_DBM, requires DETWS_ENABLE_WAL).
CDN edge-cache tier - pure engine (DETWS_ENABLE_EDGE_CACHE).