11#if PC_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')
32 while (*p >=
'0' && *p <=
'9')
34 v = v * 10 + (*p -
'0');
43bool rd_month(
const char **pp,
int *out)
45 static const char MONTHS[] =
"janfebmaraprmayjunjulaugsepoctnovdec";
50 for (
int m = 0; m < 12; m++)
52 if (a == MONTHS[m * 3] && b == MONTHS[m * 3 + 1] && c == MONTHS[m * 3 + 2])
63bool rd_time(
const char **pp,
int *hh,
int *mm,
int *ss)
66 if (!rd_uint(&p, hh) || *p !=
':')
71 if (!rd_uint(&p, mm) || *p !=
':')
85int64_t days_from_civil(
int y,
int m,
int d)
91 int64_t era = (y >= 0 ? y : y - 399) / 400;
92 int64_t yoe = y - era * 400;
93 int64_t doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
94 int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
95 return era * 146097 + doe - 719468;
99bool k_append(
char *out,
size_t *pos,
size_t cap,
const char *s,
bool lower)
102 for (
const char *q = s; *q; q++)
108 out[p++] = lower ? lc(*q) : *q;
120bool header_line_value(
const char *p,
const char *lend,
const char *name,
size_t namelen,
char *out,
size_t out_cap,
123 const char *colon = p;
124 while (colon < lend && *colon !=
':')
128 if (colon >= lend || (
size_t)(colon - p) != namelen)
132 for (
size_t i = 0; i < namelen; i++)
134 if (lc(p[i]) != lc(name[i]))
139 const char *v = colon + 1;
140 while (v < lend && (*v ==
' ' || *v ==
'\t'))
144 const char *ve = lend;
145 while (ve > v && (ve[-1] ==
' ' || ve[-1] ==
'\t'))
149 size_t vl = (size_t)(ve - v);
155 for (
size_t i = 0; i < vl; i++)
164bool edge_header_value(
const char *hdrs,
size_t len,
const char *name,
char *out,
size_t out_cap)
166 if (!hdrs || !name || !out || out_cap == 0)
171 size_t namelen = strnlen(name, out_cap);
172 const char *p = hdrs;
173 const char *end = hdrs + len;
175 while (p < end && *p !=
'\n')
185 if (*p ==
'\r' || *p ==
'\n')
190 while (le < end && *le !=
'\n')
194 const char *lend = le;
198 if (lend > p && lend[-1] ==
'\r')
202 bool overflow =
false;
203 if (header_line_value(p, lend, name, namelen, out, out_cap, &overflow))
211 p = (le < end) ? le + 1 : end;
219bool parse_date_after_comma(
const char *p,
int *mday,
int *mon,
int *year,
int *hh,
int *mm,
int *ss)
225 if (!rd_uint(&p, mday))
229 bool rfc850 = (*p ==
'-');
230 if (*p ==
'-' || *p ==
' ')
238 if (!rd_month(&p, mon))
253 if (!rd_uint(&p, year))
257 if (rfc850 && *year < 100)
259 *year += (*year < 70) ? 2000 : 1900;
265 return rd_time(&p, hh, mm, ss);
269bool parse_date_asctime(
const char *p,
int *mday,
int *mon,
int *year,
int *hh,
int *mm,
int *ss)
271 while (*p && *p !=
' ')
279 if (!rd_month(&p, mon))
287 if (!rd_uint(&p, mday))
295 if (!rd_time(&p, hh, mm, ss))
303 return rd_uint(&p, year);
307int64_t edge_parse_http_date(
const char *s,
size_t len)
314 while (len && (*s ==
' ' || *s ==
'\t'))
319 if (len == 0 || len >=
sizeof(buf))
332 const char *comma = strchr(buf,
',');
333 bool ok = comma ? parse_date_after_comma(comma + 1, &mday, &mon, &year, &hh, &mm, &ss)
334 : parse_date_asctime(buf, &mday, &mon, &year, &hh, &mm, &ss);
344 if (mon < 1 || mon > 12)
349 if (mday < 1 || mday > 31 || hh > 23 || mm > 59 || ss > 60)
353 int64_t days = days_from_civil(year, mon, mday);
354 return days * 86400 + (int64_t)hh * 3600 + (int64_t)mm * 60 + ss;
357long edge_freshness_lifetime(
const pc_cache_control *cc,
bool shared, int64_t date_epoch, int64_t expires_epoch)
359 long expires_minus_date = -1;
360 if (date_epoch >= 0 && expires_epoch >= 0)
362 expires_minus_date = (long)(expires_epoch - date_epoch);
364 return cache_freshness_lifetime(cc, shared, expires_minus_date);
367long edge_heuristic_lifetime(int64_t date_epoch, int64_t last_modified_epoch)
369 if (date_epoch < 0 || last_modified_epoch < 0 || last_modified_epoch >= date_epoch)
373 return (
long)((date_epoch - last_modified_epoch) / 10);
376long edge_initial_age(int32_t age_hdr, int64_t date_epoch, int64_t response_time_epoch)
379 if (date_epoch >= 0 && response_time_epoch >= 0 && response_time_epoch > date_epoch)
381 apparent = (long)(response_time_epoch - date_epoch);
383 long corrected = (age_hdr > 0) ? (
long)age_hdr : 0;
384 return (apparent > corrected) ? apparent : corrected;
387long edge_current_age(
long initial_age, uint32_t insert_ms, uint32_t now_ms)
389 uint32_t resident_ms = now_ms - insert_ms;
390 return initial_age + (long)(resident_ms / 1000u);
393bool edge_is_fresh_at(
long lifetime,
long current_age)
395 return lifetime >= 0 && current_age < lifetime;
398size_t edge_key_canon(
const char *method,
const char *host,
const char *path,
const char *query,
bool include_query,
399 char *out,
size_t out_cap)
401 if (!method || !host || !path || !out || out_cap == 0)
406 if (!k_append(out, &pos, out_cap, method,
false))
410 if (!k_append(out, &pos, out_cap,
"\n",
false) || !k_append(out, &pos, out_cap, host,
true))
414 if (!k_append(out, &pos, out_cap,
"\n",
false) || !k_append(out, &pos, out_cap, path,
false))
418 if (include_query && query && query[0] &&
419 (!k_append(out, &pos, out_cap,
"\n",
false) || !k_append(out, &pos, out_cap, query,
false)))
427void edge_key_digest(
const char *canon,
size_t len, uint8_t digest[32])
429 pc_sha256((
const uint8_t *)canon, len, digest);
437bool vary_emit_one(
const char **pp, EdgeHdrLookup lookup,
void *ctx,
char *out,
size_t *pos,
size_t out_cap)
442 while (*p && *p !=
',' && *p !=
' ' && *p !=
'\t')
448 if (nl + 1 <
sizeof(name))
464 const char *val = lookup ? lookup(ctx, name) : nullptr;
465 if (!k_append(out, pos, out_cap, name,
false) || !k_append(out, pos, out_cap,
"\x1e",
false))
469 if (val && !k_append(out, pos, out_cap, val,
false))
473 return k_append(out, pos, out_cap,
"\x1f",
false);
477bool edge_vary_serialize(
const char *vary_header, EdgeHdrLookup lookup,
void *ctx,
char *out,
size_t out_cap)
479 if (!out || out_cap == 0)
489 const char *p = vary_header;
492 while (*p ==
' ' || *p ==
'\t' || *p ==
',')
500 if (!vary_emit_one(&p, lookup, ctx, out, &pos, out_cap))
513void lru_unlink(EdgeCacheStore *s, uint16_t i)
515 EdgeEntry *e = &s->entries[i];
516 if (e->lru.prev != PC_EDGE_LRU_NONE)
518 s->entries[e->lru.prev].lru.next = e->lru.next;
522 s->lru_head = e->lru.next;
524 if (e->lru.next != PC_EDGE_LRU_NONE)
526 s->entries[e->lru.next].lru.prev = e->lru.prev;
530 s->lru_tail = e->lru.prev;
532 e->lru.next = PC_EDGE_LRU_NONE;
533 e->lru.prev = PC_EDGE_LRU_NONE;
536void lru_push_front(EdgeCacheStore *s, uint16_t i)
538 EdgeEntry *e = &s->entries[i];
539 e->lru.prev = PC_EDGE_LRU_NONE;
540 e->lru.next = s->lru_head;
541 if (s->lru_head != PC_EDGE_LRU_NONE)
543 s->entries[s->lru_head].lru.prev = i;
546 if (s->lru_tail == PC_EDGE_LRU_NONE)
552void store_free(EdgeCacheStore *s, uint16_t i)
555 s->entries[i].used =
false;
559const char *key_path(
const char *key)
562 for (
const char *p = key; *p; p++)
576bool vary_is_star(
const char *vary_header)
582 for (
const char *p = vary_header; *p; p++)
593void edge_store_init(EdgeCacheStore *s)
595 memset(s, 0,
sizeof(*s));
596 s->lru_head = PC_EDGE_LRU_NONE;
597 s->lru_tail = PC_EDGE_LRU_NONE;
600 s->entries[i].lru.prev = PC_EDGE_LRU_NONE;
601 s->entries[i].lru.next = PC_EDGE_LRU_NONE;
605EdgeEntry *edge_store_alloc(EdgeCacheStore *s,
const char *canon,
const char *vary_key)
607 size_t klen = strnlen(canon,
sizeof(s->entries[0].key));
608 if (klen >=
sizeof(s->entries[0].key))
612 uint16_t slot = PC_EDGE_LRU_NONE;
615 if (!s->entries[i].used)
621 if (slot == PC_EDGE_LRU_NONE)
623 if (s->lru_tail == PC_EDGE_LRU_NONE)
629 if (s->on_evict && s->entries[slot].key[0] !=
'\0')
631 s->on_evict(s->evict_ctx, &s->entries[slot]);
634 s->stats.evictions++;
636 EdgeEntry *e = &s->entries[slot];
637 memset(e, 0,
sizeof(*e));
638 e->lru.prev = PC_EDGE_LRU_NONE;
639 e->lru.next = PC_EDGE_LRU_NONE;
641 memcpy(e->key, canon, klen);
643 edge_key_digest(canon, klen, e->digest);
644 size_t vl = vary_key ? strnlen(vary_key,
sizeof(e->vary_vals)) : 0;
645 if (vl >=
sizeof(e->vary_vals))
647 vl =
sizeof(e->vary_vals) - 1;
651 memcpy(e->vary_vals, vary_key, vl);
653 e->vary_vals[vl] =
'\0';
655 e->expires_epoch = -1;
656 lru_push_front(s, slot);
661EdgeEntry *edge_store_lookup(EdgeCacheStore *s,
const char *canon,
const char *vary_key, uint32_t now_ms)
663 const char *vk = vary_key ? vary_key :
"";
666 EdgeEntry *e = &s->entries[i];
667 if (e->used && strcmp(e->key, canon) == 0 && strcmp(e->vary_vals, vk) == 0)
670 lru_push_front(s, i);
671 e->last_used_ms = now_ms;
678EdgeEntry *edge_store_find(EdgeCacheStore *s,
const char *canon, EdgeHdrLookup lookup,
void *ctx, uint32_t now_ms)
682 EdgeEntry *e = &s->entries[i];
683 if (!e->used || strcmp(e->key, canon) != 0)
689 if (!edge_vary_serialize(e->vary_names, lookup, ctx, cur,
sizeof(cur)))
693 if (strcmp(cur, e->vary_vals) == 0)
696 lru_push_front(s, i);
697 e->last_used_ms = now_ms;
704void edge_entry_set_freshness(EdgeEntry *e,
const pc_cache_control *cc,
bool shared, int64_t date_epoch,
705 int64_t expires_epoch, int64_t last_modified_epoch, int32_t age_hdr,
706 int64_t response_time_epoch, uint32_t now_ms)
708 long lifetime = edge_freshness_lifetime(cc, shared, date_epoch, expires_epoch);
711 lifetime = edge_heuristic_lifetime(date_epoch, last_modified_epoch);
717 e->lifetime_s = lifetime;
718 e->initial_age = edge_initial_age(age_hdr, date_epoch, response_time_epoch);
719 e->insert_ms = now_ms;
720 e->date_epoch = date_epoch;
721 e->expires_epoch = expires_epoch;
722 e->age_hdr = (age_hdr > 0) ? age_hdr : 0;
725bool edge_entry_has_validator(
const EdgeEntry *e)
727 return e->etag[0] !=
'\0' || e->last_modified[0] !=
'\0';
730bool edge_entry_fresh(
const EdgeEntry *e, uint32_t now_ms)
732 return edge_is_fresh_at(e->lifetime_s, edge_current_age(e->initial_age, e->insert_ms, now_ms));
735uint32_t edge_store_sweep(EdgeCacheStore *s, uint32_t now_ms)
740 const EdgeEntry *e = &s->entries[i];
741 if (e->used && !edge_entry_has_validator(e) && !edge_entry_fresh(e, now_ms))
744 s->stats.evictions++;
751uint32_t edge_store_purge(EdgeCacheStore *s,
const char *canon)
756 if (s->entries[i].used && strcmp(s->entries[i].key, canon) == 0)
766uint32_t edge_store_purge_prefix(EdgeCacheStore *s,
const char *prefix)
768 size_t plen = strnlen(prefix,
sizeof(s->entries[0].key));
772 if (!s->entries[i].used)
776 const char *path = key_path(s->entries[i].key);
777 if (path && strncmp(path, prefix, plen) == 0)
787void edge_store_free_entry(EdgeCacheStore *s,
const EdgeEntry *e)
791 if (&s->entries[i] == e)
799bool edge_is_storeable(
int status,
const char *method,
const pc_cache_control *cc,
const char *vary_header,
802 if (!method || strcmp(method,
"GET") != 0)
810 if (cc && (cc->no_store || cc->cc_private))
814 if (vary_is_star(vary_header))
830bool hdr_line(
char *out,
size_t *pos,
size_t cap,
const char *name,
const char *value)
832 return k_append(out, pos, cap, name,
false) && k_append(out, pos, cap,
": ",
false) &&
833 k_append(out, pos, cap, value,
false) && k_append(out, pos, cap,
"\r\n",
false);
837size_t edge_build_conditional(
const EdgeEntry *e,
char *out,
size_t cap)
839 if (!out || cap == 0)
844 if (e->etag[0] && !hdr_line(out, &pos, cap,
"If-None-Match", e->etag))
848 if (e->last_modified[0] && !hdr_line(out, &pos, cap,
"If-Modified-Since", e->last_modified))
856void edge_apply_304(EdgeEntry *e,
const char *new_hdrs,
size_t hdr_len, int64_t response_time_epoch, uint32_t now_ms)
860 if (edge_header_value(new_hdrs, hdr_len,
"Cache-Control", v,
sizeof(v)))
862 cache_control_parse(v, strnlen(v,
sizeof(v)), &cc);
866 cache_control_init(&cc);
870 if (edge_header_value(new_hdrs, hdr_len,
"Date", v,
sizeof(v)))
872 date = edge_parse_http_date(v, strnlen(v,
sizeof(v)));
874 int64_t expires = -1;
875 if (edge_header_value(new_hdrs, hdr_len,
"Expires", v,
sizeof(v)))
877 expires = edge_parse_http_date(v, strnlen(v,
sizeof(v)));
881 if (edge_header_value(new_hdrs, hdr_len,
"Age", v,
sizeof(v)))
885 for (
const char *p = v; *p >=
'0' && *p <=
'9'; p++)
887 a = a * 10 + (*p -
'0');
897 if (edge_header_value(new_hdrs, hdr_len,
"ETag", v,
sizeof(v)))
899 size_t vlen = strnlen(v,
sizeof(v));
900 if (vlen <
sizeof(e->etag))
902 memcpy(e->etag, v, vlen + 1);
905 int64_t last_mod = -1;
906 if (edge_header_value(new_hdrs, hdr_len,
"Last-Modified", v,
sizeof(v)))
908 size_t vlen = strnlen(v,
sizeof(v));
909 last_mod = edge_parse_http_date(v, vlen);
910 if (vlen <
sizeof(e->last_modified))
912 memcpy(e->last_modified, v, vlen + 1);
915 else if (e->last_modified[0])
917 last_mod = edge_parse_http_date(e->last_modified, strnlen(e->last_modified,
sizeof(e->last_modified)));
920 edge_entry_set_freshness(e, &cc,
true, date, expires, last_mod, age, response_time_epoch, now_ms);
#define PC_EDGE_CACHE_SLOTS
CDN edge-cache tier - pure engine (PC_ENABLE_EDGE_CACHE).
#define PC_EDGE_DEFAULT_TTL_S
void pc_sha256(const uint8_t *data, size_t len, uint8_t digest[PC_SHA256_DIGEST_LEN])
One-shot SHA-256: hash len bytes of data into digest (32 bytes).
SHA-256 (FIPS 180-4) - streaming context and one-shot API.