ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
httpcache.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 httpcache.h
6 * @brief HTTP `Cache-Control` directive builder + parser + freshness helper (RFC 9111),
7 * PROTOCORE_ENABLE_HTTP_CACHE.
8 *
9 * The origin-side of edge caching: first-class helpers to emit correct, edge-cacheable
10 * `Cache-Control` responses from app routes (so a device sitting behind a real CDN - or the
11 * library's own future cache tier - is cached correctly), a tolerant parser to read the
12 * directives on a request or an upstream response, and the RFC 9111 sec 4.2.1 freshness-lifetime
13 * calculation. Pure text - build the value with ::cache_control_build and hand it to
14 * set_cache_control(); no heap, no stdlib, host-testable.
15 *
16 * Directives: RFC 9111 (max-age, s-maxage, no-cache, no-store, no-transform, must-revalidate,
17 * proxy-revalidate, must-understand, private, public) plus the widely-used extensions
18 * `immutable` (RFC 8246) and `stale-while-revalidate` / `stale-if-error` (RFC 5861), and the
19 * request directives a server may want to read (only-if-cached, max-stale, min-fresh).
20 *
21 * This is the standards-mechanics layer only. The caching proxy/tier itself (RAM/SD storage,
22 * cache key, invalidation, mesh replication) is a separate, larger piece still being scoped.
23 *
24 * @author Douglas Quigg (dstroy0)
25 * @date 2026
26 */
27
28#ifndef PROTOCORE_HTTPCACHE_H
29#define PROTOCORE_HTTPCACHE_H
30
31#include "protocore_config.h" // the entry point: protocore_types.h for the widths
32
33#if PROTOCORE_ENABLE_HTTP_CACHE
34
36
37// This module holds nothing between calls, so it carves no borrow and states none. An entry
38// takes one all the same, and never reads it, so every namespace in the tree is invoked the
39// same way.
40
41/**
42 * @brief A `Cache-Control` directive set (a superset of request + response directives).
43 *
44 * Flags are presence; each delta-seconds value is -1 when the directive is absent. The optional
45 * field-name lists on `no-cache` / `private` are not captured (presence only). @ref max_stale
46 * uses -1 = absent and -2 = present with no value ("accept any staleness").
47 */
48typedef struct protocore_cache_control
49{
50 // response cacheability
51 proto_bool cc_public; ///< `public`
52 proto_bool cc_private; ///< `private` (field-name list not captured)
53 proto_bool no_store; ///< `no-store`
54 proto_bool no_cache; ///< `no-cache` (field-name list not captured)
55 proto_bool no_transform; ///< `no-transform`
56 proto_bool must_revalidate; ///< `must-revalidate`
57 proto_bool proxy_revalidate; ///< `proxy-revalidate`
58 proto_bool must_understand; ///< `must-understand`
59 proto_bool cc_immutable; ///< `immutable` (RFC 8246)
60 // request
61 proto_bool only_if_cached; ///< `only-if-cached` (request)
62 // delta-seconds (-1 = absent)
63 int32_t max_age; ///< `max-age=N`
64 int32_t s_maxage; ///< `s-maxage=N`
65 int32_t stale_while_revalidate; ///< `stale-while-revalidate=N` (RFC 5861)
66 int32_t stale_if_error; ///< `stale-if-error=N` (RFC 5861)
67 int32_t max_stale; ///< `max-stale[=N]` (request; -1 absent, -2 no value)
68 int32_t min_fresh; ///< `min-fresh=N` (request)
69} protocore_cache_control;
70
71/** @brief What control_init takes: cc. */
72typedef struct
73{
74 protocore_cache_control *cc;
75} HttpcacheControlInitArgs;
76
77/** @brief What control_build takes: buf, cap, cc. */
78typedef struct
79{
80 char *buf;
81 size_t cap;
82 const protocore_cache_control *cc;
83} HttpcacheControlBuildArgs;
84
85/** @brief What control_parse takes: s, len, cc. */
86typedef struct
87{
88 const char *s;
89 size_t len;
90 protocore_cache_control *cc;
91} HttpcacheControlParseArgs;
92
93/** @brief What immutable_asset takes: cc, max_age. */
94typedef struct
95{
96 protocore_cache_control *cc;
97 uint32_t max_age;
98} HttpcacheImmutableAssetArgs;
99
100/** @brief What revalidatable takes: cc, max_age, ... */
101typedef struct
102{
103 protocore_cache_control *cc;
104 uint32_t max_age;
105 int32_t stale_while_revalidate;
106} HttpcacheRevalidatableArgs;
107
108/** @brief What no_store takes: cc. */
109typedef struct
110{
111 protocore_cache_control *cc;
112} HttpcacheNoStoreArgs;
113
114/** @brief What shared takes: cc, max_age, s_maxage. */
115typedef struct
116{
117 protocore_cache_control *cc;
118 uint32_t max_age;
119 uint32_t s_maxage;
120} HttpcacheSharedArgs;
121
122/** @brief What freshness_lifetime takes: cc, shared, ... */
123typedef struct
124{
125 const protocore_cache_control *cc;
126 proto_bool shared; ///< true for a shared cache (honors s-maxage)
127 long expires_minus_date; ///< `Expires` minus `Date` in seconds, or < 0 when that pair is absent
128} HttpcacheFreshnessLifetimeArgs;
129
130/**
131 * @brief HTTP `Cache-Control` directive builder + parser + freshness helper (RFC 9111), PROTOCORE_ENABLE_HTTP_CACHE.
132 *
133 * A caller sets the members a call takes, invokes it through ::Httpcache with the bytes it runs
134 * out of, and reads the outcome off the same handle.
135 *
136 * Httpcache.control_init_args.cc = ...;
137 * Httpcache.control_init(work);
138 *
139 * @var HttpcacheNs::control_init_args what control_init takes: cc
140 * @var HttpcacheNs::control_build_args what control_build takes: buf, cap, cc
141 * @var HttpcacheNs::control_parse_args what control_parse takes: s, len, cc
142 * @var HttpcacheNs::immutable_asset_args what immutable_asset takes: cc, max_age
143 * @var HttpcacheNs::revalidatable_args what revalidatable takes: cc, max_age,
144 * @var HttpcacheNs::no_store_args what no_store takes: cc
145 * @var HttpcacheNs::shared_args what shared takes: cc, max_age, s_maxage
146 * @var HttpcacheNs::freshness_lifetime_args what freshness_lifetime takes: cc, shared,
147 * @var HttpcacheNs::ok true if at least one known directive was parsed
148 * @var HttpcacheNs::n bytes written (excluding NUL), or 0 on overflow or an empty ...
149 * @var HttpcacheNs::value the freshness lifetime in seconds, or -1 when none is explicit ...
150 * @var HttpcacheNs::control_init reset to an empty set (all flags false, all delta-seconds -1)
151 * @var HttpcacheNs::control_build build the canonical `Cache-Control` value (no `Cache-Control:` ...
152 * @var HttpcacheNs::control_parse parse a `Cache-Control` header value into cc (initializes it ...
153 * @var HttpcacheNs::immutable_asset long-lived immutable static asset: `public, max-age=<secs>, ...
154 * @var HttpcacheNs::revalidatable cacheable but served-while-revalidating: `public, max-age=<secs>` ...
155 * @var HttpcacheNs::no_store dynamic / sensitive - never store: `no-store`
156 * @var HttpcacheNs::shared distinct shared-cache TTL: `public, max-age=<browser>, ...
157 * @var HttpcacheNs::freshness_lifetime freshness lifetime in seconds (RFC 9111 sec 4.2.1), first-match ...
158 *
159 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
160 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
161 * a caller drives every namespace the same way.
162 */
163typedef struct
164{
165 HttpcacheControlInitArgs control_init_args;
166 HttpcacheControlBuildArgs control_build_args;
167 HttpcacheControlParseArgs control_parse_args;
168 HttpcacheImmutableAssetArgs immutable_asset_args;
169 HttpcacheRevalidatableArgs revalidatable_args;
170 HttpcacheNoStoreArgs no_store_args;
171 HttpcacheSharedArgs shared_args;
172 HttpcacheFreshnessLifetimeArgs freshness_lifetime_args;
173 proto_bool ok;
174 size_t n;
175 long value;
176} HttpcacheVars;
177
178/** @brief The operands and the outcome. */
179extern HttpcacheVars HttpcacheV;
180
181/** @brief The entries. */
182typedef struct
183{
184 void (*const control_init)(uint8_t *work);
185 void (*const control_build)(uint8_t *work);
186 void (*const control_parse)(uint8_t *work);
187 void (*const immutable_asset)(uint8_t *work);
188 void (*const revalidatable)(uint8_t *work);
189 void (*const no_store)(uint8_t *work);
190 void (*const shared)(uint8_t *work);
191 void (*const freshness_lifetime)(uint8_t *work);
192} HttpcacheNs;
193
194// What the table binds, defined once in the .c and taking one parameter each: everything
195// else an entry needs is an operand in HttpcacheV or a region of the borrow at a fixed offset.
196void protocore_httpcache_control_init(uint8_t *work);
197void protocore_httpcache_control_build(uint8_t *work);
198void protocore_httpcache_control_parse(uint8_t *work);
199void protocore_httpcache_immutable_asset(uint8_t *work);
200void protocore_httpcache_revalidatable(uint8_t *work);
201void protocore_httpcache_no_store(uint8_t *work);
202void protocore_httpcache_shared(uint8_t *work);
203void protocore_httpcache_freshness_lifetime(uint8_t *work);
204
205// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
206// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
207// `Httpcache.control_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
208// leaves the call indirect and the symbol live at every level, -O2 -flto included.
209static const HttpcacheNs Httpcache __attribute__((unused)) = {
210 .control_init = protocore_httpcache_control_init,
211 .control_build = protocore_httpcache_control_build,
212 .control_parse = protocore_httpcache_control_parse,
213 .immutable_asset = protocore_httpcache_immutable_asset,
214 .revalidatable = protocore_httpcache_revalidatable,
215 .no_store = protocore_httpcache_no_store,
216 .shared = protocore_httpcache_shared,
217 .freshness_lifetime = protocore_httpcache_freshness_lifetime,
218};
219
221
222#endif // PROTOCORE_ENABLE_HTTP_CACHE
223
224#endif // PROTOCORE_HTTPCACHE_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