12#if PC_ENABLE_EDGE_CACHE
27#if PC_ENABLE_EDGE_ORIGIN_TLS
29#include <mbedtls/ssl.h>
31#if PC_ENABLE_EDGE_MESH
49#if PC_ENABLE_EDGE_MESH
51enum class EdgeFetchPhase : uint8_t
64 EdgeEntry *reval_entry;
65 const EdgeFetchTransport *transport;
70#if PC_ENABLE_EDGE_MESH
93#if PC_ENABLE_EDGE_MESH
110 uint16_t out_off, out_len;
116struct EdgeCacheProxyCtx
120 EdgeCacheStore store;
125 EdgeFetchTransport transport;
126#if PC_ENABLE_EDGE_ORIGIN_TLS
127 EdgeFetchTransport transport_tls;
129 bool tls_peer_closed;
142#if PC_ENABLE_EDGE_MESH
146 bool mesh_registered;
149EdgeCacheProxyCtx s_ctx;
153void edge_on_evict(
void *ctx,
const EdgeEntry *victim)
156 if (s_ctx.l2 && edge_sd_put(s_ctx.l2, victim, s_ctx.sd_buf,
sizeof(s_ctx.sd_buf)))
158 s_ctx.store.stats.l2_spills++;
164int t_open(
void *c,
const char *host, uint16_t port, uint32_t timeout)
169bool t_send(
void *c,
int cid,
const void *d,
size_t l)
174size_t t_read(
void *c,
int cid, uint8_t *b,
size_t cap)
179bool t_closed(
void *c,
int cid)
184void t_close(
void *c,
int cid)
190#if PC_ENABLE_EDGE_ORIGIN_TLS
195int edge_tls_bio_send(
void *ctx,
const unsigned char *buf,
size_t len)
198 size_t cap = len > 0xFFFF ? 0xFFFF : len;
199 return pc_client_send(s_ctx.tls_cid, buf, cap) ? (int)cap : MBEDTLS_ERR_SSL_WANT_WRITE;
201int edge_tls_bio_recv(
void *ctx,
unsigned char *buf,
size_t len)
212int t_tls_open(
void *c,
const char *host, uint16_t port, uint32_t timeout)
216 if (s_ctx.tls_cid < 0)
220 s_ctx.tls_peer_closed =
false;
221 if (!pc_tls_client_session_begin(host, edge_tls_bio_send, edge_tls_bio_recv))
231 uint32_t deadline =
pc_millis() + timeout;
233 while ((h = pc_tls_client_session_handshake()) == 0 && !
pc_client_is_closed(s_ctx.tls_cid) &&
240 pc_tls_client_session_end();
245 return s_ctx.tls_cid;
247bool t_tls_send(
void *c,
int cid,
const void *d,
size_t l)
251 return pc_tls_client_session_write((
const uint8_t *)d, l) == (int)l;
253size_t t_tls_read(
void *c,
int cid, uint8_t *b,
size_t cap)
257 int n = pc_tls_client_session_read(b, cap);
260 s_ctx.tls_peer_closed =
true;
262 return n > 0 ? (size_t)n : 0;
264bool t_tls_closed(
void *c,
int cid)
269void t_tls_close(
void *c,
int cid)
272 pc_tls_client_session_end();
279const char *req_lookup(
void *ctx,
const char *name)
284EdgeRouteMap *map_match(
const char *path)
288 if (!s_ctx.maps[i].used)
292 size_t pl = strnlen(s_ctx.maps[i].prefix,
sizeof(s_ctx.maps[i].prefix));
293 if (strncmp(path, s_ctx.maps[i].prefix, pl) == 0)
295 return &s_ctx.maps[i];
305 if (!s_ctx.fetches[i].used)
315size_t edge_chunk_source(uint8_t *buf,
size_t cap,
void *ctx)
317 EdgeServeCursor *c = (EdgeServeCursor *)ctx;
318 if (!c->active || !c->entry)
322 size_t remaining = c->end - c->off;
326 if (c->entry->key[0] ==
'\0')
328 edge_store_free_entry(&s_ctx.store, c->entry);
333 size_t n = remaining < cap ? remaining : cap;
334 memcpy(buf, c->entry->body + c->off, n);
341void serve_hit(uint8_t slot, EdgeEntry *e, uint32_t now,
const char *xcache)
343 EdgeServeCursor *c = &s_ctx.serve[slot];
347 c->end = e->body_len;
351 const char *range = s_ctx.range_hdr[slot];
356 int rr = http_parse_byte_range(range, e->body_len, &rs, &re);
360 pc_sb sb_cr = {cr,
sizeof(cr), 0,
true};
362 pc_sb_u32(&sb_cr, (uint32_t)((
unsigned)e->body_len));
367 s_ctx.server->add_response_header(slot,
"Content-Range", cr);
370 s_ctx.server->send(slot, 416, PC_MIME_TEXT_PLAIN,
"Range Not Satisfiable");
376 c->off = (uint32_t)rs;
377 c->end = (uint32_t)re + 1;
379 pc_sb sb_cr2 = {cr,
sizeof(cr), 0,
true};
381 pc_sb_u32(&sb_cr2, (uint32_t)((
unsigned)rs));
383 pc_sb_u32(&sb_cr2, (uint32_t)((
unsigned)re));
385 pc_sb_u32(&sb_cr2, (uint32_t)((
unsigned)e->body_len));
390 s_ctx.server->add_response_header(slot,
"Content-Range", cr);
393 s_ctx.server->add_response_header(slot,
"Accept-Ranges",
"bytes");
396 s_ctx.server->add_response_header(slot,
"X-Cache", xcache);
399 s_ctx.server->add_response_header(slot,
"ETag", e->etag);
401 if (e->last_modified[0])
403 s_ctx.server->add_response_header(slot,
"Last-Modified", e->last_modified);
405 if (e->content_encoding[0])
407 s_ctx.server->add_response_header(slot,
"Content-Encoding", e->content_encoding);
409 long age = edge_current_age(e->initial_age, e->insert_ms, now);
415 pc_sb sb_agebuf = {agebuf,
sizeof(agebuf), 0,
true};
421 s_ctx.server->add_response_header(slot,
"Age", agebuf);
422 const char *ct = e->content_type[0] ? e->content_type :
"application/octet-stream";
423 s_ctx.server->send_chunked(slot, status, ct, edge_chunk_source, c);
428void serve_passthrough(uint8_t slot, EdgeFetch *f)
430 EdgeEntry *e = edge_store_alloc(&s_ctx.store,
"",
"");
433 s_ctx.server->send(slot, 502, PC_MIME_TEXT_PLAIN,
"Bad Gateway");
436 s_ctx.store.stats.stores--;
437 e->status = f->status;
438 if (!edge_header_value((
const char *)f->buf, f->head_len,
"Content-Type", e->content_type,
sizeof(e->content_type)))
440 strncpy(e->content_type,
"application/octet-stream",
sizeof(e->content_type) - 1);
442 edge_header_value((
const char *)f->buf, f->head_len,
"Content-Encoding", e->content_encoding,
443 sizeof(e->content_encoding));
444 size_t bl = f->body_len;
449 memcpy(e->body, f->buf + f->body_off, bl);
450 e->body_len = (uint16_t)bl;
452 EdgeServeCursor *c = &s_ctx.serve[slot];
456 c->end = e->body_len;
457 s_ctx.server->add_response_header(slot,
"X-Cache",
"MISS");
458 if (e->content_encoding[0])
460 s_ctx.server->add_response_header(slot,
"Content-Encoding", e->content_encoding);
462 const char *ct = e->content_type[0] ? e->content_type :
"application/octet-stream";
463 s_ctx.server->send_chunked(slot, e->status ? e->status : 200, ct, edge_chunk_source, c);
467void store_response(uint8_t slot, EdgeFetchSlot *fs,
HttpReq *req,
const pc_cache_control *cc,
const char *vary_hdr,
470 EdgeFetch *f = &fs->f;
471 const char *head = (
const char *)f->buf;
472 size_t head_len = f->head_len;
475 edge_vary_serialize(vary_hdr[0] ? vary_hdr : nullptr, req_lookup, req, vary_vals, sizeof(vary_vals));
477 EdgeEntry *e = edge_store_alloc(&s_ctx.store, fs->canon, vary_vals);
480 serve_passthrough(slot, f);
484 edge_header_value(head, head_len,
"Content-Type", e->content_type,
sizeof(e->content_type));
485 edge_header_value(head, head_len,
"Content-Encoding", e->content_encoding,
sizeof(e->content_encoding));
486 edge_header_value(head, head_len,
"ETag", e->etag,
sizeof(e->etag));
487 edge_header_value(head, head_len,
"Last-Modified", e->last_modified,
sizeof(e->last_modified));
488 size_t vhl = strnlen(vary_hdr,
sizeof(e->vary_names));
489 if (vary_hdr[0] && vhl <
sizeof(e->vary_names))
491 memcpy(e->vary_names, vary_hdr, vhl + 1);
494 size_t bl = f->body_len;
499 memcpy(e->body, f->buf + f->body_off, bl);
500 e->body_len = (uint16_t)bl;
501 s_ctx.store.stats.bytes_stored += bl;
504 int64_t expires = -1;
505 int64_t last_mod = -1;
508 if (edge_header_value(head, head_len,
"Date", v,
sizeof(v)))
510 date = edge_parse_http_date(v, strnlen(v,
sizeof(v)));
512 if (edge_header_value(head, head_len,
"Expires", v,
sizeof(v)))
514 expires = edge_parse_http_date(v, strnlen(v,
sizeof(v)));
516 if (e->last_modified[0])
518 last_mod = edge_parse_http_date(e->last_modified, strnlen(e->last_modified,
sizeof(e->last_modified)));
520 if (edge_header_value(head, head_len,
"Age", v,
sizeof(v)))
523 for (
const char *p = v; *p >=
'0' && *p <=
'9'; p++)
525 a = a * 10 + (*p -
'0');
529 edge_entry_set_freshness(e, cc,
true, date, expires, last_mod, age, -1, now);
530 serve_hit(slot, e, now,
"MISS");
534void on_fetch_done(uint8_t slot, EdgeFetchSlot *fs, uint32_t now)
536 EdgeFetch *f = &fs->f;
537 const char *head = (
const char *)f->buf;
538 size_t head_len = f->head_len;
541 if (fs->revalidate && f->status == 304 && fs->reval_entry)
543 edge_apply_304(fs->reval_entry, head, head_len, -1, now);
544 s_ctx.store.stats.revalidations_304++;
545 serve_hit(slot, fs->reval_entry, now,
"REVALIDATED");
548 if (f->status == 200)
551 cache_control_init(&cc);
553 if (edge_header_value(head, head_len,
"Cache-Control", v,
sizeof(v)))
555 cache_control_parse(v, strnlen(v,
sizeof(v)), &cc);
559 edge_header_value(head, head_len,
"Vary", vary_hdr,
sizeof(vary_hdr));
560 if (edge_is_storeable(200,
"GET", &cc, vary_hdr[0] ? vary_hdr : nullptr, f->body_len))
562 if (fs->revalidate && fs->reval_entry)
564 edge_store_free_entry(&s_ctx.store, fs->reval_entry);
565 s_ctx.store.stats.replaces_200++;
567 store_response(slot, fs, req, &cc, vary_hdr, now);
570 serve_passthrough(slot, f);
573 serve_passthrough(slot, f);
578bool edge_cache_poll(uint8_t slot);
583bool begin_origin_fetch(EdgeFetchSlot *fs, uint32_t now)
585 EdgeRouteMap *m = fs->route;
586 const EdgeFetchTransport *tport = &s_ctx.transport;
587#if PC_ENABLE_EDGE_ORIGIN_TLS
590 if (pc_tls_client_session_active())
594 tport = &s_ctx.transport_tls;
601 edge_build_conditional(fs->reval_entry, cond,
sizeof(cond));
603 pc_sb sb_reqbuf = {s_ctx.reqbuf,
sizeof(s_ctx.reqbuf), 0,
true};
606 pc_sb_put(&sb_reqbuf, fs->query[0] ?
"?" :
"");
608 pc_sb_put(&sb_reqbuf,
" HTTP/1.1\r\nHost: ");
610 pc_sb_put(&sb_reqbuf,
"\r\nUser-Agent: PC-EdgeCache\r\nConnection: close\r\n");
614 if (rl <= 0 || (
size_t)rl >=
sizeof(s_ctx.reqbuf))
618 edge_fetch_begin(&fs->f, tport, m->origin_host, m->origin_port, s_ctx.reqbuf, (
size_t)rl, now);
619 if (fs->f.st == EdgeFetchStatus::FAILED)
621 edge_fetch_end(&fs->f, tport);
624 fs->transport = tport;
628#if PC_ENABLE_EDGE_MESH
634 if (s_ctx.peers[i].used)
643MeshPeer *mesh_peer_nth(
int n)
647 if (s_ctx.peers[i].used && n-- == 0)
649 return &s_ctx.peers[i];
657void mesh_snapshot_headers(
const HttpReq *req,
char *out,
size_t cap)
667 if (pos + kl + 1 + vl + 1 >= cap)
671 memcpy(out + pos, k, kl);
674 memcpy(out + pos, v, vl);
682#if PC_EDGE_FETCH_BUF < PC_EDGE_MESH_RESP_MAX
683#error "PC_EDGE_FETCH_BUF must hold a mesh response (>= PC_EDGE_MESH_RESP_MAX). It defaults to that "\
684 "floor, so this only fires if you pinned it lower; raise PC_EDGE_FETCH_BUF or lower PC_EDGE_BODY_MAX."
688bool mesh_begin_peer(EdgeFetchSlot *fs, uint32_t now)
690 MeshPeer *p = mesh_peer_nth(fs->peer_idx);
695 edge_mesh_fetch_begin(&fs->mf, &s_ctx.transport, p->host, p->port, fs->mreq, fs->mreq_len, fs->f.buf,
696 sizeof(fs->f.buf), now);
702bool mesh_store_and_serve(uint8_t slot, EdgeFetchSlot *fs, uint32_t now)
704 EdgeEntry *e = edge_store_alloc(&s_ctx.store, fs->canon,
"");
709 if (!edge_mesh_deserialize_entry(fs->mf.buf + fs->mf.entry_off, fs->mf.entry_len, e, now) ||
710 strcmp(e->key, fs->canon) != 0 || !edge_entry_fresh(e, now))
712 edge_store_free_entry(&s_ctx.store, e);
715 s_ctx.store.stats.bytes_stored += e->body_len;
716 serve_hit(slot, e, now,
"MESH");
722bool mesh_advance_or_origin(EdgeFetchSlot *fs, uint32_t now)
725 if (mesh_begin_peer(fs, now))
729 s_ctx.store.stats.mesh_misses++;
730 if (begin_origin_fetch(fs, now))
732 fs->phase = EdgeFetchPhase::ORIGIN;
739bool start_fetch(uint8_t slot,
HttpReq *req, EdgeRouteMap *m,
const char *canon, EdgeEntry *reval, uint32_t now)
741 int fi = alloc_fetch();
746 EdgeFetchSlot *fs = &s_ctx.fetches[fi];
747 fs->client_slot = slot;
748 fs->revalidate = (reval !=
nullptr);
749 fs->reval_entry = reval;
751 memcpy(fs->canon, canon, strnlen(canon,
sizeof(fs->canon) - 1) + 1);
752 strncpy(fs->path, req->
path,
sizeof(fs->path) - 1);
753 fs->path[
sizeof(fs->path) - 1] =
'\0';
754 strncpy(fs->query, req->
query,
sizeof(fs->query) - 1);
755 fs->query[
sizeof(fs->query) - 1] =
'\0';
757#if PC_ENABLE_EDGE_MESH
759 if (!reval && mesh_peer_count() > 0)
763 mesh_snapshot_headers(req, s_ctx.mesh_hdrs,
sizeof(s_ctx.mesh_hdrs));
764 fs->mreq_len = edge_mesh_build_request(digest, canon, s_ctx.mesh_hdrs, fs->mreq,
sizeof(fs->mreq));
766 if (fs->mreq_len > 0 && mesh_begin_peer(fs, now))
768 fs->phase = EdgeFetchPhase::MESH;
770 s_ctx.pending[slot].active =
true;
771 s_ctx.pending[slot].fetch_idx = (uint8_t)fi;
775 fs->phase = EdgeFetchPhase::ORIGIN;
777 if (!begin_origin_fetch(fs, now))
782 s_ctx.pending[slot].active =
true;
783 s_ctx.pending[slot].fetch_idx = (uint8_t)fi;
790EdgeEntry *try_promote_l2(
const char *canon, uint32_t now)
794 EdgeEntry *e = edge_store_alloc(&s_ctx.store, canon,
"");
799 if (!edge_sd_get(s_ctx.l2, digest, e, s_ctx.sd_buf,
sizeof(s_ctx.sd_buf)) || strcmp(e->key, canon) != 0)
801 edge_store_free_entry(&s_ctx.store, e);
806 e->date_epoch = e->expires_epoch = -1;
809 e->last_used_ms = now;
810 s_ctx.store.stats.l2_promotes++;
819 if (!s_ctx.registered || !s_ctx.server || slot >=
MAX_CONNS)
823 bool is_get = strcmp(req->
method,
"GET") == 0;
824 bool is_head = strcmp(req->
method,
"HEAD") == 0;
825 if (!is_get && !is_head)
833 EdgeRouteMap *m = map_match(req->
path);
845 if (edge_key_canon(
"GET", host, req->
path, req->
query,
true, canon,
sizeof(canon)) == 0)
854 strncpy(s_ctx.range_hdr[slot], rh ? rh :
"", sizeof(s_ctx.range_hdr[slot]) - 1);
855 s_ctx.range_hdr[slot][
sizeof(s_ctx.range_hdr[slot]) - 1] =
'\0';
859 EdgeEntry *e = edge_store_find(&s_ctx.store, canon, req_lookup, req, now);
860 if (e && edge_entry_fresh(e, now))
862 s_ctx.store.stats.hits++;
863 serve_hit(slot, e, now,
"HIT");
869 e = try_promote_l2(canon, now);
872 s_ctx.store.stats.misses++;
873 EdgeEntry *reval = (e && edge_entry_has_validator(e)) ? e : nullptr;
874 if (!start_fetch(slot, req, m, canon, reval, now))
883bool edge_cache_poll(uint8_t slot)
885 if (slot >=
MAX_CONNS || !s_ctx.pending[slot].active)
889 uint8_t fi = s_ctx.pending[slot].fetch_idx;
890 EdgeFetchSlot *fs = &s_ctx.fetches[fi];
893#if PC_ENABLE_EDGE_MESH
894 if (fs->phase == EdgeFetchPhase::MESH)
896 if (!pc_conn_active(slot))
898 edge_mesh_fetch_end(&fs->mf, &s_ctx.transport);
900 s_ctx.pending[slot].active =
false;
903 EdgeMeshStatus ms = edge_mesh_fetch_pump(&fs->mf, &s_ctx.transport, now);
904 if (ms == EdgeMeshStatus::PENDING)
908 bool served = (ms == EdgeMeshStatus::HIT) && mesh_store_and_serve(slot, fs, now);
909 edge_mesh_fetch_end(&fs->mf, &s_ctx.transport);
912 s_ctx.store.stats.mesh_hits++;
914 s_ctx.pending[slot].active =
false;
917 if (mesh_advance_or_origin(fs, now))
921 s_ctx.server->send(slot, 502, PC_MIME_TEXT_PLAIN,
"Bad Gateway");
923 s_ctx.pending[slot].active =
false;
928 const EdgeFetchTransport *tport = fs->transport;
929 if (!pc_conn_active(slot))
931 edge_fetch_end(&fs->f, tport);
933 s_ctx.pending[slot].active =
false;
937 EdgeFetchStatus st = edge_fetch_pump(&fs->f, tport, now);
938 if (st == EdgeFetchStatus::PENDING)
943 if (st == EdgeFetchStatus::DONE)
945 on_fetch_done(slot, fs, now);
947 else if (st == EdgeFetchStatus::FAILED && fs->revalidate && fs->reval_entry)
949 serve_hit(slot, fs->reval_entry, now,
"STALE");
953 s_ctx.server->send(slot, 502, PC_MIME_TEXT_PLAIN,
"Bad Gateway");
955 edge_fetch_end(&fs->f, tport);
957 s_ctx.pending[slot].active =
false;
961#if PC_ENABLE_EDGE_MESH
966bool mesh_name_eq(
const char *a,
const char *b,
size_t n)
968 for (
size_t i = 0; i < n; i++)
972 if (ca >=
'A' && ca <=
'Z')
974 ca = (char)(ca -
'A' +
'a');
976 if (cb >=
'A' && cb <=
'Z')
978 cb = (char)(cb -
'A' +
'a');
995const char *mesh_hdr_lookup(
void *ctx,
const char *name)
997 MeshLookupCtx *lc = (MeshLookupCtx *)ctx;
999 const char *p = lc->blob;
1002 const char *rs = strchr(p,
'\x1e');
1007 const char *us = strchr(rs + 1,
'\x1f');
1012 if ((
size_t)(rs - p) == nl && mesh_name_eq(p, name, nl))
1014 size_t vl = (size_t)(us - (rs + 1));
1015 if (vl >=
sizeof(lc->valbuf))
1017 vl =
sizeof(lc->valbuf) - 1;
1019 memcpy(lc->valbuf, rs + 1, vl);
1020 lc->valbuf[vl] =
'\0';
1028MeshConn *mesh_conn_by_slot(uint8_t slot)
1032 if (s_ctx.mesh_conns[i].active && s_ctx.mesh_conns[i].conn_slot == slot)
1034 return &s_ctx.mesh_conns[i];
1041void mesh_answer(MeshConn *mc,
const uint8_t digest[32],
const char *canon, uint32_t now)
1046 if (memcmp(verify, digest, 32) == 0)
1049 lc.blob = s_ctx.mesh_hdrs;
1050 EdgeEntry *e = edge_store_find(&s_ctx.store, canon, mesh_hdr_lookup, &lc, now);
1051 if (e && edge_entry_fresh(e, now))
1053 long age = edge_current_age(e->initial_age, e->insert_ms, now);
1059 size_t fn = edge_mesh_serialize_entry(e, age, mc->outbuf + 6,
sizeof(mc->outbuf) - 6);
1060 if (fn > 0 && fn <= 0xFFFFu)
1062 mc->outbuf[0] = PC_EDGE_MESH_MAGIC0;
1063 mc->outbuf[1] = PC_EDGE_MESH_MAGIC1;
1064 mc->outbuf[2] = PC_EDGE_MESH_VERSION;
1066 mc->outbuf[4] = (uint8_t)(fn & 0xFF);
1067 mc->outbuf[5] = (uint8_t)(fn >> 8);
1068 mc->out_len = (uint16_t)(6 + fn);
1075 mc->out_len = (uint16_t)edge_mesh_build_response(
false,
nullptr, 0, mc->outbuf,
sizeof(mc->outbuf));
1078 mc->responded =
true;
1081void mesh_serve_end(MeshConn *mc)
1088void mesh_serve_pump(MeshConn *mc)
1090 uint8_t slot = mc->conn_slot;
1093 if (pc_conn_available(slot) && mc->req_len <
sizeof(mc->reqbuf))
1095 mc->req_len += (uint16_t)pc_conn_read(slot, mc->reqbuf + mc->req_len,
sizeof(mc->reqbuf) - mc->req_len);
1099 EdgeMeshParse p = edge_mesh_parse_request(mc->reqbuf, mc->req_len, digest, canon,
sizeof(canon),
1100 s_ctx.mesh_hdrs,
sizeof(s_ctx.mesh_hdrs));
1101 if (p == EdgeMeshParse::INCOMPLETE)
1103 if (mc->req_len >=
sizeof(mc->reqbuf))
1109 if (p != EdgeMeshParse::HIT)
1114 mesh_answer(mc, digest, canon,
pc_millis());
1116 while (mc->out_off < mc->out_len)
1123 uint16_t remaining = (uint16_t)(mc->out_len - mc->out_off);
1124 u16_t n = remaining < room ? remaining : room;
1129 mc->out_off = (uint16_t)(mc->out_off + n);
1140void mesh_on_accept(uint8_t slot)
1144 if (!s_ctx.mesh_conns[i].active)
1146 MeshConn *mc = &s_ctx.mesh_conns[i];
1148 mc->conn_slot = slot;
1150 mc->responded =
false;
1159void mesh_on_data(uint8_t slot)
1161 MeshConn *mc = mesh_conn_by_slot(slot);
1164 mesh_serve_pump(mc);
1168void mesh_on_poll(uint8_t slot)
1170 if (!pc_conn_active(slot))
1174 MeshConn *mc = mesh_conn_by_slot(slot);
1177 mesh_serve_pump(mc);
1181void mesh_on_close(uint8_t slot)
1183 MeshConn *mc = mesh_conn_by_slot(slot);
1190const ProtoHandler s_mesh_handler = {mesh_on_accept, mesh_on_data, mesh_on_close, mesh_on_poll};
1196void pc_edge_cache_enable(
PC &server)
1198 s_ctx.server = &server;
1199 edge_store_init(&s_ctx.store);
1202 s_ctx.fetches[i].used =
false;
1203 s_ctx.fetches[i].f.cid = -1;
1207 s_ctx.pending[i].active =
false;
1208 s_ctx.serve[i].active =
false;
1209 s_ctx.serve[i].entry =
nullptr;
1211 s_ctx.range_hdr[i][0] =
'\0';
1214 s_ctx.transport.open = t_open;
1215 s_ctx.transport.send = t_send;
1216 s_ctx.transport.read = t_read;
1217 s_ctx.transport.closed = t_closed;
1218 s_ctx.transport.close = t_close;
1219 s_ctx.transport.ctx =
nullptr;
1220#if PC_ENABLE_EDGE_ORIGIN_TLS
1221 s_ctx.transport_tls.open = t_tls_open;
1222 s_ctx.transport_tls.send = t_tls_send;
1223 s_ctx.transport_tls.read = t_tls_read;
1224 s_ctx.transport_tls.closed = t_tls_closed;
1225 s_ctx.transport_tls.close = t_tls_close;
1226 s_ctx.transport_tls.ctx =
nullptr;
1228 s_ctx.tls_peer_closed =
false;
1231 s_ctx.store.on_evict = s_ctx.l2 ? edge_on_evict :
nullptr;
1232 s_ctx.store.evict_ctx =
nullptr;
1234#if PC_ENABLE_EDGE_MESH
1237 s_ctx.mesh_conns[i].active =
false;
1240 if (!s_ctx.registered)
1242 server.
use(edge_cache_mw);
1243 pc_http_set_edge_poll(edge_cache_poll);
1244 s_ctx.registered =
true;
1249void pc_edge_cache_bind_sd(pc_dbm *dbm)
1252 s_ctx.store.on_evict = dbm ? edge_on_evict :
nullptr;
1253 s_ctx.store.evict_ctx =
nullptr;
1257bool pc_edge_cache_map(
const char *path_prefix,
const char *origin_base_url)
1259 if (!path_prefix || !origin_base_url)
1263 if (strnlen(path_prefix,
sizeof(s_ctx.maps[0].prefix)) >=
sizeof(s_ctx.maps[0].prefix))
1270 char ignore_path[256];
1271 if (!http_client_parse_url(origin_base_url, &https, host,
sizeof(host), &port, ignore_path,
sizeof(ignore_path)))
1275#if !PC_ENABLE_EDGE_ORIGIN_TLS
1283 if (s_ctx.maps[i].used)
1287 strncpy(s_ctx.maps[i].prefix, path_prefix,
sizeof(s_ctx.maps[i].prefix) - 1);
1288 s_ctx.maps[i].prefix[
sizeof(s_ctx.maps[i].prefix) - 1] =
'\0';
1289 strncpy(s_ctx.maps[i].origin_host, host,
sizeof(s_ctx.maps[i].origin_host) - 1);
1290 s_ctx.maps[i].origin_host[
sizeof(s_ctx.maps[i].origin_host) - 1] =
'\0';
1291 s_ctx.maps[i].origin_port = port;
1292 s_ctx.maps[i].https = https;
1293 s_ctx.maps[i].used =
true;
1299#if PC_ENABLE_EDGE_ORIGIN_TLS
1300void pc_edge_cache_set_origin_ca(
const uint8_t *ca_pem,
size_t len)
1302 pc_tls_client_set_ca(ca_pem, len);
1304void pc_edge_cache_set_origin_pin(
const uint8_t sha256[32])
1306 pc_tls_client_set_pin(sha256);
1310#if PC_ENABLE_EDGE_MESH
1311bool pc_edge_cache_add_peer(
const char *host, uint16_t port)
1324 if (!s_ctx.peers[i].used)
1326 memcpy(s_ctx.peers[i].host, host, hl + 1);
1327 s_ctx.peers[i].port = port;
1328 s_ctx.peers[i].used =
true;
1335void pc_edge_cache_mesh_serve(
void)
1337 if (!s_ctx.mesh_registered)
1340 s_ctx.mesh_registered =
true;
1345void pc_edge_cache_reset(
void)
1347 edge_store_init(&s_ctx.store);
1351 edge_sd_purge_all(s_ctx.l2);
1352 s_ctx.store.on_evict = edge_on_evict;
1357 s_ctx.maps[i].used =
false;
1359#if PC_ENABLE_EDGE_MESH
1362 s_ctx.peers[i].used =
false;
1367bool pc_edge_cache_purge(
const char *canonical_key)
1373 bool purged = edge_store_purge(&s_ctx.store, canonical_key) > 0;
1378 edge_key_digest(canonical_key, strnlen(canonical_key,
PC_EDGE_KEY_MAX), digest);
1379 if (edge_sd_del(s_ctx.l2, digest))
1388uint32_t pc_edge_cache_purge_prefix(
const char *path_prefix)
1394 uint32_t n = edge_store_purge_prefix(&s_ctx.store, path_prefix);
1398 n += edge_sd_purge_prefix(s_ctx.l2, path_prefix, s_ctx.sd_buf,
sizeof(s_ctx.sd_buf));
1404void pc_edge_cache_stats(EdgeCacheStats *out)
1408 *out = s_ctx.store.stats;
Single-port HTTP server with deterministic, zero-allocation execution.
void use(Middleware mw)
Register a middleware to run before every request is dispatched.
#define PC_EDGE_FETCH_SLOTS
#define PC_MESH_MAX_CONNS
#define PC_MESH_MAX_PEERS
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int pc_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
CDN edge-cache tier - server glue (PC_ENABLE_EDGE_CACHE).
CDN edge-cache tier - L2 SD persistence (PC_ENABLE_EDGE_CACHE && PC_ENABLE_DBM).
CDN edge-cache tier - async origin-fetch engine (PC_ENABLE_EDGE_CACHE).
CDN edge-cache tier - mesh (sibling-cache) wire codec + async peer-query engine (PC_ENABLE_EDGE_MESH)...
Zero-heap outbound HTTP(S) client over raw lwIP (PC_ENABLE_HTTP_CLIENT).
const char * http_get_header(const HttpReq *req, const char *key)
Look up a header value by name (case-insensitive).
HttpReq http_pool[CONN_POOL_SLOTS]
Pool of parser contexts, one per connection-pool slot (incl. reserved dispatch slots).
Standalone HTTP/1.1 request parser - no transport dependency.
Shared single-range Range: bytes=... parser (RFC 7233), used by static file serving and the edge cach...
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Layer 6 (Presentation) - wires the transport ring buffer to the HTTP parser.
Layer 5 (Session) - per-protocol connection handler dispatch table.
void proto_register(ConnProto proto, const ProtoHandler *h)
Register h for protocol proto (replaces any previous handler).
Layer 7 (Application) - public HTTP routing API.
MwResult
Outcome of a middleware function (see Middleware).
@ MW_HALT
Stop dispatch; the middleware already sent a response.
@ MW_NEXT
Continue to the next middleware / the route handler.
#define MAX_VAL_LEN
Maximum header field-value length.
@ PROTO_MESH
Edge-cache sibling link (PC_ENABLE_EDGE_MESH): answers a peer's content-addressed query.
#define MAX_QUERY_LEN
Maximum raw query-string length (everything after ?).
#define PC_EDGE_MESH_RESP_MAX
Worst-case mesh response frame (header + entry on a HIT).
#define PC_EDGE_ORIGIN_URL_MAX
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define PC_EDGE_SD_VALUE_MAX
Worst-case serialized L2 entry (edge_sd_serialize).
#define MAX_KEY_LEN
Maximum header field-name length (e.g. "Content-Type").
#define PC_EDGE_MESH_REQ_MAX
Worst-case mesh request frame (bounded request-header snapshot for Vary matching).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_i64(pc_sb *b, int64_t v)
Append v as signed decimal (64-bit), with a leading '-' when negative.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Fully-parsed HTTP/1.1 request.
Header headers[MAX_HEADERS]
Captured header fields.
char query[MAX_QUERY_LEN]
Raw query string (after ?).
uint8_t header_count
Valid entries in headers[].
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
char method[PC_METHOD_BUF_SIZE]
HTTP method, null-terminated (OPTIONS, or WebDAV methods when enabled).
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
Bump-append target; ok latches false once an append would overflow cap.
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
u16_t pc_conn_sndbuf(uint8_t slot)
Bytes that can currently be queued for sending on slot.
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
void pc_conn_begin_close(uint8_t slot_id)
Begin a graceful close that dwells in ConnState::CONN_CLOSING until the peer ACKs.
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).