cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
webrtc.c
Go to the documentation of this file.
1
22#include "webrtc_internal.h"
23
25
26#include <arpa/inet.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <ifaddrs.h>
30#include <openssl/rand.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
38#define STUN_RTO_INITIAL_MS 300
39#define STUN_RTO_MAX_MS 3000
40#define STUN_MAX_ATTEMPTS 7
41#define PARK_TIMEOUT_MS 30000
42#define HANDSHAKE_TIMEOUT_MS 30000
43#define LIVENESS_TIMEOUT_MS 30000
44#define SCTP_TICK_ACTIVE_MS 10
45#define SCTP_TICK_IDLE_MS 250
46#define SCTP_ACTIVE_WINDOW_MS 1000
50#define RX_BUDGET 256
52#define UDP_SOCKBUF (4 << 20)
53
55static _Thread_local cwist_webrtc_ctx *tl_ctx;
56
57static void ctx_release(cwist_webrtc_ctx *ctx);
58static void ctx_release_resources(cwist_webrtc_ctx *ctx);
59static void conn_teardown(struct cwist_webrtc_conn *conn);
60static void conn_rearm_timer(struct cwist_webrtc_conn *conn);
61static void ctx_arm_sctp_timer(cwist_webrtc_ctx *ctx);
62static void ctx_note_activity(cwist_webrtc_ctx *ctx);
63static void ctx_teardown_owner(cwist_webrtc_ctx *ctx);
64static void conn_timer_cb(void *arg);
65
66/* ---- time ---- */
67
69static uint64_t mono_ns(void) {
70 struct timespec ts;
72 return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
73}
74
75/* ---- references ---- */
76
78static void ctx_retain(cwist_webrtc_ctx *ctx) {
80}
81
85
87 if (!conn || atomic_fetch_sub_explicit(&conn->refs, 1, memory_order_acq_rel) != 1) return;
88 /* Last reference: teardown already released everything but the memory,
89 * unless the conn never got registered (post dropped at ctx teardown). */
90 cwist_webrtc_ctx *ctx = conn->ctx;
91 if (conn->ssl) SSL_free(conn->ssl);
92 cwist_free(conn->ch_bits);
94 cwist_free(conn);
95 ctx_release(ctx);
96}
97
98/* ---- owner-thread bracketing ---- */
99
101 return tl_ctx == ctx;
102}
103
110static cwist_webrtc_ctx *ctx_enter(cwist_webrtc_ctx *ctx) {
111 cwist_webrtc_ctx *prev = tl_ctx;
112 if (prev != ctx) {
113 ctx_retain(ctx);
114 tl_ctx = ctx;
115 ctx->now_ns = mono_ns();
116 }
117 return prev;
118}
119
121static void ctx_tx_flush(cwist_webrtc_ctx *ctx) {
122 uint32_t n = ctx->tx_n;
123 ctx->tx_n = 0;
124 if (n == 0 || ctx->udp_fd < 0) return;
125#ifdef __linux__
128 for (uint32_t i = 0; i < n; i++) {
129 iov[i].iov_base = ctx->tx_bufs + (size_t)i * CWIST_WEBRTC_DGRAM_MAX;
130 iov[i].iov_len = ctx->tx_len[i];
131 memset(&msgs[i].msg_hdr, 0, sizeof(msgs[i].msg_hdr));
132 msgs[i].msg_hdr.msg_name = &ctx->tx_addr[i];
133 msgs[i].msg_hdr.msg_namelen = sizeof(ctx->tx_addr[i]);
134 msgs[i].msg_hdr.msg_iov = &iov[i];
135 msgs[i].msg_hdr.msg_iovlen = 1;
136 }
137 uint32_t off = 0;
138 while (off < n) {
139 int sent = sendmmsg(ctx->udp_fd, msgs + off, n - off, MSG_DONTWAIT);
140 if (sent > 0) {
141 off += (uint32_t)sent;
142 continue;
143 }
144 if (sent < 0 && errno == EINTR) continue;
145 /* EAGAIN/ENOBUFS or a per-destination error: UDP is lossy anyway and
146 * DTLS/SCTP retransmit, so skip this datagram and keep going. */
147 off++;
148 }
149#else
150 for (uint32_t i = 0; i < n; i++) {
151 sendto(ctx->udp_fd, ctx->tx_bufs + (size_t)i * CWIST_WEBRTC_DGRAM_MAX, ctx->tx_len[i], 0,
152 (const struct sockaddr *)&ctx->tx_addr[i], sizeof(ctx->tx_addr[i]));
153 }
154#endif
155}
156
158static void ctx_tx_queue(cwist_webrtc_ctx *ctx, const uint8_t *data, size_t len,
159 const struct sockaddr_in *to) {
160 if (len > CWIST_WEBRTC_DGRAM_MAX || ctx->closed) return;
161 if (ctx->tx_n == CWIST_WEBRTC_TX_BATCH) ctx_tx_flush(ctx);
162 uint32_t i = ctx->tx_n++;
163 memcpy(ctx->tx_bufs + (size_t)i * CWIST_WEBRTC_DGRAM_MAX, data, len);
164 ctx->tx_len[i] = len;
165 ctx->tx_addr[i] = *to;
166}
167
169static void conn_mark_dirty(struct cwist_webrtc_conn *conn) {
170 if (conn->dirty) return;
171 conn->dirty = true;
173 conn->dirty_next = conn->ctx->dirty;
174 conn->ctx->dirty = conn;
175}
176
178static void conn_flush_pending(struct cwist_webrtc_conn *conn) {
179 while (conn->pending_head && conn->sctp_ready) {
181 int rc = cwist_sctp_send(conn, m->channel, m->data, m->len, m->is_string);
182 if (rc == 1) break; /* send buffer full: retried after the next inbound packet */
183 conn->pending_head = m->next;
184 if (!conn->pending_head) conn->pending_tail = NULL;
186 cwist_free(m);
187 }
188}
189
191static void conn_service(struct cwist_webrtc_conn *conn) {
192 if (atomic_load(&conn->detached)) return;
193 if (conn->state == CWIST_CONN_ESTABLISHED && !conn->close_requested) {
194 if (cwist_sctp_conn_drain(conn) < 0) conn->close_requested = true;
195 if (!conn->close_requested && !conn->sctp_ready && cwist_sctp_assoc_established(conn)) {
196 conn->sctp_ready = true;
197 conn->notified_open = true;
198 atomic_fetch_add(&conn->ctx->ready_count, 1);
199 }
200 if (!conn->close_requested) conn_flush_pending(conn);
201 }
202 if (conn->close_requested) conn_teardown(conn);
203}
204
206static void ctx_leave(cwist_webrtc_ctx *ctx, cwist_webrtc_ctx *prev) {
207 if (prev != ctx) {
208 /* Servicing can dirty more conns (a message handler sending on
209 * another conn), so loop until the list stays empty. */
210 while (ctx->dirty) {
211 struct cwist_webrtc_conn *list = ctx->dirty;
212 ctx->dirty = NULL;
213 while (list) {
214 struct cwist_webrtc_conn *next = list->dirty_next;
215 list->dirty = false;
216 conn_service(list);
218 list = next;
219 }
220 }
221 ctx_tx_flush(ctx);
222 tl_ctx = prev;
223 ctx_release(ctx);
224 }
225}
226
227/* ---- connection table ---- */
228
230static uint32_t conn_hash(const cwist_webrtc_ctx *ctx, const struct sockaddr_in *a) {
231 uint32_t h = a->sin_addr.s_addr * 0x9E3779B1u ^ (uint32_t)a->sin_port * 0x85EBCA77u;
232 h ^= h >> 15;
233 return h & (ctx->nbuckets - 1);
234}
235
237static struct cwist_webrtc_conn *table_find(const cwist_webrtc_ctx *ctx,
238 const struct sockaddr_in *remote) {
239 for (struct cwist_webrtc_conn *c = ctx->buckets[conn_hash(ctx, remote)]; c; c = c->hnext) {
240 if (c->remote.sin_addr.s_addr == remote->sin_addr.s_addr &&
241 c->remote.sin_port == remote->sin_port)
242 return c;
243 }
244 return NULL;
245}
246
248static void table_grow(cwist_webrtc_ctx *ctx) {
249 uint32_t nb = ctx->nbuckets * 2;
250 struct cwist_webrtc_conn **b = cwist_alloc(nb * sizeof(*b));
251 if (!b) return; /* keep the old, longer chains */
253 struct cwist_webrtc_conn **old = ctx->buckets;
254 ctx->buckets = b;
255 ctx->nbuckets = nb;
256 for (uint32_t i = 0; i < old_n; i++) {
257 struct cwist_webrtc_conn *c = old[i];
258 while (c) {
259 struct cwist_webrtc_conn *next = c->hnext;
260 uint32_t h = conn_hash(ctx, &c->remote);
261 c->hnext = b[h];
262 b[h] = c;
263 c = next;
264 }
265 }
267}
268
270static void table_insert(cwist_webrtc_ctx *ctx, struct cwist_webrtc_conn *conn) {
271 if (ctx->nconns + 1 > ctx->nbuckets) table_grow(ctx);
272 uint32_t h = conn_hash(ctx, &conn->remote);
273 conn->hnext = ctx->buckets[h];
274 ctx->buckets[h] = conn;
275 ctx->nconns++;
276 conn->registered = true;
277 conn->parked = false;
278}
279
281static void table_remove(cwist_webrtc_ctx *ctx, struct cwist_webrtc_conn *conn) {
282 struct cwist_webrtc_conn **pp =
283 conn->parked ? &ctx->parked : &ctx->buckets[conn_hash(ctx, &conn->remote)];
284 while (*pp && *pp != conn) pp = &(*pp)->hnext;
285 if (*pp) {
286 *pp = conn->hnext;
287 if (!conn->parked) ctx->nconns--;
288 }
289 conn->hnext = NULL;
290 conn->registered = false;
291 conn->parked = false;
292}
293
294/* ---- conn lifecycle ---- */
295
297static struct cwist_webrtc_conn *conn_new(cwist_webrtc_ctx *ctx, const struct sockaddr_in *remote,
298 bool is_server_role) {
299 struct cwist_webrtc_conn *conn = cwist_malloc(sizeof(*conn));
300 if (!conn) return NULL;
301 conn->ctx = ctx;
302 ctx_retain(ctx);
303 atomic_init(&conn->refs, 1);
304 atomic_init(&conn->detached, false);
305 atomic_init(&conn->close_posted, false);
306 atomic_init(&conn->buffered, 0);
308 conn->state = CWIST_CONN_STUN;
309 if (remote) conn->remote = *remote;
310 conn->created_ns = mono_ns();
311 conn->last_rx_ns = conn->created_ns;
313 cwist_reactor_timer_init(&conn->timer, conn_timer_cb, conn);
314 return conn;
315}
316
321static void conn_teardown(struct cwist_webrtc_conn *conn) {
322 if (atomic_exchange(&conn->detached, true)) return;
323 cwist_webrtc_ctx *ctx = conn->ctx;
324 conn->state = CWIST_CONN_CLOSED;
327 conn->sctp_ready = false;
328 if (conn->sctp_sock || conn->sctp_acc) {
331 }
332 if (conn->ssl) {
333 SSL_free(conn->ssl);
334 conn->ssl = NULL;
335 }
336 while (conn->pending_head) {
338 conn->pending_head = m->next;
340 cwist_free(m);
341 }
342 conn->pending_tail = NULL;
343 bool notify = conn->notified_open;
344 conn->notified_open = false;
345 if (notify && ctx->close_cb) ctx->close_cb(conn, ctx->close_user);
346 if (conn->registered) {
347 table_remove(ctx, conn);
348 cwist_webrtc_conn_release(conn); /* the table's reference */
349 }
350}
351
353static void conn_start_dtls(struct cwist_webrtc_conn *conn) {
354 cwist_webrtc_ctx *ctx = conn->ctx;
356 conn->ssl = SSL_new(ssl_ctx);
357 BIO *bio = conn->ssl ? cwist_dtls_bio_new(conn) : NULL;
358 if (!bio) {
359 conn->close_requested = true;
360 conn_mark_dirty(conn);
361 return;
362 }
363 SSL_set_bio(conn->ssl, bio, bio); /* one BIO for both directions */
366 if (conn->is_server_role)
368 else
370 conn->state = CWIST_CONN_DTLS;
371 SSL_do_handshake(conn->ssl); /* client: emits ClientHello */
372 conn_rearm_timer(conn);
373}
374
376static void conn_on_dtls_up(struct cwist_webrtc_conn *conn) {
377 cwist_webrtc_ctx *ctx = conn->ctx;
379 if (cwist_sctp_conn_open(conn) < 0) {
380 conn->close_requested = true;
381 return;
382 }
383 if (ctx->sctp_assocs++ == 0) ctx_arm_sctp_timer(ctx);
384 conn_rearm_timer(conn);
385}
386
388static void conn_dtls_input(struct cwist_webrtc_conn *conn, const uint8_t *buf, size_t len) {
389 conn->dtls_in = buf;
390 conn->dtls_in_len = len;
391 if (conn->state == CWIST_CONN_DTLS) {
392 int rc = SSL_do_handshake(conn->ssl);
393 if (rc == 1) {
394 conn_on_dtls_up(conn);
395 } else if (SSL_get_error(conn->ssl, rc) == SSL_ERROR_SSL) {
396 conn->close_requested = true;
397 } else {
398 conn_rearm_timer(conn); /* a flight arrived: new retransmit deadline */
399 }
400 }
401 if (conn->state == CWIST_CONN_ESTABLISHED && !conn->close_requested) {
402 for (;;) {
403 int n = SSL_read(conn->ssl, conn->ctx->plain_buf, CWIST_WEBRTC_MAX_MESSAGE);
404 if (n > 0) {
405 cwist_sctp_conn_input(conn, conn->ctx->plain_buf, (size_t)n);
406 continue;
407 }
408 int err = SSL_get_error(conn->ssl, n);
410 (err == SSL_ERROR_SYSCALL && n == 0))
411 conn->close_requested = true;
412 break;
413 }
414 }
415 conn->dtls_in = NULL;
416 conn->dtls_in_len = 0;
417 conn_mark_dirty(conn);
418}
419
421static void conn_send_stun_request(struct cwist_webrtc_conn *conn) {
422 uint8_t msg[512];
423 char username[128];
424 snprintf(username, sizeof(username), "%s:%s", conn->peer_ufrag, conn->ctx->ice_ufrag);
425 int len = cwist_ice_stun_build_request(msg, sizeof(msg), conn->stun_txid, username);
426 if (len < 0) return;
427 len = cwist_ice_stun_sign_request(msg, sizeof(msg), (size_t)len, conn->peer_pwd);
428 if (len < 0) return;
429 ctx_tx_queue(conn->ctx, msg, (size_t)len, &conn->remote);
430 conn->stun_attempts++;
431}
432
433/* ---- per-conn timer ---- */
434
436static void conn_rearm_timer(struct cwist_webrtc_conn *conn) {
437 cwist_webrtc_ctx *ctx = conn->ctx;
438 uint64_t now = ctx->now_ns ? ctx->now_ns : mono_ns();
440 switch (conn->state) {
441 case CWIST_CONN_STUN:
442 if (conn->parked) {
443 uint64_t end = conn->created_ns + PARK_TIMEOUT_MS * 1000000ull;
444 delay_us = end > now ? (end - now) / 1000 : 0;
445 } else if (!conn->is_server_role) {
446 delay_us = (uint64_t)conn->stun_rto_ms * 1000;
447 } else {
448 uint64_t end = conn->created_ns + HANDSHAKE_TIMEOUT_MS * 1000000ull;
449 delay_us = end > now ? (end - now) / 1000 : 0;
450 }
451 break;
452 case CWIST_CONN_DTLS: {
453 struct timeval tv;
454 uint64_t end = conn->created_ns + HANDSHAKE_TIMEOUT_MS * 1000000ull;
455 uint64_t hs_us = end > now ? (end - now) / 1000 : 0;
456 delay_us = hs_us;
457 if (DTLSv1_get_timeout(conn->ssl, &tv) == 1) {
458 uint64_t t = (uint64_t)tv.tv_sec * 1000000ull + (uint64_t)tv.tv_usec;
459 if (t < delay_us) delay_us = t;
460 }
461 break;
462 }
464 uint64_t end = conn->last_rx_ns + LIVENESS_TIMEOUT_MS * 1000000ull;
465 delay_us = end > now ? (end - now) / 1000 : 0;
466 break;
467 }
468 default: return;
469 }
471}
472
474static void conn_timer_cb(void *arg) {
475 struct cwist_webrtc_conn *conn = arg;
476 cwist_webrtc_ctx *ctx = conn->ctx;
477 cwist_webrtc_ctx *prev = ctx_enter(ctx);
479 uint64_t age_ms = (now - conn->created_ns) / 1000000ull;
480 switch (conn->state) {
481 case CWIST_CONN_STUN:
482 if (conn->parked || conn->is_server_role) {
484 conn->close_requested = true;
485 } else if (conn->stun_attempts >= STUN_MAX_ATTEMPTS) {
486 conn->close_requested = true;
487 } else {
488 conn_send_stun_request(conn);
490 : conn->stun_rto_ms * 2;
491 }
492 break;
493 case CWIST_CONN_DTLS:
495 conn->close_requested = true;
496 } else if (DTLSv1_handle_timeout(conn->ssl) < 0) {
497 conn->close_requested = true;
498 }
499 break;
501 if ((now - conn->last_rx_ns) / 1000000ull >= LIVENESS_TIMEOUT_MS)
502 conn->close_requested = true;
503 break;
504 default: break;
505 }
506 if (conn->close_requested)
507 conn_mark_dirty(conn);
508 else
509 conn_rearm_timer(conn);
510 ctx_leave(ctx, prev);
511}
512
513/* ---- SCTP clock ---- */
514
516static void ctx_arm_sctp_timer(cwist_webrtc_ctx *ctx) {
517 uint64_t now = ctx->now_ns ? ctx->now_ns : mono_ns();
521}
522
524static void ctx_note_activity(cwist_webrtc_ctx *ctx) {
526 if (ctx->sctp_assocs > 0 && !ctx->sctp_fast) ctx_arm_sctp_timer(ctx);
527}
528
530static void ctx_sctp_timer_cb(void *arg) {
531 cwist_webrtc_ctx *ctx = arg;
532 cwist_webrtc_ctx *prev = ctx_enter(ctx);
534 if (ctx->sctp_assocs > 0) ctx_arm_sctp_timer(ctx);
535 ctx_leave(ctx, prev);
536}
537
538/* ---- callbacks used by sctp.c / dtls.c ---- */
539
541 const uint8_t *data, size_t len, int is_string) {
542 cwist_webrtc_ctx *ctx = conn->ctx;
543 if (ctx->msg_cb)
544 ctx->msg_cb(conn, channel, data, len,
546}
547
549 const char *label) {
550 cwist_webrtc_ctx *ctx = conn->ctx;
551 if (ctx->ch_cb) ctx->ch_cb(conn, channel, label, ctx->ch_user);
552}
553
554void cwist_webrtc_conn_dtls_out(struct cwist_webrtc_conn *conn, const uint8_t *data, size_t len) {
555 ctx_tx_queue(conn->ctx, data, len, &conn->remote);
556}
557
559typedef struct {
561 struct cwist_webrtc_conn *conn;
562 size_t len;
563 uint8_t data[];
564} sctp_out_post;
565
567static void sctp_out_post_cb(void *arg) {
568 sctp_out_post *p = arg;
569 struct cwist_webrtc_conn *conn = p->conn;
570 cwist_webrtc_ctx *prev = ctx_enter(conn->ctx);
571 if (conn->ssl) SSL_write(conn->ssl, p->data, (int)p->len);
572 ctx_leave(conn->ctx, prev);
574 cwist_free(p);
575}
576
577void cwist_webrtc_conn_sctp_out(struct cwist_webrtc_conn *conn, const void *buffer, size_t len) {
578 if (cwist_webrtc_on_owner(conn->ctx)) {
579 /* No detached check: teardown closes SCTP (emitting its ABORT)
580 * before it frees the DTLS session. */
581 if (conn->ssl) SSL_write(conn->ssl, buffer, (int)len);
582 return;
583 }
584 /* A usrsctp timer pass on another thread produced this packet. */
585 sctp_out_post *p = cwist_alloc(sizeof(*p) + len);
586 if (!p) return; /* SCTP retransmits */
587 p->conn = conn;
588 p->len = len;
589 memcpy(p->data, buffer, len);
590 p->post.cb = sctp_out_post_cb;
591 p->post.ctx = p;
593 cwist_reactor_post(conn->ctx->reactor, &p->post);
594}
595
596/* ---- inbound datagrams ---- */
597
599static void handle_stun(cwist_webrtc_ctx *ctx, const uint8_t *buf, size_t len,
600 const struct sockaddr_in *remote) {
601 if (cwist_ice_stun_is_binding_request(buf, len)) {
603 return; /* no offer answered yet: not an ICE-lite endpoint */
605 return; /* bad MESSAGE-INTEGRITY: drop (RFC 8445 would send a 400) */
606 uint8_t resp[256];
607 int rlen =
609 if (rlen < 0) return;
610 ctx_tx_queue(ctx, resp, (size_t)rlen, remote);
611
613 struct cwist_webrtc_conn *conn = table_find(ctx, remote);
614 if (conn) {
615 conn->last_rx_ns = ctx->now_ns; /* consent check keeps it alive */
616 } else if (nominate) {
617 /* Adopt the conn parked by cwist_webrtc_handle_offer() when the
618 * USERNAME's remote ufrag matches; otherwise the request is still
619 * authenticated with our password, so start a fresh conn. */
620 char rfrag[64] = "";
621 cwist_ice_stun_get_remote_ufrag(buf, len, rfrag, sizeof(rfrag));
622 for (struct cwist_webrtc_conn *c = ctx->parked; c; c = c->hnext) {
623 if (rfrag[0] && strcmp(c->peer_ufrag, rfrag) == 0) {
624 conn = c;
625 break;
626 }
627 }
628 if (conn) {
629 table_remove(ctx, conn); /* the parked list's reference moves */
630 } else {
631 conn = conn_new(ctx, NULL, true);
632 if (!conn) return;
633 }
634 conn->remote = *remote;
635 conn->last_rx_ns = ctx->now_ns;
636 table_insert(ctx, conn);
637 }
638 if (conn && nominate && conn->state == CWIST_CONN_STUN) conn_start_dtls(conn);
639 return;
640 }
641 /* STUN response: only client-role conns expect these. */
642 struct cwist_webrtc_conn *conn = table_find(ctx, remote);
643 if (!conn || conn->is_server_role || conn->state != CWIST_CONN_STUN) return;
644 struct sockaddr_in mapped;
645 if (cwist_ice_stun_parse_response(buf, len, conn->stun_txid, conn->peer_pwd, &mapped)) {
646 conn->last_rx_ns = ctx->now_ns;
647 conn_start_dtls(conn);
648 }
649}
650
652static void handle_packet(cwist_webrtc_ctx *ctx, const uint8_t *buf, size_t len,
653 const struct sockaddr_in *remote) {
654 if (len == 0) return;
655 if (buf[0] <= 3) {
656 if (cwist_ice_stun_is_message(buf, len)) handle_stun(ctx, buf, len, remote);
657 return;
658 }
659 if (buf[0] < 20 || buf[0] > 63) return; /* not DTLS (RTP/RTCP or garbage) */
660 struct cwist_webrtc_conn *conn = table_find(ctx, remote);
661 if (!conn || !conn->ssl || atomic_load(&conn->detached)) return;
662 conn->last_rx_ns = ctx->now_ns;
663 conn_dtls_input(conn, buf, len);
664}
665
666static void udp_readable(int fd, void *payload);
667
669static bool ctx_arm_udp_slot(cwist_webrtc_ctx *ctx) {
670 if (!ctx->owns_reactor) ctx_retain(ctx);
671 ctx->udp_armed = cwist_reactor_add(ctx->reactor, ctx->udp_fd, udp_readable, &ctx, sizeof(ctx));
672 if (!ctx->udp_armed && !ctx->owns_reactor) ctx_release(ctx);
673 return ctx->udp_armed;
674}
675
677static void ctx_close_fd(cwist_webrtc_ctx *ctx) {
678 if (ctx->udp_fd >= 0) {
679 close(ctx->udp_fd);
680 ctx->udp_fd = -1;
681 }
683 ctx->fd_closed = true;
686}
687
689static void udp_readable(int fd, void *payload) {
690 cwist_webrtc_ctx *ctx = *(cwist_webrtc_ctx **)payload;
691 ctx->udp_armed = false;
692 if (ctx->closed) {
693 /* Woken by teardown: the slot is consumed, so the fd can go. */
694 ctx_close_fd(ctx);
695 if (!ctx->owns_reactor) ctx_release(ctx);
696 return;
697 }
698 cwist_webrtc_ctx *prev = ctx_enter(ctx);
699 int handled = 0;
700 while (handled < RX_BUDGET) {
701#ifdef __linux__
705 for (int i = 0; i < CWIST_WEBRTC_RX_BATCH; i++) {
706 iov[i].iov_base = ctx->rx_bufs + (size_t)i * CWIST_WEBRTC_DGRAM_MAX;
707 iov[i].iov_len = CWIST_WEBRTC_DGRAM_MAX;
708 memset(&msgs[i].msg_hdr, 0, sizeof(msgs[i].msg_hdr));
709 msgs[i].msg_hdr.msg_name = &from[i];
710 msgs[i].msg_hdr.msg_namelen = sizeof(from[i]);
711 msgs[i].msg_hdr.msg_iov = &iov[i];
712 msgs[i].msg_hdr.msg_iovlen = 1;
713 }
715 if (n < 0 && errno == EINTR) continue;
716 if (n <= 0) break;
717 for (int i = 0; i < n; i++) {
718 if (msgs[i].msg_hdr.msg_flags & MSG_TRUNC) continue;
719 if (msgs[i].msg_hdr.msg_namelen < sizeof(struct sockaddr_in) ||
720 from[i].sin_family != AF_INET)
721 continue;
722 handle_packet(ctx, iov[i].iov_base, msgs[i].msg_len, &from[i]);
723 }
724 handled += n;
725 if (n < CWIST_WEBRTC_RX_BATCH) break;
726#else
727 struct sockaddr_in from;
728 socklen_t flen = sizeof(from);
730 (struct sockaddr *)&from, &flen);
731 if (n < 0 && errno == EINTR) continue;
732 if (n <= 0) break;
733 if (flen >= sizeof(struct sockaddr_in) && from.sin_family == AF_INET)
734 handle_packet(ctx, ctx->rx_bufs, (size_t)n, &from);
735 handled++;
736#endif
737 }
738 if (handled > 0) ctx_note_activity(ctx);
739 ctx_leave(ctx, prev);
740 if (!ctx->closed) {
741 /* The slot that fired held a ref; the new slot takes its own. */
742 ctx_arm_udp_slot(ctx);
743 }
744 if (!ctx->owns_reactor) ctx_release(ctx);
745}
746
747/* ---- host address ---- */
748
750static void detect_host_ip(char *out, size_t cap) {
751 snprintf(out, cap, "127.0.0.1");
752 struct ifaddrs *ifas = NULL;
753 if (getifaddrs(&ifas) < 0) return;
754 for (struct ifaddrs *ifa = ifas; ifa; ifa = ifa->ifa_next) {
755 if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) continue;
756 struct sockaddr_in *sin = (struct sockaddr_in *)ifa->ifa_addr;
757 uint32_t a = ntohl(sin->sin_addr.s_addr);
758 if (((a >> 24) & 0xFF) == 127) continue;
759 snprintf(out, cap, "%u.%u.%u.%u", (a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF,
760 a & 0xFF);
761 break;
762 }
764}
765
766/* ---- ctx lifecycle ---- */
767
769static void ctx_release(cwist_webrtc_ctx *ctx) {
770 if (atomic_fetch_sub_explicit(&ctx->refs, 1, memory_order_acq_rel) != 1) return;
771 ctx_release_resources(ctx);
774 cwist_free(ctx);
775}
776
778static void ctx_release_resources(cwist_webrtc_ctx *ctx) {
781 if (ctx->cert) X509_free(ctx->cert);
782 if (ctx->pkey) EVP_PKEY_free(ctx->pkey);
783 ctx->server_ssl_ctx = ctx->client_ssl_ctx = NULL;
784 ctx->cert = NULL;
785 ctx->pkey = NULL;
786 cwist_free(ctx->buckets);
787 cwist_free(ctx->rx_bufs);
788 cwist_free(ctx->plain_buf);
789 cwist_free(ctx->sctp_buf);
790 cwist_free(ctx->tx_bufs);
791 ctx->buckets = NULL;
792 ctx->rx_bufs = ctx->plain_buf = ctx->sctp_buf = ctx->tx_bufs = NULL;
793}
794
799static void ctx_teardown_owner(cwist_webrtc_ctx *ctx) {
800 if (ctx->closed) return;
801 cwist_webrtc_ctx *prev = ctx_enter(ctx);
802 while (ctx->parked) conn_teardown(ctx->parked);
803 for (uint32_t i = 0; i < ctx->nbuckets; i++) {
804 while (ctx->buckets[i]) conn_teardown(ctx->buckets[i]);
805 }
807 ctx_tx_flush(ctx); /* SCTP ABORTs from the closes */
808 ctx->closed = true;
809 ctx_leave(ctx, prev); /* dirty conns are detached: only their refs drop */
810}
811
813static cwist_webrtc_ctx *ctx_create(cwist_reactor_t *reactor, bool owns_reactor, uint16_t port) {
814 if (!reactor || cwist_sctp_global_init() < 0) return NULL;
815 cwist_webrtc_ctx *ctx = cwist_malloc(sizeof(*ctx));
816 if (!ctx) return NULL;
817 ctx->reactor = reactor;
818 ctx->owns_reactor = owns_reactor;
819 ctx->owner_pid = getpid();
820 atomic_init(&ctx->refs, 1);
822 atomic_init(&ctx->ready_count, 0);
823 ctx->udp_fd = -1;
826 cwist_reactor_timer_init(&ctx->sctp_timer, ctx_sctp_timer_cb, ctx);
827
828 ctx->nbuckets = 64;
829 ctx->buckets = cwist_alloc(ctx->nbuckets * sizeof(*ctx->buckets));
834 if (!ctx->buckets || !ctx->rx_bufs || !ctx->tx_bufs || !ctx->plain_buf || !ctx->sctp_buf)
835 goto fail;
836
837 ctx->udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
838 if (ctx->udp_fd < 0) goto fail;
839 int flags = fcntl(ctx->udp_fd, F_GETFL, 0);
840 fcntl(ctx->udp_fd, F_SETFL, flags | O_NONBLOCK);
841 int buf = UDP_SOCKBUF;
842 setsockopt(ctx->udp_fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf));
843 setsockopt(ctx->udp_fd, SOL_SOCKET, SO_SNDBUF, &buf, sizeof(buf));
844 struct sockaddr_in addr;
845 memset(&addr, 0, sizeof(addr));
846 addr.sin_family = AF_INET;
847 addr.sin_addr.s_addr = htonl(INADDR_ANY);
848 addr.sin_port = htons(port);
849 if (bind(ctx->udp_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) goto fail;
850 socklen_t alen = sizeof(addr);
851 if (getsockname(ctx->udp_fd, (struct sockaddr *)&addr, &alen) < 0) goto fail;
852 ctx->port = ntohs(addr.sin_port);
853
854 if (cwist_dtls_generate_cert(&ctx->cert, &ctx->pkey) < 0) goto fail;
855 if (cwist_dtls_fingerprint(ctx->cert, ctx->fingerprint, sizeof(ctx->fingerprint)) < 0)
856 goto fail;
857 ctx->server_ssl_ctx = cwist_dtls_ctx_new(1, ctx->cert, ctx->pkey);
859 if (!ctx->server_ssl_ctx || !ctx->client_ssl_ctx) goto fail;
860
861 cwist_ice_random_creds(ctx->ice_ufrag, sizeof(ctx->ice_ufrag), ctx->ice_pwd,
862 sizeof(ctx->ice_pwd));
863 detect_host_ip(ctx->host_ip, sizeof(ctx->host_ip));
864 return ctx;
865
866fail:
867 if (ctx->udp_fd >= 0) close(ctx->udp_fd);
868 ctx_release(ctx);
869 return NULL;
870}
871
873static void *ctx_thread_main(void *arg) {
874 cwist_webrtc_ctx *ctx = arg;
876 return NULL;
877}
878
881 if (!reactor) return NULL;
882 cwist_webrtc_ctx *ctx = ctx_create(reactor, true, port);
883 if (!ctx) {
884 cwist_reactor_destroy(reactor);
885 return NULL;
886 }
887 if (!ctx_arm_udp_slot(ctx) || pthread_create(&ctx->thread, NULL, &ctx_thread_main, ctx) != 0) {
889 return NULL;
890 }
891 ctx->thread_running = true;
892 return ctx;
893}
894
896 cwist_webrtc_ctx *ctx = ctx_create(reactor, false, port);
897 if (!ctx) return NULL;
898 if (!ctx_arm_udp_slot(ctx)) {
899 close(ctx->udp_fd);
900 ctx->udp_fd = -1;
901 ctx_release(ctx);
902 return NULL;
903 }
904 return ctx;
905}
906
912static void ctx_teardown_on_reactor(cwist_webrtc_ctx *ctx) {
913 ctx_teardown_owner(ctx);
914 if (ctx->udp_armed && ctx->udp_fd >= 0) {
915 struct sockaddr_in self;
916 memset(&self, 0, sizeof(self));
917 self.sin_family = AF_INET;
918 self.sin_port = htons(ctx->port);
919 self.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
920 uint8_t b = 0;
921 if (sendto(ctx->udp_fd, &b, 1, 0, (struct sockaddr *)&self, sizeof(self)) == 1) return;
922 /* Could not wake it: cancel the slot and close here. */
923 cwist_reactor_del(ctx->reactor, ctx->udp_fd);
924 ctx->udp_armed = false;
925 ctx_release(ctx); /* the slot's reference */
926 }
927 ctx_close_fd(ctx);
928}
929
931static void ctx_free_post_cb(void *arg) {
932 cwist_webrtc_ctx *ctx = arg;
933 ctx_teardown_on_reactor(ctx);
934 ctx_release(ctx); /* the post's reference */
935}
936
938static void ctx_stop_post_cb(void *arg) {
939 cwist_webrtc_ctx *ctx = arg;
940 ctx_teardown_owner(ctx);
942}
943
945 if (!ctx) return;
946 if (getpid() != ctx->owner_pid) {
947 /* A forked child's copy: its reactor thread exists only in the
948 * parent, and the sockets and memory are the parent's to release.
949 * Leave the copy alone; it goes away with this process. */
950 return;
951 }
952 if (ctx->owns_reactor) {
953 if (ctx->thread_running) {
954 ctx->free_post.cb = ctx_stop_post_cb;
955 ctx->free_post.ctx = ctx;
957 pthread_join(ctx->thread, NULL);
958 }
959 /* The loop is gone (or never ran): finish here. Destroying the
960 * reactor drains leftover posts, which only drop references. */
961 ctx_teardown_owner(ctx);
962 if (ctx->udp_fd >= 0) {
963 close(ctx->udp_fd);
964 ctx->udp_fd = -1;
965 }
967 ctx->reactor = NULL;
968 ctx_release(ctx);
969 return;
970 }
971 if (tl_ctx == ctx) {
972 /* Called from one of our own callbacks: tear down inline; the read
973 * slot's callback finishes the fd close after we return. */
974 ctx_teardown_on_reactor(ctx);
975 } else {
976 ctx_retain(ctx);
977 ctx->free_post.cb = ctx_free_post_cb;
978 ctx->free_post.ctx = ctx;
981 while (!ctx->fd_closed) pthread_cond_wait(&ctx->free_cv, &ctx->free_mu);
983 }
984 ctx_release(ctx);
985}
986
987/* ---- public accessors ---- */
988
990 return ctx->port;
991}
992
994 return ctx->fingerprint;
995}
996
998 void *user) {
999 ctx->msg_cb = cb;
1000 ctx->msg_user = user;
1001}
1002
1004 void *user) {
1005 ctx->ch_cb = cb;
1006 ctx->ch_user = user;
1007}
1008
1010 void *user) {
1011 ctx->close_cb = cb;
1012 ctx->close_user = user;
1013}
1014
1018
1022
1023/* ---- offer / connect (any thread) ---- */
1024
1026static void park_post_cb(void *arg) {
1027 struct cwist_webrtc_conn *conn = arg;
1028 cwist_webrtc_ctx *ctx = conn->ctx;
1029 if (ctx->closed) {
1031 return;
1032 }
1033 cwist_webrtc_ctx *prev = ctx_enter(ctx);
1034 conn->hnext = ctx->parked;
1035 ctx->parked = conn;
1036 conn->registered = true;
1037 conn->parked = true; /* the post's reference becomes the list's */
1038 conn_rearm_timer(conn);
1039 ctx_leave(ctx, prev);
1040}
1041
1043 size_t out_len) {
1045 if (!ctx || !offer || cwist_sdp_parse(offer, strlen(offer), &info) < 0) return -1;
1046 if (getpid() != ctx->owner_pid)
1047 return -1; /* inherited across fork(): this process has no loop for it */
1048 const char *mid = info.mid[0] ? info.mid : "0";
1050 mid, ctx->host_ip, ctx->port) < 0)
1051 return -1;
1052 struct cwist_webrtc_conn *conn = conn_new(ctx, NULL, true);
1053 if (!conn) return -1;
1054 snprintf(conn->peer_ufrag, sizeof(conn->peer_ufrag), "%s", info.ice_ufrag);
1055 snprintf(conn->peer_pwd, sizeof(conn->peer_pwd), "%s", info.ice_pwd);
1057 conn->reg_post.cb = park_post_cb;
1058 conn->reg_post.ctx = conn;
1060 return 0;
1061}
1062
1064static void connect_post_cb(void *arg) {
1065 struct cwist_webrtc_conn *conn = arg;
1066 cwist_webrtc_ctx *ctx = conn->ctx;
1067 if (ctx->closed) {
1069 return;
1070 }
1071 cwist_webrtc_ctx *prev = ctx_enter(ctx);
1072 table_insert(ctx, conn); /* the post's reference becomes the table's */
1073 conn_send_stun_request(conn);
1074 conn_rearm_timer(conn);
1075 ctx_leave(ctx, prev);
1076}
1077
1079 const char *peer_ufrag, const char *peer_pwd) {
1080 struct cwist_webrtc_conn *conn = conn_new(ctx, remote, false);
1081 if (!conn) return NULL;
1082 snprintf(conn->peer_ufrag, sizeof(conn->peer_ufrag), "%s", peer_ufrag);
1083 snprintf(conn->peer_pwd, sizeof(conn->peer_pwd), "%s", peer_pwd);
1084 RAND_bytes(conn->stun_txid, sizeof(conn->stun_txid));
1085 cwist_webrtc_conn_retain(conn); /* returned to the caller */
1086 conn->reg_post.cb = connect_post_cb;
1087 conn->reg_post.ctx = conn;
1089 return conn;
1090}
1091
1092/* ---- send / close (any thread) ---- */
1093
1095static void conn_enqueue(struct cwist_webrtc_conn *conn, cwist_webrtc_msg *m) {
1096 m->next = NULL;
1097 if (conn->pending_tail)
1098 conn->pending_tail->next = m;
1099 else
1100 conn->pending_head = m;
1101 conn->pending_tail = m;
1102}
1103
1112static void kick_post_cb(void *arg) {
1113 struct cwist_webrtc_conn *conn = arg;
1114 /* acq_rel: producers that see the emptied inbox re-post kick_post, which
1115 * must happen after the reactor's reads of it for this run. */
1118 while (list) {
1119 cwist_webrtc_msg *next = list->next;
1120 list->next = rev;
1121 rev = list;
1122 list = next;
1123 }
1124 if (atomic_load(&conn->detached)) {
1125 while (rev) {
1126 cwist_webrtc_msg *next = rev->next;
1128 cwist_free(rev);
1129 rev = next;
1130 }
1131 } else if (rev) {
1132 cwist_webrtc_ctx *prev = ctx_enter(conn->ctx);
1133 ctx_note_activity(conn->ctx);
1134 while (rev) {
1135 cwist_webrtc_msg *next = rev->next;
1136 conn_enqueue(conn, rev);
1137 rev = next;
1138 }
1139 conn_mark_dirty(conn);
1140 ctx_leave(conn->ctx, prev);
1141 }
1142 cwist_webrtc_conn_release(conn); /* the kick's reference */
1143}
1144
1149static bool conn_reserve(struct cwist_webrtc_conn *conn, size_t len) {
1151 if (before + len > CWIST_WEBRTC_MAX_BUFFERED && before > 0) {
1153 return false;
1154 }
1155 return true;
1156}
1157
1159 size_t len, cwist_webrtc_data_type type) {
1160 if (!conn || atomic_load(&conn->detached) || len > CWIST_WEBRTC_MAX_MESSAGE ||
1161 (len > 0 && !data))
1162 return -1;
1163 int is_string = type == CWIST_WEBRTC_DATA_STRING;
1164 if (cwist_webrtc_on_owner(conn->ctx)) {
1165 ctx_note_activity(conn->ctx);
1166 if (conn->sctp_ready && !conn->pending_head) {
1167 int rc = cwist_sctp_send(conn, channel_id, data, len, is_string);
1168 if (rc == 0) return 0;
1169 if (rc < 0) return -1;
1170 }
1171 if (!conn_reserve(conn, len)) return -1;
1172 cwist_webrtc_msg *m = cwist_alloc(sizeof(*m) + len);
1173 if (!m) {
1175 return -1;
1176 }
1177 m->conn = conn;
1178 m->len = (uint32_t)len;
1179 m->channel = channel_id;
1180 m->is_string = (uint8_t)is_string;
1181 if (len) memcpy(m->data, data, len);
1182 conn_enqueue(conn, m);
1183 conn_mark_dirty(conn);
1184 return 0;
1185 }
1186 if (!conn_reserve(conn, len)) return -1;
1187 cwist_webrtc_msg *m = cwist_alloc(sizeof(*m) + len);
1188 if (!m) {
1190 return -1;
1191 }
1192 m->conn = conn;
1193 m->len = (uint32_t)len;
1194 m->channel = channel_id;
1195 m->is_string = (uint8_t)is_string;
1196 if (len) memcpy(m->data, data, len);
1198 do {
1199 m->next = head;
1202 if (head == NULL) {
1203 /* First message of a batch: wake the owner. The kick node is reused;
1204 * the reactor reads its link before running it, so re-posting from
1205 * here while the previous kick runs is safe. */
1207 conn->kick_post.cb = kick_post_cb;
1208 conn->kick_post.ctx = conn;
1209 cwist_reactor_post(conn->ctx->reactor, &conn->kick_post);
1210 }
1211 return 0;
1212}
1213
1215static void close_post_cb(void *arg) {
1216 struct cwist_webrtc_conn *conn = arg;
1217 if (!atomic_load(&conn->detached)) {
1218 cwist_webrtc_ctx *prev = ctx_enter(conn->ctx);
1219 conn->close_requested = true;
1220 conn_mark_dirty(conn);
1221 ctx_leave(conn->ctx, prev);
1222 }
1223 atomic_store(&conn->close_posted, false);
1225}
1226
1228 if (!conn || atomic_load(&conn->detached)) return;
1229 if (cwist_webrtc_on_owner(conn->ctx)) {
1230 /* Deferred to the end of the round: the caller may be inside a
1231 * message callback that is still reading this conn's SCTP socket. */
1232 conn->close_requested = true;
1233 conn_mark_dirty(conn);
1234 return;
1235 }
1236 if (atomic_exchange(&conn->close_posted, true)) return;
1238 conn->close_post.cb = close_post_cb;
1239 conn->close_post.ctx = conn;
1240 cwist_reactor_post(conn->ctx->reactor, &conn->close_post);
1241}
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_malloc(size_t size)
Allocate zeroed memory via the CWIST owner context.
Definition alloc.c:13
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
int cwist_dtls_fingerprint(X509 *cert, char *out, size_t cap)
Compute the SHA-256 fingerprint of a certificate in SDP format.
Definition dtls.c:90
int cwist_dtls_generate_cert(X509 **cert_out, EVP_PKEY **pkey_out)
Generate an ephemeral self-signed ECDSA certificate for DTLS.
Definition dtls.c:36
SSL_CTX * cwist_dtls_ctx_new(int is_server, X509 *cert, EVP_PKEY *pkey)
Create a DTLS SSL_CTX for the client or server role.
Definition dtls.c:120
unsigned int cwist_dtls_link_mtu(void)
Return the DTLS link MTU in bytes.
Definition dtls.c:142
BIO * cwist_dtls_bio_new(struct cwist_webrtc_conn *conn)
Datagram BIO for conn: reads return conn->dtls_in once, writes go to cwist_webrtc_conn_dtls_out() one...
Definition dtls.c:211
#define MSG_DONTWAIT
Definition http3.c:82
int cwist_ice_stun_parse_response(const uint8_t *buf, size_t len, const uint8_t txid[12], const char *pwd, struct sockaddr_in *mapped)
Validate a received STUN binding response.
Definition ice.c:518
int cwist_ice_stun_is_message(const uint8_t *buf, size_t len)
Check whether a datagram looks like a STUN message.
Definition ice.c:110
bool cwist_ice_stun_has_use_candidate(const uint8_t *buf, size_t len)
Check whether a STUN message carries the USE-CANDIDATE attribute.
Definition ice.c:197
int cwist_ice_stun_sign_request(uint8_t *msg, size_t cap, size_t msg_len, const char *pwd)
Public wrapper around stun_sign() for signing a built binding request.
Definition ice.c:474
int cwist_ice_stun_validate_request(const uint8_t *buf, size_t len, const char *pwd)
Validate the MESSAGE-INTEGRITY of a STUN message against a password.
Definition ice.c:241
int cwist_ice_stun_build_request(uint8_t *out, size_t cap, const uint8_t txid[12], const char *username)
Build an unsigned ICE-controlling STUN binding request.
Definition ice.c:361
int cwist_ice_stun_get_remote_ufrag(const uint8_t *buf, size_t len, char *out, size_t cap)
Extract the sender's ufrag from the USERNAME attribute of a STUN message.
Definition ice.c:556
bool cwist_ice_stun_is_binding_request(const uint8_t *buf, size_t len)
Check whether a datagram is a STUN binding request.
Definition ice.c:119
void cwist_ice_random_creds(char *ufrag, size_t ufrag_cap, char *pwd, size_t pwd_cap)
Generate random ICE ufrag/pwd credentials.
Definition ice.c:592
int cwist_ice_stun_build_response(uint8_t *out, size_t cap, const uint8_t *req, size_t req_len, const struct sockaddr_in *mapped, const char *pwd)
Build a STUN binding response for a received binding request.
Definition ice.c:281
size_t head
Definition otel.c:57
size_t cap
Definition otel.c:56
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
#define RAND_bytes
Definition rand.h:17
struct cwist_reactor cwist_reactor_t
Definition reactor.h:14
bool cwist_reactor_timer_arm(cwist_reactor_t *reactor, cwist_reactor_timer_t *timer, uint64_t delay_us)
Definition reactor.c:937
bool cwist_reactor_add(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
Definition reactor.c:1357
void cwist_reactor_timer_init(cwist_reactor_timer_t *timer, void(*cb)(void *ctx), void *ctx)
Definition reactor.c:929
bool cwist_reactor_del(cwist_reactor_t *reactor, int fd)
Definition reactor.c:1410
void cwist_reactor_destroy(cwist_reactor_t *reactor)
Definition reactor.c:1116
void cwist_reactor_stop(cwist_reactor_t *reactor)
Definition reactor.c:1904
cwist_reactor_t * cwist_reactor_create(void)
Definition reactor.c:1006
void cwist_reactor_run(cwist_reactor_t *reactor)
Definition reactor.c:1579
void cwist_reactor_timer_cancel(cwist_reactor_t *reactor, cwist_reactor_timer_t *timer)
Definition reactor.c:956
int cwist_sctp_conn_open(struct cwist_webrtc_conn *conn)
Create, bind and listen/connect the conn's SCTP socket.
Definition sctp.c:265
int cwist_sctp_global_init(void)
One-time usrsctp initialisation (no threads, AF_CONN only).
Definition sctp.c:106
int cwist_sctp_conn_drain(struct cwist_webrtc_conn *conn)
Accept (server role), then deliver every complete inbound message.
Definition sctp.c:343
void cwist_sctp_tick(void)
Advance usrsctp's process-wide timer clock to now. Any thread.
Definition sctp.c:111
bool cwist_sctp_assoc_established(struct cwist_webrtc_conn *conn)
true once the SCTP association is up.
Definition sctp.c:399
int cwist_sctp_send(struct cwist_webrtc_conn *conn, uint16_t channel, const uint8_t *data, size_t len, int is_string)
Send one message, announcing the channel with DCEP OPEN first if we are opening it.
Definition sctp.c:410
void cwist_sctp_conn_close(struct cwist_webrtc_conn *conn)
Abort the association and release SCTP state. Idempotent.
Definition sctp.c:296
void cwist_sctp_conn_input(struct cwist_webrtc_conn *conn, const uint8_t *data, size_t len)
Feed one decrypted SCTP packet to usrsctp.
Definition sctp.c:317
int cwist_sdp_write_answer(char *out, size_t cap, const char *fingerprint, const char *ufrag, const char *pwd, const char *mid, const char *host, uint16_t port)
Generate an SDP answer for an ICE-lite DataChannel endpoint.
Definition sdp.c:129
int cwist_sdp_parse(const char *sdp, size_t len, cwist_sdp_info *info)
Parse an SDP blob into a cwist_sdp_info structure.
Definition sdp.c:43
#define SSL_new
Definition ssl.h:166
#define SSL_ERROR_SSL
Definition ssl.h:53
#define SSL_ERROR_SYSCALL
Definition ssl.h:56
#define SSL_free
Definition ssl.h:167
#define SSL_write
Definition ssl.h:172
#define SSL_CTX_free
Definition ssl.h:99
#define SSL_read
Definition ssl.h:171
#define SSL_ERROR_ZERO_RETURN
Definition ssl.h:57
struct cwist_mtls_ctx SSL_CTX
Definition ssl.h:43
#define SSL_get_error
Definition ssl.h:175
Definition reactor.h:46
void * ctx
Definition reactor.h:49
void(* cb)(void *ctx)
Definition reactor.h:48
Fields the ICE-lite agent needs from an SDP description.
One peer: ICE state, DTLS session, SCTP association.
cwist_reactor_post_t reg_post
const uint8_t * dtls_in
cwist_webrtc_ctx * ctx
struct socket * sctp_sock
struct cwist_webrtc_conn * hnext
atomic_size_t buffered
struct sockaddr_in remote
struct cwist_webrtc_conn * dirty_next
cwist_conn_state state
atomic_bool close_posted
cwist_webrtc_msg * pending_head
cwist_webrtc_msg * pending_tail
cwist_reactor_post_t kick_post
cwist_reactor_post_t close_post
cwist_reactor_timer_t timer
struct socket * sctp_acc
A UDP endpoint bound to one reactor, serving many conns.
pthread_mutex_t free_mu
cwist_webrtc_close_cb close_cb
cwist_reactor_t * reactor
cwist_webrtc_message_cb msg_cb
cwist_reactor_post_t free_post
struct sockaddr_in tx_addr[CWIST_WEBRTC_TX_BATCH]
pthread_cond_t free_cv
cwist_webrtc_conn ** buckets
cwist_webrtc_conn * dirty
cwist_reactor_timer_t sctp_timer
cwist_webrtc_channel_cb ch_cb
atomic_int ice_lite_server
size_t tx_len[CWIST_WEBRTC_TX_BATCH]
cwist_webrtc_conn * parked
A DataChannel message on its way into SCTP.
struct cwist_webrtc_msg * next
void cwist_webrtc_conn_on_channel_open(struct cwist_webrtc_conn *conn, uint16_t channel, const char *label)
Report a channel the peer opened to the channel handler.
Definition webrtc.c:548
#define STUN_RTO_INITIAL_MS
Definition webrtc.c:38
void cwist_webrtc_conn_close(cwist_webrtc_conn *conn)
Close a connection (SCTP abort, DTLS teardown). Safe from any thread; the close handler runs on the r...
Definition webrtc.c:1227
#define RX_BUDGET
Definition webrtc.c:50
const char * cwist_webrtc_ctx_fingerprint(const cwist_webrtc_ctx *ctx)
Local certificate fingerprint (SHA-256, colon-separated hex).
Definition webrtc.c:993
void cwist_webrtc_ctx_set_close_handler(cwist_webrtc_ctx *ctx, cwist_webrtc_close_cb cb, void *user)
Set the callback for closed connections.
Definition webrtc.c:1009
void cwist_webrtc_conn_sctp_out(struct cwist_webrtc_conn *conn, const void *buffer, size_t len)
usrsctp output for conn; marshalled to the owner thread when needed.
Definition webrtc.c:577
#define SCTP_TICK_IDLE_MS
Definition webrtc.c:45
size_t cwist_webrtc_conn_buffered_amount(const cwist_webrtc_conn *conn)
Bytes accepted by cwist_webrtc_conn_send() and not yet handed to SCTP (like RTCDataChannel....
Definition webrtc.c:1019
#define UDP_SOCKBUF
Definition webrtc.c:52
cwist_webrtc_ctx * cwist_webrtc_ctx_new(uint16_t port)
Create a WebRTC context bound to a UDP port (0 picks an ephemeral port), running on its own reactor t...
Definition webrtc.c:879
cwist_webrtc_ctx * cwist_webrtc_ctx_new_on(cwist_reactor_t *reactor, uint16_t port)
Create a WebRTC context on a reactor the caller runs.
Definition webrtc.c:895
int cwist_webrtc_ctx_connection_count(const cwist_webrtc_ctx *ctx)
Number of connections whose SCTP association is up.
Definition webrtc.c:1015
cwist_webrtc_conn * cwist_webrtc_connect(cwist_webrtc_ctx *ctx, const struct sockaddr_in *remote, const char *peer_ufrag, const char *peer_pwd)
Offering-side dialer used by the loopback tests: registers a client conn to remote and starts ICE.
Definition webrtc.c:1078
void cwist_webrtc_ctx_free(cwist_webrtc_ctx *ctx)
Close every connection and free the context.
Definition webrtc.c:944
void cwist_webrtc_conn_release(cwist_webrtc_conn *conn)
Drop a reference taken with cwist_webrtc_conn_retain().
Definition webrtc.c:86
bool cwist_webrtc_on_owner(const cwist_webrtc_ctx *ctx)
true when the calling thread is servicing ctx right now.
Definition webrtc.c:100
void cwist_webrtc_conn_dtls_out(struct cwist_webrtc_conn *conn, const uint8_t *data, size_t len)
Queue one datagram to the conn's peer (owner thread).
Definition webrtc.c:554
#define SCTP_ACTIVE_WINDOW_MS
Definition webrtc.c:46
#define LIVENESS_TIMEOUT_MS
Definition webrtc.c:43
#define HANDSHAKE_TIMEOUT_MS
Definition webrtc.c:42
#define STUN_MAX_ATTEMPTS
Definition webrtc.c:40
#define SCTP_TICK_ACTIVE_MS
Definition webrtc.c:44
void cwist_webrtc_ctx_set_message_handler(cwist_webrtc_ctx *ctx, cwist_webrtc_message_cb cb, void *user)
Set the callback for incoming DataChannel messages. Set handlers before answering the first offer.
Definition webrtc.c:997
void cwist_webrtc_conn_retain(cwist_webrtc_conn *conn)
Take a reference on conn so it stays valid outside a callback.
Definition webrtc.c:82
uint16_t cwist_webrtc_ctx_port(const cwist_webrtc_ctx *ctx)
UDP port the context is bound to.
Definition webrtc.c:989
int cwist_webrtc_conn_send(cwist_webrtc_conn *conn, uint16_t channel_id, const uint8_t *data, size_t len, cwist_webrtc_data_type type)
Send a message on a DataChannel.
Definition webrtc.c:1158
int cwist_webrtc_handle_offer(cwist_webrtc_ctx *ctx, const char *offer, char *answer_out, size_t out_len)
Answer a browser SDP offer (ICE-lite, setup:passive).
Definition webrtc.c:1042
#define PARK_TIMEOUT_MS
Definition webrtc.c:41
void cwist_webrtc_conn_on_message(struct cwist_webrtc_conn *conn, uint16_t channel, const uint8_t *data, size_t len, int is_string)
Deliver a complete inbound message to the message handler.
Definition webrtc.c:540
void cwist_webrtc_ctx_set_channel_handler(cwist_webrtc_ctx *ctx, cwist_webrtc_channel_cb cb, void *user)
Set the callback for incoming DataChannel open (DCEP) events.
Definition webrtc.c:1003
#define STUN_RTO_MAX_MS
Definition webrtc.c:39
void(* cwist_webrtc_channel_cb)(cwist_webrtc_conn *conn, uint16_t channel_id, const char *label, void *user)
Optional DataChannel open notification (remote peer opened a channel).
Definition webrtc.h:57
cwist_webrtc_data_type
Definition webrtc.h:39
@ CWIST_WEBRTC_DATA_STRING
Definition webrtc.h:41
@ CWIST_WEBRTC_DATA_BINARY
Definition webrtc.h:40
void(* cwist_webrtc_close_cb)(cwist_webrtc_conn *conn, void *user)
Optional connection-closed notification. Runs once per connection whose SCTP association came up; aft...
Definition webrtc.h:65
void(* cwist_webrtc_message_cb)(cwist_webrtc_conn *conn, uint16_t channel_id, const uint8_t *data, size_t len, cwist_webrtc_data_type type, void *user)
DataChannel message callback, invoked on the ctx's reactor thread.
Definition webrtc.h:50
Internal types and cross-file helpers for the WebRTC DataChannel module (ICE-lite + DTLS + SCTP over ...
@ CWIST_CONN_CLOSED
@ CWIST_CONN_STUN
@ CWIST_CONN_DTLS
@ CWIST_CONN_ESTABLISHED
#define CWIST_WEBRTC_DGRAM_MAX
#define CWIST_WEBRTC_RX_BATCH
#define CWIST_WEBRTC_TX_BATCH
#define CWIST_WEBRTC_MAX_MESSAGE
#define CWIST_WEBRTC_MAX_BUFFERED