cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
lwip.c
Go to the documentation of this file.
1
26#include <cwist/sys/port/lwip.h>
27
29#include <cwist/net/http/http.h>
30
31#include "lwip/opt.h"
32#include "lwip/sys.h"
33#include "lwip/tcp.h"
34
35#include <stdio.h>
36#include <string.h>
37#include <strings.h>
38
39#ifdef CWIST_LWIP_TLS
40#include <cwist/sys/port.h>
41
42#include "mbedtls/ctr_drbg.h"
43#include "mbedtls/pk.h"
44#include "mbedtls/ssl.h"
45#include "mbedtls/x509_crt.h"
46
47/* Received ciphertext not yet consumed by mbedTLS: one full record plus slack. */
48#define CWIST_LWIP_TLS_IN_MAX (MBEDTLS_SSL_IN_CONTENT_LEN + 2048)
49#endif
50
51/* lwip/opt.h always defines both; testing for it keeps tools that analyze
52 * this file without the firmware's lwIP tree (cppcheck) off the #error. */
53#if defined(LWIP_TCP) && (!LWIP_TCP || !LWIP_CALLBACK_API)
54#error "cwist lwIP adapter needs LWIP_TCP and LWIP_CALLBACK_API"
55#endif
56
57/* tcp_poll interval in TCP coarse-timer ticks (TCP_TMR_INTERVAL * 2 = 500 ms). */
58#define CWIST_LWIP_POLL_TICKS 2
59
60static const char CWIST_LWIP_100_CONTINUE[] = "HTTP/1.1 100 Continue\r\n\r\n";
61
63
64#ifdef CWIST_LWIP_TLS
65struct cwist_lwip_tls {
70};
71#endif
72
73struct cwist_lwip_server {
74 cwist_app *app;
76 struct tcp_pcb *listen_pcb;
77 cwist_lwip_conn *conns;
78 size_t nconns;
79};
80
81struct cwist_lwip_conn {
83 struct tcp_pcb *pcb;
84 cwist_lwip_conn *next;
85
86 char *rx;
87 size_t rx_len;
88 size_t rx_cap;
89
90 char *tx; /* serialized response; NULL when idle */
91 size_t tx_len;
92 size_t tx_queued; /* bytes handed to tcp_write */
93 size_t tx_acked; /* bytes the peer acknowledged */
94 size_t ack_skip; /* acked bytes that belong to an interim 100 */
95
96 bool close_after_tx; /* Connection: close, error reply, or peer FIN */
97 bool peer_closed;
98 bool continue_sent;
99 u32_t last_active_ms;
100
101#ifdef CWIST_LWIP_TLS
102 mbedtls_ssl_context *ssl; /* NULL on a plaintext server */
103 char *tin; /* ciphertext queue: [tin_off, tin_len) unread */
104 size_t tin_off;
105 size_t tin_len;
106 size_t tin_cap;
107 bool tcp_fin; /* TCP FIN seen; the TLS stream ends once tin drains */
108 bool handshake_done;
109 bool tx_static; /* tx is the static 100 Continue, not a heap buffer */
110#endif
111};
112
118
119/* --- framing ---------------------------------------------------------------- */
120
121static const char *find_crlfcrlf(const char *buf, size_t len) {
122 for (size_t i = 0; i + 3 < len; i++) {
123 if (buf[i] == '\r' && buf[i + 1] == '\n' && buf[i + 2] == '\r' && buf[i + 3] == '\n')
124 return buf + i;
125 }
126 return NULL;
127}
128
129static bool header_is(const char *name, size_t name_len, const char *want) {
130 size_t n = strlen(want);
131 return name_len == n && strncasecmp(name, want, n) == 0;
132}
133
134/* Trim optional whitespace (RFC 9110 5.6.3) around a field value. */
135static void trim_ows(const char **v, size_t *len) {
136 while (*len > 0 && (**v == ' ' || **v == '\t')) (*v)++, (*len)--;
137 while (*len > 0 && ((*v)[*len - 1] == ' ' || (*v)[*len - 1] == '\t')) (*len)--;
138}
139
145static frame_result frame_request(const cwist_lwip_conn *c, size_t *req_len, int *status,
146 bool *expect_continue) {
147 const cwist_lwip_config *cfg = &c->srv->cfg;
148 *expect_continue = false;
149 const char *end = find_crlfcrlf(c->rx, c->rx_len);
150 if (!end) {
151 if (c->rx_len > cfg->max_header_bytes) {
153 return FRAME_ERROR;
154 }
155 return FRAME_NEED_MORE;
156 }
157 size_t head_len = (size_t)(end - c->rx) + 4;
158 if (head_len > cfg->max_header_bytes) {
160 return FRAME_ERROR;
161 }
162
163 bool cl_seen = false, te_seen = false;
164 size_t cl = 0;
165 const char *line = memchr(c->rx, '\n', head_len);
166 while (line && (size_t)(line + 1 - c->rx) < head_len - 2) {
167 line++;
168 const char *eol = memchr(line, '\r', (size_t)(end + 2 - line));
169 if (!eol) break;
170 const char *colon = memchr(line, ':', (size_t)(eol - line));
171 if (colon) {
172 size_t name_len = (size_t)(colon - line);
173 const char *v = colon + 1;
174 size_t vlen = (size_t)(eol - v);
175 trim_ows(&v, &vlen);
176 if (header_is(line, name_len, "Content-Length")) {
177 size_t n = 0;
178 if (vlen == 0) goto bad_request;
179 for (size_t i = 0; i < vlen; i++) {
180 if (v[i] < '0' || v[i] > '9' || n > (SIZE_MAX - 9) / 10) goto bad_request;
181 n = n * 10 + (size_t)(v[i] - '0');
182 }
183 if (cl_seen && n != cl) goto bad_request;
184 cl_seen = true;
185 cl = n;
186 } else if (header_is(line, name_len, "Transfer-Encoding")) {
187 te_seen = true;
188 } else if (header_is(line, name_len, "Expect")) {
189 *expect_continue = vlen == 12 && strncasecmp(v, "100-continue", 12) == 0;
190 }
191 }
192 line = memchr(eol, '\n', (size_t)(end + 4 - eol));
193 }
194
195 if (te_seen) {
196 if (cl_seen) goto bad_request;
197 *status = CWIST_HTTP_NOT_IMPLEMENTED; /* chunked request bodies are not reassembled */
198 return FRAME_ERROR;
199 }
200 if (cl > cfg->max_body_bytes) {
202 return FRAME_ERROR;
203 }
204 if (c->rx_len - head_len < cl) return FRAME_NEED_MORE;
205 *req_len = head_len + cl;
206 return FRAME_COMPLETE;
207
209 *status = CWIST_HTTP_BAD_REQUEST;
210 return FRAME_ERROR;
211}
212
213/* --- connection lifecycle ---------------------------------------------------- */
214
215static void conn_unlink(cwist_lwip_conn *c) {
216 cwist_lwip_server *srv = c->srv;
217 for (cwist_lwip_conn **pp = &srv->conns; *pp; pp = &(*pp)->next) {
218 if (*pp == c) {
219 *pp = c->next;
220 srv->nconns--;
221 break;
222 }
223 }
224}
225
226static void conn_free(cwist_lwip_conn *c) {
227 conn_unlink(c);
228 cwist_free(c->rx);
229#ifdef CWIST_LWIP_TLS
230 if (!c->tx_static) cwist_free(c->tx);
231 if (c->ssl) {
232 mbedtls_ssl_free(c->ssl);
233 cwist_free(c->ssl);
234 }
235 cwist_free(c->tin);
236#else
237 cwist_free(c->tx);
238#endif
239 cwist_free(c);
240}
241
242/* Grow rx to hold @p need bytes. @return false on allocation failure. */
243static bool rx_reserve(cwist_lwip_conn *c, size_t need, size_t limit) {
244 if (need <= c->rx_cap) return true;
245 size_t cap = c->rx_cap ? c->rx_cap : 512;
246 while (cap < need) cap *= 2;
247 if (cap > limit + 1) cap = limit + 1;
248 char *grown = cwist_realloc(c->rx, cap);
249 if (!grown) return false;
250 c->rx = grown;
251 c->rx_cap = cap;
252 return true;
253}
254
255static void conn_detach_pcb(cwist_lwip_conn *c) {
256 if (!c->pcb) return;
257 tcp_arg(c->pcb, NULL);
258 tcp_recv(c->pcb, NULL);
259 tcp_sent(c->pcb, NULL);
260 tcp_err(c->pcb, NULL);
261 tcp_poll(c->pcb, NULL, 0);
262}
263
264/* Abort: lwIP frees the pcb; callers inside a pcb callback return ERR_ABRT. */
265static void conn_abort(cwist_lwip_conn *c) {
266 struct tcp_pcb *pcb = c->pcb;
267 conn_detach_pcb(c);
268 c->pcb = NULL;
269 conn_free(c);
270 if (pcb) tcp_abort(pcb);
271}
272
273/* Graceful close; falls back to abort when lwIP is out of memory.
274 * @return ERR_OK, or ERR_ABRT when the pcb was aborted. */
275static err_t conn_close(cwist_lwip_conn *c) {
276 struct tcp_pcb *pcb = c->pcb;
277 conn_detach_pcb(c);
278 c->pcb = NULL;
279 conn_free(c);
280 if (pcb && tcp_close(pcb) != ERR_OK) {
281 tcp_abort(pcb);
282 return ERR_ABRT;
283 }
284 return ERR_OK;
285}
286
287/* --- sending ------------------------------------------------------------------ */
288
289/* Hand as much of tx to lwIP as the send buffer allows.
290 * @return ERR_OK, or ERR_ABRT when the connection was aborted. */
291#ifdef CWIST_LWIP_TLS
292static err_t tls_push(cwist_lwip_conn *c);
293#endif
294
295static err_t conn_push(cwist_lwip_conn *c) {
296#ifdef CWIST_LWIP_TLS
297 if (c->ssl) return tls_push(c);
298#endif
299 while (c->tx && c->tx_queued < c->tx_len) {
300 size_t room = tcp_sndbuf(c->pcb);
301 if (room == 0) break;
302 size_t n = c->tx_len - c->tx_queued;
303 if (n > room) n = room;
304 if (n > 0xFFFF) n = 0xFFFF;
305 u8_t flags = (c->tx_queued + n < c->tx_len) ? TCP_WRITE_FLAG_MORE : 0;
306 err_t err = tcp_write(c->pcb, c->tx + c->tx_queued, (u16_t)n, flags);
307 if (err == ERR_MEM) break; /* retried from tcp_sent / tcp_poll */
308 if (err != ERR_OK) {
309 conn_abort(c);
310 return ERR_ABRT;
311 }
312 c->tx_queued += n;
313 }
314 tcp_output(c->pcb);
315 return ERR_OK;
316}
317
318/* Take ownership of a serialized response and start sending it. */
319static err_t conn_send(cwist_lwip_conn *c, char *buf, size_t len, bool close_after) {
320 c->tx = buf;
321 c->tx_len = len;
322 c->tx_queued = 0;
323 c->tx_acked = 0;
324 c->close_after_tx = c->close_after_tx || close_after;
325 return conn_push(c);
326}
327
328/* Serialize a bodyless-by-default error reply and close after it is sent. */
329static err_t conn_send_error(cwist_lwip_conn *c, int status) {
330 c->rx_len = 0; /* the stream position is unknown past a framing error */
332 char *out = NULL;
333 size_t out_len = 0;
334 if (res) {
335 const char *reason = cwist_http_status_reason(status);
336 res->status_code = (cwist_http_status_t)status;
337 res->keep_alive = false;
339 if (cwist_http_response_serialize(res, &out, &out_len) != 0) out = NULL;
341 }
342 if (!out) {
343 conn_abort(c);
344 return ERR_ABRT;
345 }
346 return conn_send(c, out, out_len, true);
347}
348
349/* Length of the status line + headers + CRLFCRLF in a serialized response. */
350static size_t response_head_len(const char *buf, size_t len) {
351 const char *end = find_crlfcrlf(buf, len);
352 return end ? (size_t)(end - buf) + 4 : len;
353}
354
355/* --- request processing ------------------------------------------------------- */
356
357/* Answer the next complete request in rx, if any and if idle.
358 * @return ERR_OK, or ERR_ABRT when the connection was aborted. */
359static err_t conn_process(cwist_lwip_conn *c) {
360 if (c->tx || !c->pcb) return ERR_OK;
361 if (c->rx_len == 0) {
362 if (c->peer_closed) return conn_close(c);
363 return ERR_OK;
364 }
365
366 size_t req_len = 0;
367 int status = 0;
368 bool expect_continue = false;
369 switch (frame_request(c, &req_len, &status, &expect_continue)) {
370 case FRAME_ERROR:
371 return conn_send_error(c, status);
372 case FRAME_NEED_MORE:
373 if (c->peer_closed) return conn_close(c); /* truncated request */
374 if (expect_continue && !c->continue_sent) {
375 c->continue_sent = true;
376#ifdef CWIST_LWIP_TLS
377 if (c->ssl) { /* must go through the record layer, like any reply */
378 c->tx_static = true;
379 return conn_send(c, (char *)CWIST_LWIP_100_CONTINUE,
380 sizeof(CWIST_LWIP_100_CONTINUE) - 1, false);
381 }
382#endif
383 if (tcp_write(c->pcb, CWIST_LWIP_100_CONTINUE, sizeof(CWIST_LWIP_100_CONTINUE) - 1, 0) ==
384 ERR_OK) {
385 c->ack_skip += sizeof(CWIST_LWIP_100_CONTINUE) - 1;
386 tcp_output(c->pcb);
387 }
388 }
389 return ERR_OK;
390 case FRAME_COMPLETE:
391 break;
392 }
393
395 memmove(c->rx, c->rx + req_len, c->rx_len - req_len);
396 c->rx_len -= req_len;
397 c->continue_sent = false;
398 if (!req) return conn_send_error(c, CWIST_HTTP_BAD_REQUEST);
399
401 if (!res) {
403 return conn_send_error(c, CWIST_HTTP_INTERNAL_ERROR);
404 }
405 cwist_app_dispatch(c->srv->app, req, res);
407
408 bool keep = c->srv->cfg.keep_alive && req->keep_alive && res->keep_alive && !c->peer_closed;
409 bool head = req->method == CWIST_HTTP_HEAD;
411 res->keep_alive = keep;
412
413 char *out = NULL;
414 size_t out_len = 0;
415 int rc = cwist_http_response_serialize(res, &out, &out_len);
417 if (rc != 0 || !out) return conn_send_error(c, CWIST_HTTP_INTERNAL_ERROR);
418 if (head) out_len = response_head_len(out, out_len);
419 return conn_send(c, out, out_len, !keep);
420}
421
422#ifdef CWIST_LWIP_TLS
423/* --- TLS (mbedTLS over the pcb) ----------------------------------------------- */
424
425/* BIO send: copy one chunk of ciphertext into lwIP's send buffer. */
426static int tls_bio_send(void *ctx, const unsigned char *buf, size_t len) {
427 cwist_lwip_conn *c = ctx;
428 if (!c->pcb) return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
429 size_t room = tcp_sndbuf(c->pcb);
430 if (room == 0) return MBEDTLS_ERR_SSL_WANT_WRITE;
431 size_t n = len < room ? len : room;
432 if (n > 0xFFFF) n = 0xFFFF;
434 if (err == ERR_MEM) return MBEDTLS_ERR_SSL_WANT_WRITE; /* segment queue full */
436 return (int)n;
437}
438
439/* BIO recv: hand mbedTLS queued ciphertext; 0 (EOF) once FIN arrived and it drained. */
440static int tls_bio_recv(void *ctx, unsigned char *buf, size_t len) {
441 cwist_lwip_conn *c = ctx;
442 size_t avail = c->tin_len - c->tin_off;
443 if (avail == 0) return c->tcp_fin ? 0 : MBEDTLS_ERR_SSL_WANT_READ;
444 size_t n = len < avail ? len : avail;
445 memcpy(buf, c->tin + c->tin_off, n);
446 c->tin_off += n;
447 if (c->tin_off == c->tin_len) c->tin_off = c->tin_len = 0;
448 return (int)n;
449}
450
451/* Best-effort close_notify, then a graceful TCP close. */
452static err_t tls_close(cwist_lwip_conn *c) {
453 if (c->handshake_done) mbedtls_ssl_close_notify(c->ssl); /* WANT_WRITE: skip it */
454 if (c->pcb) tcp_output(c->pcb);
455 return conn_close(c);
456}
457
458/* tx fully taken by mbedTLS: release it. The next request is read from
459 * tcp_sent (tls_pump), never recursively from here. */
460static err_t tls_tx_done(cwist_lwip_conn *c) {
461 if (!c->tx_static) cwist_free(c->tx);
462 c->tx = NULL;
463 c->tx_static = false;
464 c->tx_len = c->tx_queued = c->tx_acked = 0;
465 if (c->close_after_tx) return tls_close(c);
466 return ERR_OK;
467}
468
469/* Encrypt as much of tx as lwIP's send buffer takes. */
470static err_t tls_push(cwist_lwip_conn *c) {
471 while (c->tx && c->tx_queued < c->tx_len) {
472 int r = mbedtls_ssl_write(c->ssl, (const unsigned char *)c->tx + c->tx_queued,
473 c->tx_len - c->tx_queued);
475 if (r < 0) {
476 conn_abort(c);
477 return ERR_ABRT;
478 }
479 c->tx_queued += (size_t)r; /* retried with the same pointer after WANT_WRITE */
480 }
481 if (c->pcb) tcp_output(c->pcb);
482 if (c->tx && c->tx_queued == c->tx_len) return tls_tx_done(c);
483 return ERR_OK;
484}
485
486/* Advance the handshake, decrypt into rx (within the request limits), and
487 * answer whatever request that completes. */
488static err_t tls_pump(cwist_lwip_conn *c) {
489 if (!c->pcb) return ERR_OK;
490 if (c->tx) return tls_push(c); /* one exchange at a time */
491 if (!c->handshake_done) {
492 int r = mbedtls_ssl_handshake(c->ssl);
493 tcp_output(c->pcb);
495 if (r != 0) { /* bad client hello, no shared cipher, peer alert, ... */
496 conn_abort(c);
497 return ERR_ABRT;
498 }
499 c->handshake_done = true;
500 }
501 if (c->close_after_tx) return ERR_OK;
502
503 size_t limit = c->srv->cfg.max_header_bytes + c->srv->cfg.max_body_bytes;
504 while (c->rx_len < limit + 1) {
505 size_t want = limit + 1 - c->rx_len;
506 if (want > 2048) want = 2048;
507 if (!rx_reserve(c, c->rx_len + want, limit)) {
508 conn_abort(c);
509 return ERR_ABRT;
510 }
511 int r = mbedtls_ssl_read(c->ssl, (unsigned char *)c->rx + c->rx_len, want);
512 if (r > 0) {
513 c->rx_len += (size_t)r;
514 continue;
515 }
518 c->peer_closed = true;
519 break;
520 }
521 conn_abort(c); /* bad record MAC, unexpected message, ... */
522 return ERR_ABRT;
523 }
524 tcp_output(c->pcb); /* alerts or renegotiation refusals */
525 return conn_process(c);
526}
527
528/* Queue received ciphertext; refuse it (lwIP redelivers) while the queue is full. */
529static err_t tls_on_recv(cwist_lwip_conn *c, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
530 if (!p) {
531 c->tcp_fin = true;
532 return tls_pump(c);
533 }
534 if (err != ERR_OK) {
535 pbuf_free(p);
536 return err;
537 }
538 c->last_active_ms = sys_now();
539 if (c->tin_off > 0) { /* compact before growing */
540 memmove(c->tin, c->tin + c->tin_off, c->tin_len - c->tin_off);
541 c->tin_len -= c->tin_off;
542 c->tin_off = 0;
543 }
544 size_t need = c->tin_len + p->tot_len;
545 if (need > CWIST_LWIP_TLS_IN_MAX) return ERR_MEM;
546 if (need > c->tin_cap) {
547 size_t cap = c->tin_cap ? c->tin_cap : 1024;
548 while (cap < need) cap *= 2;
550 char *grown = cwist_realloc(c->tin, cap);
551 if (!grown) {
552 pbuf_free(p);
553 conn_abort(c);
554 return ERR_ABRT;
555 }
556 c->tin = grown;
557 c->tin_cap = cap;
558 }
559 pbuf_copy_partial(p, c->tin + c->tin_len, p->tot_len, 0);
560 c->tin_len += p->tot_len;
561 tcp_recved(pcb, p->tot_len);
562 pbuf_free(p);
563 return tls_pump(c);
564}
565
566/* CTR_DRBG entropy source: the firmware TRNG, nothing else. */
567static int tls_entropy(void *ctx, unsigned char *out, size_t len) {
568 (void)ctx;
570}
571#endif /* CWIST_LWIP_TLS */
572
573/* --- lwIP callbacks ------------------------------------------------------------- */
574
575static err_t on_sent(void *arg, struct tcp_pcb *pcb, u16_t len) {
576 cwist_lwip_conn *c = arg;
577 (void)pcb;
578 if (!c) return ERR_OK;
579 c->last_active_ms = sys_now();
580#ifdef CWIST_LWIP_TLS
581 if (c->ssl) return tls_pump(c); /* resume handshake / writes, then the next request */
582#endif
583 size_t acked = len;
584 size_t skip = acked < c->ack_skip ? acked : c->ack_skip;
585 c->ack_skip -= skip;
586 acked -= skip;
587 if (!c->tx) return ERR_OK;
588 c->tx_acked += acked;
589 if (c->tx_acked < c->tx_len) return conn_push(c);
590
591 cwist_free(c->tx);
592 c->tx = NULL;
593 c->tx_len = c->tx_queued = c->tx_acked = 0;
594 if (c->close_after_tx) return conn_close(c);
595 return conn_process(c); /* next pipelined request */
596}
597
598static err_t on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
599 cwist_lwip_conn *c = arg;
600 if (!c) {
601 if (p) pbuf_free(p);
602 return ERR_OK;
603 }
604#ifdef CWIST_LWIP_TLS
605 if (c->ssl) return tls_on_recv(c, pcb, p, err);
606#endif
607 if (!p) { /* FIN */
608 c->peer_closed = true;
609 return conn_process(c);
610 }
611 if (err != ERR_OK) {
612 pbuf_free(p);
613 return err;
614 }
615 c->last_active_ms = sys_now();
616 if (c->close_after_tx) { /* draining toward close: discard */
617 tcp_recved(pcb, p->tot_len);
618 pbuf_free(p);
619 return ERR_OK;
620 }
621
622 /* One request is at most header cap + body cap; keep one spare request
623 * worth of pipelined bytes while a response is in flight. */
624 size_t limit = c->srv->cfg.max_header_bytes + c->srv->cfg.max_body_bytes;
625 size_t need = c->rx_len + p->tot_len;
626 if (need > limit + 1) {
627 if (c->tx) return ERR_MEM; /* lwIP keeps p and redelivers it later */
628 need = limit + 1; /* enough to classify as 413/431 */
629 }
630 if (!rx_reserve(c, need, limit)) {
631 pbuf_free(p);
632 conn_abort(c);
633 return ERR_ABRT;
634 }
635 size_t take = need - c->rx_len;
636 pbuf_copy_partial(p, c->rx + c->rx_len, (u16_t)take, 0);
637 c->rx_len += take;
638 tcp_recved(pcb, p->tot_len);
639 pbuf_free(p);
640 return conn_process(c);
641}
642
643static void on_err(void *arg, err_t err) {
644 cwist_lwip_conn *c = arg;
645 (void)err;
646 if (!c) return;
647 c->pcb = NULL; /* already freed by lwIP */
648 conn_free(c);
649}
650
651static err_t on_poll(void *arg, struct tcp_pcb *pcb) {
652 cwist_lwip_conn *c = arg;
653 (void)pcb;
654 if (!c) return ERR_OK;
655 uint32_t timeout = c->srv->cfg.idle_timeout_ms;
656 if (timeout && (u32_t)(sys_now() - c->last_active_ms) >= timeout) return conn_close(c);
657 if (c->tx && c->tx_queued < c->tx_len) return conn_push(c); /* retry after ERR_MEM */
658#ifdef CWIST_LWIP_TLS
659 if (c->ssl) return tls_pump(c); /* refused ciphertext / a stalled handshake */
660#endif
661 return ERR_OK;
662}
663
664static err_t on_accept(void *arg, struct tcp_pcb *pcb, err_t err) {
665 cwist_lwip_server *srv = arg;
666 if (err != ERR_OK || !pcb) return ERR_VAL;
667 if (srv->nconns >= srv->cfg.max_conns) {
668 tcp_abort(pcb);
669 return ERR_ABRT;
670 }
671 cwist_lwip_conn *c = cwist_alloc(sizeof(*c));
672 if (!c) {
673 tcp_abort(pcb);
674 return ERR_ABRT;
675 }
676 memset(c, 0, sizeof(*c));
677 c->srv = srv;
678 c->pcb = pcb;
679 c->last_active_ms = sys_now();
680 c->next = srv->conns;
681 srv->conns = c;
682 srv->nconns++;
683
684#ifdef CWIST_LWIP_TLS
685 if (srv->cfg.tls) {
686 c->ssl = cwist_alloc(sizeof(*c->ssl));
687 if (c->ssl) mbedtls_ssl_init(c->ssl);
688 if (!c->ssl || mbedtls_ssl_setup(c->ssl, &srv->cfg.tls->conf) != 0) {
689 c->pcb = NULL;
690 conn_free(c);
691 tcp_abort(pcb);
692 return ERR_ABRT;
693 }
695 }
696#endif
697
698 tcp_arg(pcb, c);
699 tcp_recv(pcb, on_recv);
700 tcp_sent(pcb, on_sent);
701 tcp_err(pcb, on_err);
702 tcp_poll(pcb, on_poll, CWIST_LWIP_POLL_TICKS);
703 return ERR_OK;
704}
705
706/* --- public API ----------------------------------------------------------------- */
707
713 if (!cfg) return;
714 cfg->port = 80;
715 cfg->max_conns = 4;
716 cfg->max_header_bytes = 4096;
717 cfg->max_body_bytes = 16384;
718 cfg->idle_timeout_ms = 30000;
719 cfg->keep_alive = true;
720 cfg->tls = NULL;
721}
722
730 if (!app) return NULL;
731 cwist_lwip_server *srv = cwist_alloc(sizeof(*srv));
732 if (!srv) return NULL;
733 memset(srv, 0, sizeof(*srv));
734 srv->app = app;
735 if (cfg)
736 srv->cfg = *cfg;
737 else
738 cwist_lwip_config_init(&srv->cfg);
739 if (srv->cfg.max_conns == 0) srv->cfg.max_conns = 1;
740#ifndef CWIST_LWIP_TLS
741 if (srv->cfg.tls) goto fail; /* never fall back to plaintext silently */
742#endif
743
745 if (!pcb) goto fail;
746#if SO_REUSE
747 /* Restarting the listener must not wait out TIME_WAIT on connections the
748 * server closed (it is the active closer for Connection: close). */
750#endif
751 if (tcp_bind(pcb, IP_ANY_TYPE, srv->cfg.port) != ERR_OK) {
752 tcp_close(pcb);
753 goto fail;
754 }
755 struct tcp_pcb *lpcb = tcp_listen_with_backlog(pcb, srv->cfg.max_conns);
756 if (!lpcb) {
757 tcp_close(pcb);
758 goto fail;
759 }
760 srv->listen_pcb = lpcb;
761 tcp_arg(lpcb, srv);
762 tcp_accept(lpcb, on_accept);
763 return srv;
764
765fail:
766 cwist_free(srv);
767 return NULL;
768}
769
774void cwist_lwip_close(cwist_lwip_server *srv) {
775 if (!srv) return;
776 if (srv->listen_pcb) {
777 tcp_arg(srv->listen_pcb, NULL);
778 tcp_accept(srv->listen_pcb, NULL);
779 tcp_close(srv->listen_pcb);
780 srv->listen_pcb = NULL;
781 }
782 while (srv->conns) conn_abort(srv->conns);
783 cwist_free(srv);
784}
785
791size_t cwist_lwip_active_connections(const cwist_lwip_server *srv) {
792 return srv ? srv->nconns : 0;
793}
794
804cwist_lwip_tls *cwist_lwip_tls_create(const unsigned char *cert, size_t cert_len,
805 const unsigned char *key, size_t key_len) {
806#ifdef CWIST_LWIP_TLS
807 static const unsigned char pers[] = "cwist-lwip-tls";
808 if (!cert || !key) return NULL;
809 cwist_lwip_tls *tls = cwist_alloc(sizeof(*tls));
810 if (!tls) return NULL;
811 mbedtls_ssl_config_init(&tls->conf);
812 mbedtls_ctr_drbg_init(&tls->drbg);
813 mbedtls_x509_crt_init(&tls->cert);
814 mbedtls_pk_init(&tls->key);
815 if (mbedtls_ctr_drbg_seed(&tls->drbg, tls_entropy, NULL, pers, sizeof(pers) - 1) != 0 ||
816 mbedtls_x509_crt_parse(&tls->cert, cert, cert_len) != 0 ||
818 &tls->drbg) != 0 ||
819 mbedtls_pk_check_pair(&tls->cert.pk, &tls->key, mbedtls_ctr_drbg_random, &tls->drbg) != 0 ||
822 mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->key) != 0) {
824 return NULL;
825 }
826 mbedtls_ssl_conf_rng(&tls->conf, mbedtls_ctr_drbg_random, &tls->drbg);
827 return tls;
828#else
829 (void)cert;
830 (void)cert_len;
831 (void)key;
832 (void)key_len;
833 return NULL;
834#endif
835}
836
842#ifdef CWIST_LWIP_TLS
843 if (!tls) return;
844 mbedtls_ssl_config_free(&tls->conf);
845 mbedtls_x509_crt_free(&tls->cert);
846 mbedtls_pk_free(&tls->key);
847 mbedtls_ctr_drbg_free(&tls->drbg);
848 cwist_free(tls);
849#else
850 (void)tls;
851#endif
852}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
Definition alloc.c:27
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
cwist_http_status_t
Definition http.h:27
@ CWIST_HTTP_INTERNAL_ERROR
Definition http.h:35
@ CWIST_HTTP_NOT_IMPLEMENTED
Definition http.h:36
@ CWIST_HTTP_BAD_REQUEST
Definition http.h:31
void cwist_http_request_destroy(cwist_http_request *req)
Destroy an HTTP request object.
Definition http.c:2180
@ CWIST_HTTP_HEAD
Definition http.h:22
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response object.
Definition http.c:2315
cwist_http_response * cwist_http_response_create(void)
Create a new HTTP response object.
Definition http.c:2291
void cwist_lwip_tls_destroy(cwist_lwip_tls *tls)
Free a TLS context; close every server using it first.
Definition lwip.c:841
void cwist_lwip_close(cwist_lwip_server *srv)
Stop listening, abort every open connection, and free srv.
Definition lwip.c:774
frame_result
Definition lwip.c:113
@ FRAME_NEED_MORE
Definition lwip.c:114
@ FRAME_ERROR
Definition lwip.c:116
@ FRAME_COMPLETE
Definition lwip.c:115
cwist_lwip_server * cwist_lwip_listen(cwist_app *app, const cwist_lwip_config *cfg)
Bind a listening tcp_pcb on cfg->port and serve app on it.
Definition lwip.c:729
cwist_lwip_tls * cwist_lwip_tls_create(const unsigned char *cert, size_t cert_len, const unsigned char *key, size_t key_len)
Create a TLS server context from a certificate chain and private key.
Definition lwip.c:804
#define CWIST_LWIP_POLL_TICKS
Definition lwip.c:58
struct cwist_lwip_conn cwist_lwip_conn
Definition lwip.c:62
void cwist_lwip_config_init(cwist_lwip_config *cfg)
Fill cfg with the defaults documented on cwist_lwip_config.
Definition lwip.c:712
size_t cwist_lwip_active_connections(const cwist_lwip_server *srv)
Number of connections the server currently holds open.
Definition lwip.c:791
Serve a CWIST app over lwIP's raw TCP API (issue #447 Phase 3).
struct cwist_lwip_server cwist_lwip_server
Opaque listener handle returned by cwist_lwip_listen().
Definition lwip.h:81
struct cwist_lwip_tls cwist_lwip_tls
Opaque TLS server context: certificate chain, key, DRBG and mbedTLS config.
Definition lwip.h:78
HTTP Protocol Definitions and Helpers.
int cwist_http_response_stream_end(cwist_http_response *res)
Terminate a streaming response with the final zero-length chunk and flush.
Definition http.c:4549
void cwist_http_response_set_body_ptr(cwist_http_response *res, const void *ptr, size_t len)
Sets a direct pointer for the response body (Zero Copy). Use this when serving large files from memor...
Definition http.c:2346
@ CWIST_HTTP_PAYLOAD_TOO_LARGE
Definition http.h:90
@ CWIST_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE
Definition http.h:104
cwist_http_request * cwist_http_parse_request_len(const char *buf, size_t len)
Parse an HTTP/1.x request held in a memory buffer (length-based; the buffer may contain a binary body...
Definition http.c:4424
int cwist_http_response_serialize(cwist_http_response *res, char **out, size_t *out_len)
Serialize a response into a freshly allocated HTTP/1.x buffer (status line + headers + body)....
Definition http.c:4439
const char * cwist_http_status_reason(int status)
Return the standard reason phrase for an HTTP status code (e.g. 404 -> "Not Found")....
Definition http.c:3970
size_t head
Definition otel.c:57
size_t cap
Definition otel.c:56
Hooks a bare-metal firmware supplies to CWIST (issue #447).
int cwist_port_entropy(void *buf, size_t len)
Fill a buffer with cryptographically secure random bytes.
Main Application Context.
Definition app.h:155
bool keep_alive
Definition http.h:74
cwist_http_method_t method
Definition http.h:66
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_http_status_t status_code
Definition http.h:84
bool stream_mode
cwist_http_response_stream_begin() ran.
Definition http.h:225
bool stream_ended
cwist_http_response_stream_end() ran.
Definition http.h:226
Listener settings; start from cwist_lwip_config_init().
Definition lwip.h:67
uint16_t port
Definition lwip.h:68
struct cwist_lwip_tls * tls
Definition lwip.h:74
bool keep_alive
Definition lwip.h:73
uint32_t idle_timeout_ms
Definition lwip.h:72
size_t max_body_bytes
Definition lwip.h:71
uint16_t max_conns
Definition lwip.h:69
size_t max_header_bytes
Definition lwip.h:70
void cwist_app_dispatch(cwist_app *app, cwist_http_request *req, cwist_http_response *res)
Dispatch a request internally through the app's router and middleware. Used by the test client and ad...
Definition app.c:2516