DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
edge_fetch.cpp
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_fetch.cpp
6 * @brief CDN edge-cache tier - async origin-fetch engine. See edge_fetch.h.
7 */
8
10
11#if DETWS_ENABLE_EDGE_CACHE
12
13#include "services/edge_cache/edge_cache.h" // edge_header_value
14#include "services/http_client/http_client.h" // http_client_parse_response
15#include <string.h>
16
17namespace
18{
19// Offset just past the CRLFCRLF header terminator, or 0 if the header block is not complete.
20size_t head_end(const uint8_t *b, size_t n)
21{
22 for (size_t i = 0; i + 3 < n; i++)
23 if (b[i] == '\r' && b[i + 1] == '\n' && b[i + 2] == '\r' && b[i + 3] == '\n')
24 return i + 4;
25 return 0;
26}
27
28bool hex_val(uint8_t c, int *v)
29{
30 if (c >= '0' && c <= '9')
31 {
32 *v = c - '0';
33 return true;
34 }
35 c |= 0x20;
36 if (c >= 'a' && c <= 'f')
37 {
38 *v = c - 'a' + 10;
39 return true;
40 }
41 return false;
42}
43
44// True if the chunked body @p b[0..n) reaches its terminating zero-length chunk + trailer CRLF.
45bool chunked_complete(const uint8_t *b, size_t n)
46{
47 size_t i = 0;
48 for (;;)
49 {
50 size_t sz = 0;
51 size_t j = i;
52 bool any = false;
53 int v = 0;
54 while (j < n && hex_val(b[j], &v))
55 {
56 sz = sz * 16 + (size_t)v;
57 j++;
58 any = true;
59 }
60 if (!any)
61 return false;
62 while (j < n && b[j] != '\n') // skip chunk extensions to the size-line LF
63 j++;
64 if (j >= n)
65 return false;
66 j++; // past LF
67 if (sz == 0)
68 {
69 // trailer field lines until an empty line (CRLF) closes the message
70 for (;;)
71 {
72 if (j + 1 < n && b[j] == '\r' && b[j + 1] == '\n')
73 return true;
74 size_t k = j;
75 while (k < n && b[k] != '\n')
76 k++;
77 if (k >= n)
78 return false;
79 j = k + 1;
80 }
81 }
82 size_t next = j + sz + 2; // chunk data + trailing CRLF
83 if (next > n)
84 return false;
85 i = next;
86 }
87}
88
89// Case-insensitive "does @p s contain 'chunked'".
90bool has_chunked(const char *s)
91{
92 char low[40];
93 size_t i = 0;
94 for (; s[i] && i + 1 < sizeof(low); i++)
95 low[i] = (s[i] >= 'A' && s[i] <= 'Z') ? (char)(s[i] + 32) : s[i];
96 low[i] = '\0';
97 return strstr(low, "chunked") != nullptr;
98}
99} // namespace
100
101bool edge_resp_complete(const uint8_t *buf, size_t len, bool conn_closed, size_t *head_len)
102{
103 size_t h = head_end(buf, len);
104 *head_len = h;
105 if (h == 0)
106 return conn_closed; // no whole header block yet (a closed peer ends the wait)
107 char v[24];
108 if (edge_header_value((const char *)buf, h, "Content-Length", v, sizeof(v)))
109 {
110 size_t cl = 0;
111 bool any = false;
112 for (const char *p = v; *p >= '0' && *p <= '9'; p++)
113 {
114 cl = cl * 10 + (size_t)(*p - '0');
115 any = true;
116 }
117 if (any)
118 return len >= h + cl;
119 }
120 char te[40];
121 if (edge_header_value((const char *)buf, h, "Transfer-Encoding", te, sizeof(te)) && has_chunked(te))
122 return chunked_complete(buf + h, len - h);
123 return conn_closed; // close-delimited body
124}
125
126void edge_fetch_begin(EdgeFetch *f, const EdgeFetchTransport *t, const char *host, uint16_t port, const void *request,
127 size_t req_len, uint32_t now_ms)
128{
129 memset(f, 0, sizeof(*f));
130 f->cid = -1;
131 f->start_ms = now_ms;
132 f->st = EdgeFetchStatus::PENDING;
133 f->cid = t->open(t->ctx, host, port, DETWS_EDGE_FETCH_TIMEOUT_MS);
134 if (f->cid < 0)
135 {
136 f->st = EdgeFetchStatus::FAILED;
137 return;
138 }
139 if (!t->send(t->ctx, f->cid, request, req_len))
140 f->st = EdgeFetchStatus::FAILED;
141}
142
143EdgeFetchStatus edge_fetch_pump(EdgeFetch *f, const EdgeFetchTransport *t, uint32_t now_ms)
144{
145 if (f->st != EdgeFetchStatus::PENDING)
146 return f->st;
147
148 while (f->got < sizeof(f->buf))
149 {
150 size_t n = t->read(t->ctx, f->cid, f->buf + f->got, sizeof(f->buf) - f->got);
151 if (n == 0)
152 break;
153 f->got += n;
154 }
155 bool closed = t->closed(t->ctx, f->cid);
156
157 size_t hl = 0;
158 if (edge_resp_complete(f->buf, f->got, closed, &hl))
159 {
160 size_t bo = 0;
161 size_t bl = 0;
162 int status = http_client_parse_response(f->buf, f->got, &bo, &bl);
163 if (status < 0)
164 {
165 f->st = EdgeFetchStatus::FAILED;
166 return f->st;
167 }
168 f->status = status;
169 f->head_len = hl;
170 f->body_off = bo;
171 f->body_len = bl;
172 f->st = EdgeFetchStatus::DONE;
173 return f->st;
174 }
175 if (f->got >= sizeof(f->buf)) // full but not complete -> too big to cache
176 {
177 f->st = EdgeFetchStatus::OVERSIZE;
178 return f->st;
179 }
180 if (closed) // origin closed before a complete response
181 {
182 f->st = EdgeFetchStatus::FAILED;
183 return f->st;
184 }
185 if ((uint32_t)(now_ms - f->start_ms) >= DETWS_EDGE_FETCH_TIMEOUT_MS)
186 {
187 f->st = EdgeFetchStatus::FAILED;
188 return f->st;
189 }
190 return EdgeFetchStatus::PENDING;
191}
192
193void edge_fetch_end(EdgeFetch *f, const EdgeFetchTransport *t)
194{
195 if (f->cid >= 0)
196 {
197 t->close(t->ctx, f->cid);
198 f->cid = -1;
199 }
200}
201
202#endif // DETWS_ENABLE_EDGE_CACHE
#define DETWS_EDGE_FETCH_TIMEOUT_MS
CDN edge-cache tier - pure engine (DETWS_ENABLE_EDGE_CACHE).
CDN edge-cache tier - async origin-fetch engine (DETWS_ENABLE_EDGE_CACHE).
Zero-heap outbound HTTP(S) client over raw lwIP (DETWS_ENABLE_HTTP_CLIENT).