11#if DETWS_ENABLE_EDGE_CACHE
20 return (c >=
'A' && c <=
'Z') ? (char)(c + 32) : c;
24bool rd_uint(
const char **pp,
int *out)
27 if (*p <
'0' || *p >
'9')
30 while (*p >=
'0' && *p <=
'9')
32 v = v * 10 + (*p -
'0');
41bool rd_month(
const char **pp,
int *out)
43 static const char MONTHS[] =
"janfebmaraprmayjunjulaugsepoctnovdec";
48 for (
int m = 0; m < 12; m++)
49 if (a == MONTHS[m * 3] && b == MONTHS[m * 3 + 1] && c == MONTHS[m * 3 + 2])
59bool rd_time(
const char **pp,
int *hh,
int *mm,
int *ss)
62 if (!rd_uint(&p, hh) || *p !=
':')
65 if (!rd_uint(&p, mm) || *p !=
':')
75int64_t days_from_civil(
int y,
int m,
int d)
78 int64_t era = (y >= 0 ? y : y - 399) / 400;
79 int64_t yoe = y - era * 400;
80 int64_t doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
81 int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
82 return era * 146097 + doe - 719468;
86bool k_append(
char *out,
size_t *pos,
size_t cap,
const char *s,
bool lower)
89 for (
const char *q = s; *q; q++)
93 out[p++] = lower ? lc(*q) : *q;
100bool edge_header_value(
const char *hdrs,
size_t len,
const char *name,
char *out,
size_t out_cap)
102 if (!hdrs || !name || !out || out_cap == 0)
105 size_t namelen = strlen(name);
106 const char *p = hdrs;
107 const char *end = hdrs + len;
109 while (p < end && *p !=
'\n')
115 if (*p ==
'\r' || *p ==
'\n')
118 while (le < end && *le !=
'\n')
120 const char *lend = le;
121 if (lend > p && lend[-1] ==
'\r')
123 const char *colon = p;
124 while (colon < lend && *colon !=
':')
126 if (colon < lend && (
size_t)(colon - p) == namelen)
129 for (
size_t i = 0; i < namelen; i++)
130 if (lc(p[i]) != lc(name[i]))
137 const char *v = colon + 1;
138 while (v < lend && (*v ==
' ' || *v ==
'\t'))
140 const char *ve = lend;
141 while (ve > v && (ve[-1] ==
' ' || ve[-1] ==
'\t'))
143 size_t vl = (size_t)(ve - v);
146 for (
size_t i = 0; i < vl; i++)
152 p = (le < end) ? le + 1 : end;
157int64_t edge_parse_http_date(
const char *s,
size_t len)
162 while (len && (*s ==
' ' || *s ==
'\t'))
167 if (len == 0 || len >=
sizeof(buf))
178 const char *comma = strchr(buf,
',');
182 const char *p = comma + 1;
185 if (!rd_uint(&p, &mday))
187 bool rfc850 = (*p ==
'-');
188 if (*p ==
'-' || *p ==
' ')
192 if (!rd_month(&p, &mon))
199 if (!rd_uint(&p, &year))
201 if (rfc850 && year < 100)
202 year += (year < 70) ? 2000 : 1900;
205 if (!rd_time(&p, &hh, &mm, &ss))
212 while (*p && *p !=
' ')
216 if (!rd_month(&p, &mon))
220 if (!rd_uint(&p, &mday))
224 if (!rd_time(&p, &hh, &mm, &ss))
228 if (!rd_uint(&p, &year))
232 if (mon < 1 || mon > 12 || mday < 1 || mday > 31 || hh > 23 || mm > 59 || ss > 60)
234 int64_t days = days_from_civil(year, mon, mday);
235 return days * 86400 + (int64_t)hh * 3600 + (int64_t)mm * 60 + ss;
238long edge_freshness_lifetime(
const DetwsCacheControl *cc,
bool shared, int64_t date_epoch, int64_t expires_epoch)
240 long expires_minus_date = -1;
241 if (date_epoch >= 0 && expires_epoch >= 0)
242 expires_minus_date = (long)(expires_epoch - date_epoch);
243 return cache_freshness_lifetime(cc, shared, expires_minus_date);
246long edge_heuristic_lifetime(int64_t date_epoch, int64_t last_modified_epoch)
248 if (date_epoch < 0 || last_modified_epoch < 0 || last_modified_epoch >= date_epoch)
250 return (
long)((date_epoch - last_modified_epoch) / 10);
253long edge_initial_age(int32_t age_hdr, int64_t date_epoch, int64_t response_time_epoch)
256 if (date_epoch >= 0 && response_time_epoch >= 0 && response_time_epoch > date_epoch)
257 apparent = (long)(response_time_epoch - date_epoch);
258 long corrected = (age_hdr > 0) ? (
long)age_hdr : 0;
259 return (apparent > corrected) ? apparent : corrected;
262long edge_current_age(
long initial_age, uint32_t insert_ms, uint32_t now_ms)
264 uint32_t resident_ms = now_ms - insert_ms;
265 return initial_age + (long)(resident_ms / 1000u);
268bool edge_is_fresh_at(
long lifetime,
long current_age)
270 return lifetime >= 0 && current_age < lifetime;
273size_t edge_key_canon(
const char *method,
const char *host,
const char *path,
const char *query,
bool include_query,
274 char *out,
size_t out_cap)
276 if (!method || !host || !path || !out || out_cap == 0)
279 if (!k_append(out, &pos, out_cap, method,
false))
281 if (!k_append(out, &pos, out_cap,
"\n",
false) || !k_append(out, &pos, out_cap, host,
true))
283 if (!k_append(out, &pos, out_cap,
"\n",
false) || !k_append(out, &pos, out_cap, path,
false))
285 if (include_query && query && query[0])
287 if (!k_append(out, &pos, out_cap,
"\n",
false) || !k_append(out, &pos, out_cap, query,
false))
294void edge_key_digest(
const char *canon,
size_t len, uint8_t digest[32])
296 ssh_sha256((
const uint8_t *)canon, len, digest);
299bool edge_vary_serialize(
const char *vary_header, EdgeHdrLookup lookup,
void *ctx,
char *out,
size_t out_cap)
301 if (!out || out_cap == 0)
307 const char *p = vary_header;
310 while (*p ==
' ' || *p ==
'\t' || *p ==
',')
317 while (*p && *p !=
',' && *p !=
' ' && *p !=
'\t')
321 if (nl + 1 <
sizeof(name))
328 const char *val = lookup ? lookup(ctx, name) : nullptr;
331 if (!k_append(out, &pos, out_cap, name,
false) || !k_append(out, &pos, out_cap,
"\x1e",
false))
333 if (val && !k_append(out, &pos, out_cap, val,
false))
335 if (!k_append(out, &pos, out_cap,
"\x1f",
false))
346void lru_unlink(EdgeCacheStore *s, uint16_t i)
348 EdgeEntry *e = &s->entries[i];
349 if (e->lru_prev != EDGE_LRU_NONE)
350 s->entries[e->lru_prev].lru_next = e->lru_next;
352 s->lru_head = e->lru_next;
353 if (e->lru_next != EDGE_LRU_NONE)
354 s->entries[e->lru_next].lru_prev = e->lru_prev;
356 s->lru_tail = e->lru_prev;
357 e->lru_prev = e->lru_next = EDGE_LRU_NONE;
360void lru_push_front(EdgeCacheStore *s, uint16_t i)
362 EdgeEntry *e = &s->entries[i];
363 e->lru_prev = EDGE_LRU_NONE;
364 e->lru_next = s->lru_head;
365 if (s->lru_head != EDGE_LRU_NONE)
366 s->entries[s->lru_head].lru_prev = i;
368 if (s->lru_tail == EDGE_LRU_NONE)
372void store_free(EdgeCacheStore *s, uint16_t i)
375 s->entries[i].used =
false;
379const char *key_path(
const char *key)
382 for (
const char *p = key; *p; p++)
383 if (*p ==
'\n' && ++nl == 2)
388bool vary_is_star(
const char *vary_header)
392 for (
const char *p = vary_header; *p; p++)
399void edge_store_init(EdgeCacheStore *s)
401 memset(s, 0,
sizeof(*s));
402 s->lru_head = s->lru_tail = EDGE_LRU_NONE;
404 s->entries[i].lru_prev = s->entries[i].lru_next = EDGE_LRU_NONE;
407EdgeEntry *edge_store_alloc(EdgeCacheStore *s,
const char *canon,
const char *vary_key)
409 size_t klen = strlen(canon);
410 if (klen >=
sizeof(s->entries[0].key))
412 uint16_t slot = EDGE_LRU_NONE;
414 if (!s->entries[i].used)
419 if (slot == EDGE_LRU_NONE)
421 if (s->lru_tail == EDGE_LRU_NONE)
425 if (s->on_evict && s->entries[slot].key[0] !=
'\0')
426 s->on_evict(s->evict_ctx, &s->entries[slot]);
428 s->stats.evictions++;
430 EdgeEntry *e = &s->entries[slot];
431 memset(e, 0,
sizeof(*e));
432 e->lru_prev = e->lru_next = EDGE_LRU_NONE;
434 memcpy(e->key, canon, klen);
436 edge_key_digest(canon, klen, e->digest);
437 size_t vl = vary_key ? strlen(vary_key) : 0;
438 if (vl >=
sizeof(e->vary_vals))
439 vl =
sizeof(e->vary_vals) - 1;
441 memcpy(e->vary_vals, vary_key, vl);
442 e->vary_vals[vl] =
'\0';
443 e->date_epoch = e->expires_epoch = -1;
444 lru_push_front(s, slot);
449EdgeEntry *edge_store_lookup(EdgeCacheStore *s,
const char *canon,
const char *vary_key, uint32_t now_ms)
451 const char *vk = vary_key ? vary_key :
"";
454 EdgeEntry *e = &s->entries[i];
455 if (e->used && strcmp(e->key, canon) == 0 && strcmp(e->vary_vals, vk) == 0)
458 lru_push_front(s, i);
459 e->last_used_ms = now_ms;
466EdgeEntry *edge_store_find(EdgeCacheStore *s,
const char *canon, EdgeHdrLookup lookup,
void *ctx, uint32_t now_ms)
470 EdgeEntry *e = &s->entries[i];
471 if (!e->used || strcmp(e->key, canon) != 0)
475 if (!edge_vary_serialize(e->vary_names, lookup, ctx, cur,
sizeof(cur)))
477 if (strcmp(cur, e->vary_vals) == 0)
480 lru_push_front(s, i);
481 e->last_used_ms = now_ms;
488void edge_entry_set_freshness(EdgeEntry *e,
const DetwsCacheControl *cc,
bool shared, int64_t date_epoch,
489 int64_t expires_epoch, int64_t last_modified_epoch, int32_t age_hdr,
490 int64_t response_time_epoch, uint32_t now_ms)
492 long lifetime = edge_freshness_lifetime(cc, shared, date_epoch, expires_epoch);
494 lifetime = edge_heuristic_lifetime(date_epoch, last_modified_epoch);
497 e->lifetime_s = lifetime;
498 e->initial_age = edge_initial_age(age_hdr, date_epoch, response_time_epoch);
499 e->insert_ms = now_ms;
500 e->date_epoch = date_epoch;
501 e->expires_epoch = expires_epoch;
502 e->age_hdr = (age_hdr > 0) ? age_hdr : 0;
505bool edge_entry_has_validator(
const EdgeEntry *e)
507 return e->etag[0] !=
'\0' || e->last_modified[0] !=
'\0';
510bool edge_entry_fresh(
const EdgeEntry *e, uint32_t now_ms)
512 return edge_is_fresh_at(e->lifetime_s, edge_current_age(e->initial_age, e->insert_ms, now_ms));
515uint32_t edge_store_sweep(EdgeCacheStore *s, uint32_t now_ms)
520 EdgeEntry *e = &s->entries[i];
521 if (e->used && !edge_entry_has_validator(e) && !edge_entry_fresh(e, now_ms))
524 s->stats.evictions++;
531uint32_t edge_store_purge(EdgeCacheStore *s,
const char *canon)
535 if (s->entries[i].used && strcmp(s->entries[i].key, canon) == 0)
544uint32_t edge_store_purge_prefix(EdgeCacheStore *s,
const char *prefix)
546 size_t plen = strlen(prefix);
550 if (!s->entries[i].used)
552 const char *path = key_path(s->entries[i].key);
553 if (path && strncmp(path, prefix, plen) == 0)
563void edge_store_free_entry(EdgeCacheStore *s, EdgeEntry *e)
566 if (&s->entries[i] == e)
573bool edge_is_storeable(
int status,
const char *method,
const DetwsCacheControl *cc,
const char *vary_header,
576 if (!method || strcmp(method,
"GET") != 0)
580 if (cc && (cc->no_store || cc->cc_private))
582 if (vary_is_star(vary_header))
594bool hdr_line(
char *out,
size_t *pos,
size_t cap,
const char *name,
const char *value)
596 return k_append(out, pos, cap, name,
false) && k_append(out, pos, cap,
": ",
false) &&
597 k_append(out, pos, cap, value,
false) && k_append(out, pos, cap,
"\r\n",
false);
601size_t edge_build_conditional(
const EdgeEntry *e,
char *out,
size_t cap)
603 if (!out || cap == 0)
606 if (e->etag[0] && !hdr_line(out, &pos, cap,
"If-None-Match", e->etag))
608 if (e->last_modified[0] && !hdr_line(out, &pos, cap,
"If-Modified-Since", e->last_modified))
614void edge_apply_304(EdgeEntry *e,
const char *new_hdrs,
size_t hdr_len, int64_t response_time_epoch, uint32_t now_ms)
617 DetwsCacheControl cc;
618 if (edge_header_value(new_hdrs, hdr_len,
"Cache-Control", v,
sizeof(v)))
619 cache_control_parse(v, strlen(v), &cc);
621 cache_control_init(&cc);
624 if (edge_header_value(new_hdrs, hdr_len,
"Date", v,
sizeof(v)))
625 date = edge_parse_http_date(v, strlen(v));
626 int64_t expires = -1;
627 if (edge_header_value(new_hdrs, hdr_len,
"Expires", v,
sizeof(v)))
628 expires = edge_parse_http_date(v, strlen(v));
631 if (edge_header_value(new_hdrs, hdr_len,
"Age", v,
sizeof(v)))
635 for (
const char *p = v; *p >=
'0' && *p <=
'9'; p++)
637 a = a * 10 + (*p -
'0');
645 if (edge_header_value(new_hdrs, hdr_len,
"ETag", v,
sizeof(v)) && strlen(v) <
sizeof(e->etag))
646 memcpy(e->etag, v, strlen(v) + 1);
647 int64_t last_mod = -1;
648 if (edge_header_value(new_hdrs, hdr_len,
"Last-Modified", v,
sizeof(v)))
650 last_mod = edge_parse_http_date(v, strlen(v));
651 if (strlen(v) <
sizeof(e->last_modified))
652 memcpy(e->last_modified, v, strlen(v) + 1);
654 else if (e->last_modified[0])
655 last_mod = edge_parse_http_date(e->last_modified, strlen(e->last_modified));
657 edge_entry_set_freshness(e, &cc,
true, date, expires, last_mod, age, response_time_epoch, now_ms);
#define DETWS_EDGE_BODY_MAX
#define DETWS_EDGE_CACHE_SLOTS
#define DETWS_EDGE_VARY_MAX
#define DETWS_EDGE_DEFAULT_TTL_S
CDN edge-cache tier - pure engine (DETWS_ENABLE_EDGE_CACHE).
void ssh_sha256(const uint8_t *data, size_t len, uint8_t digest[SSH_SHA256_DIGEST_LEN])
One-shot SHA-256: hash len bytes and write the digest.
SHA-256 (FIPS 180-4) - streaming context and one-shot API.