ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
lib/network/connection.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define NETWORK_CONNECTION_PEER_BUFFER_SIZE 256
4
5#include <ascii-chat/otel.h>
16#include <ascii-chat/buffer.h>
21#include <libplatform/system.h>
24#include <ascii-chat/util/ip.h>
26#include <ascii-chat/log/log.h>
27#include <string.h>
28
29#ifdef _WIN32
30#include <ws2tcpip.h>
31#else
32#include <netdb.h>
33#endif
34
35#define NETWORK_CONNECTION_LIMIT 512
36
51
83
88 return c ? atomic_ptr_load(&c->context) : NULL;
89}
91 atomic_ptr_store(&c->context, context);
92}
94 return c->peer;
95}
103 int expected = NETWORK_NEGOTIATING;
104 atomic_cas_int(&c->state, &expected, NETWORK_READY);
105}
107 if (!c)
108 return;
109 atomic_store_int(&c->state, NETWORK_CLOSING);
110 acip_transport_interrupt(c->transport);
111}
112
121 return SET_ERRNO(ERROR_INVALID_STATE, "Connection security already configured");
122 c->security_claimed = c->security_options = true;
123 /* QUIC protects its own endpoints, but ACIP encryption is also required
124 * for the application identity and intermediary trust boundary. */
125 bool transport_satisfies_acip_encryption = c->secure && acip_transport_get_type(c->transport) != ACIP_TRANSPORT_QUIC;
126 asciichat_error_t result =
127 acip_security_init_options(&c->security, server, transport_satisfies_acip_encryption, password);
128 if (result == ASCIICHAT_OK && !server) {
130 SAFE_STRNCPY(ctx->server_hostname, c->peer, sizeof(ctx->server_hostname));
131 SAFE_STRNCPY(ctx->server_ip, c->peer, sizeof(ctx->server_ip));
132 ctx->server_port = c->port;
133 }
134 return result;
135}
136
137static network_connection_t *connection_create(network_server_t *s, acip_transport_t *transport, const char *peer,
138 bool secure, bool ready) {
140 if (!c)
141 return NULL;
142 c->server = s;
143 c->transport = transport;
145 c->secure = secure;
146 c->authenticated_peer = ready;
147 c->started = c->last_packet = time_get_ns();
148 atomic_store_int(&c->state, ready ? NETWORK_READY : NETWORK_NEGOTIATING);
149 SAFE_STRNCPY(c->peer, peer ? peer : "", sizeof(c->peer));
150 c->policy.handshake_timeout_ms = 30000;
151 if (s) {
152 c->policy = s->config;
153 c->callbacks = s->config.callbacks;
156 mutex_lock(&s->registry_mutex);
157 if (atomic_load_bool(&s->stopping) || s->count >= s->config.max_connections) {
158 mutex_unlock(&s->registry_mutex);
160 SAFE_FREE(c);
161 SET_ERRNO(ERROR_RESOURCE_EXHAUSTED, "Connection admission limit reached");
162 return NULL;
163 }
164 c->next = s->connections;
165 s->connections = c;
166 ++s->count;
167 mutex_unlock(&s->registry_mutex);
168 }
169 return c;
170}
171
173 if (!s || !visit)
174 return;
176 size_t count = 0;
177 mutex_lock(&s->registry_mutex);
178 for (network_connection_t *c = s->connections; c; c = c->next) {
179 if (atomic_load_int(&c->state) < NETWORK_CLOSING) {
180 atomic_fetch_add_int(&c->pins, 1);
181 snapshot[count++] = c;
182 }
183 }
184 mutex_unlock(&s->registry_mutex);
185 for (size_t i = 0; i < count; ++i) {
186 visit(snapshot[i], context);
187 atomic_fetch_sub_int(&snapshot[i]->pins, 1);
188 }
189}
190
191static void connection_finish(void *arg) {
192 ASCII_OTEL_SCOPE(CONNECTION_CLEANUP);
193 network_connection_t *c = arg;
194 network_server_t *s = c->server;
195 atomic_store_int(&c->state, NETWORK_CLOSING);
196 if (s) {
197 mutex_lock(&s->registry_mutex);
199 while (*link && *link != c)
200 link = &(*link)->next;
201 if (*link)
202 *link = c->next;
203 mutex_unlock(&s->registry_mutex);
204 while (atomic_load_int(&c->pins))
206 }
207 if (c->callbacks.closing) {
209 if (result != ASCIICHAT_OK) {
210 c->result = result;
211 c->cleanup_failed = true;
212 if (s) {
213 mutex_lock(&s->registry_mutex);
214 c->next = s->connections;
215 s->connections = c;
216 mutex_unlock(&s->registry_mutex);
217 }
218 return;
219 }
220 }
221 c->cleanup_failed = false;
223 c->transport = NULL;
224 if (c->owned_quic_endpoint) {
226 if (result != ASCIICHAT_OK)
228 if (result == ASCIICHAT_OK)
230 if (result != ASCIICHAT_OK) {
231 c->result = result;
232 c->cleanup_failed = true;
233 return;
234 }
235 }
236 c->adopted_security = NULL;
237 if (c->security_options)
239 else if (c->security_claimed)
242 atomic_store_int(&c->state, NETWORK_CLOSED);
243 if (c->callbacks.closed)
245 if (s) {
246 mutex_lock(&s->registry_mutex);
247 --s->count;
248 mutex_unlock(&s->registry_mutex);
249 }
250}
251static void connection_free(void *arg) {
252 network_connection_t *c = arg;
253 if (!c->cleanup_failed) {
257 SAFE_FREE(c);
258 }
259}
260
261static asciichat_error_t connection_open(network_connection_t *c) {
262 if (c->opened)
263 return ASCIICHAT_OK;
264 c->opened = true;
266}
267
268static asciichat_error_t connection_packet(const void *data, size_t length, void *arg) {
269 ASCII_OTEL_SCOPE(NETWORK_PACKET_DISPATCH);
270 network_connection_t *c = arg;
271 if (atomic_load_int(&c->state) >= NETWORK_CLOSING)
272 return SET_ERRNO(ERROR_NETWORK, "Connection is stopping");
273 packet_header_t header;
274 memcpy(&header, data, sizeof(header));
275 packet_envelope_t envelope = {
276 .type = NET_TO_HOST_U16(header.type), .data = (uint8_t *)data + sizeof(header), .len = length - sizeof(header)};
280 acip_transport_type_t transport_type = acip_transport_get_type(c->transport);
281 bool backend_decrypted =
282 !c->stream && (transport_type == ACIP_TRANSPORT_TCP || transport_type == ACIP_TRANSPORT_QUIC);
283 if (envelope.type != PACKET_TYPE_ENCRYPTED && !backend_decrypted)
284 return SET_ERRNO(ERROR_CRYPTO, "Plaintext application packet on encrypted connection");
285 if (envelope.type == PACKET_TYPE_ENCRYPTED)
286 result = packet_decrypt_envelope(&envelope, c->transport->crypto_ctx);
287 if (result == ASCIICHAT_OK && envelope.allocated_buffer) {
288 const packet_header_t *inner = envelope.allocated_buffer;
289 if (NET_TO_HOST_U64(inner->magic) != PACKET_MAGIC || envelope.type == PACKET_TYPE_ENCRYPTED ||
290 asciichat_crc32(envelope.data, envelope.len) != NET_TO_HOST_U32(inner->crc32))
291 result = SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid encrypted packet");
292 }
293 }
294 ASCII_OTEL_PACKET(envelope.type, envelope.len, "dispatch", acip_transport_get_type(c->transport));
295 ASCII_OTEL_ATTR_INT("acip.wire.packet.type", NET_TO_HOST_U16(header.type));
296 ASCII_OTEL_ATTR_INT("acip.wire.packet.size", (int64_t)length);
297 c->last_packet = time_get_ns();
298 if (result == ASCIICHAT_OK && !envelope.consumed)
299 result = c->callbacks.packet(c, envelope.type, envelope.data, envelope.len, c->callback_context);
300 buffer_release(envelope.allocated_buffer);
301 return result;
302}
303
304static uint64_t connection_deadline(network_connection_t *c) {
306 uint64_t now = time_get_ns(), end = 0;
309 else if (c->partial_since && cfg->partial_timeout_ms)
311 else if (cfg->idle_timeout_ms)
312 end = c->last_packet + cfg->idle_timeout_ms * NS_PER_MS_INT;
313 return !end ? 0 : now >= end ? 1 : (end - now) / NS_PER_MS_INT + 1;
314}
315
316static void connection_notify_error(network_connection_t *c, asciichat_error_t reason) {
317 // A closed backend can retain queued bytes until teardown. Enqueuing a
318 // reliable rejection there would delay producer shutdown behind dead output.
319 if (!acip_transport_is_connected(c->transport))
320 return;
322 asciichat_error_t result = acip_send_error(c->transport, reason, NULL);
323 if (result == ASCIICHAT_OK && acip_transport_get_type(c->transport) == ACIP_TRANSPORT_WEBSOCKET)
325 if (result == ASCIICHAT_OK && c->stream) {
326 // This runs on a worker. Give the event loop time to deliver the rejection
327 // before stream_done closes the TCP stream and discards pending writes.
328 uint64_t deadline = time_get_ns() + 500 * NS_PER_MS_INT;
330 if (time_get_ns() >= deadline) {
331 result = SET_ERRNO(ERROR_NETWORK_TIMEOUT, "Connection rejection did not drain");
332 break;
333 }
335 }
336 }
337 if (result != ASCIICHAT_OK)
338 log_debug("Could not send connection error to %s: %s", c->peer, asciichat_error_format(NULL, result));
340}
341
342static void connection_work(void *arg) {
343 network_connection_t *c = arg;
344 c->result = connection_open(c);
345 if (c->result == ASCIICHAT_OK && c->input_length) {
346 uint64_t previous_packet = c->last_packet;
347 bool partial = packet_decoder_pending(&c->decoder);
348 c->result = packet_decoder_feed(&c->decoder, c->input, c->input_length, connection_packet, c);
349 if (packet_decoder_pending(&c->decoder) && (!partial || previous_packet != c->last_packet))
350 c->partial_since = time_get_ns();
352 c->partial_since = 0;
353 }
354 if (c->result != ASCIICHAT_OK && c->server && c->stream)
355 connection_notify_error(c, c->result);
356}
357static void stream_done(platform_event_stream_t *stream, void *arg, bool closing) {
358 network_connection_t *c = arg;
359 if (closing)
360 return;
363 return;
364 }
365 asciichat_error_t result = platform_event_stream_deadline(stream, connection_deadline(c));
366 if (result == ASCIICHAT_OK)
367 result = platform_event_stream_resume(stream);
368 if (result != ASCIICHAT_OK)
369 platform_event_stream_close(stream, result);
370}
371static void stream_read(platform_event_stream_t *stream, const void *data, size_t length, void *arg) {
372 network_connection_t *c = arg;
373 c->input = data;
374 c->input_length = length;
375 asciichat_error_t result = platform_event_stream_work(stream, connection_work, stream_done, c);
376 if (result != ASCIICHAT_OK)
377 platform_event_stream_close(stream, result);
378}
379static void stream_closed(void *arg, asciichat_error_t reason) {
380 network_connection_t *c = arg;
381 log_debug("TCP connection %s closed: %s", c->peer, asciichat_error_format(NULL, reason));
382 c->result = reason;
384 c->stream = NULL;
385 if (platform_event_loop_work(c->server->listener.event_loop, connection_finish, connection_free, c) != ASCIICHAT_OK) {
386 c->cleanup_failed = true;
388 }
389}
390static void listener_accept(platform_event_stream_t *stream, void *arg) {
391 network_server_t *s = arg;
392 char peer[INET6_ADDRSTRLEN] = "";
393 struct sockaddr_storage address;
394 socklen_t length = sizeof(address);
395 if (platform_event_stream_peer(stream, &address, &length) == ASCIICHAT_OK)
396 format_ip_address(address.ss_family, (struct sockaddr *)&address, peer, sizeof(peer));
397 acip_transport_t *transport = acip_async_tcp_transport_create(stream, NULL);
398 network_connection_t *c = transport ? connection_create(s, transport, peer, false, false) : NULL;
399 if (!c) {
400 if (transport) {
402 acip_transport_destroy(transport);
403 }
405 return;
406 }
407 c->stream = stream;
408 asciichat_error_t result = platform_event_stream_start(stream, stream_read, stream_closed, c);
409 if (result == ASCIICHAT_OK)
410 result = platform_event_stream_deadline(stream, connection_deadline(c));
411 if (result == ASCIICHAT_OK)
412 result = platform_event_stream_work(stream, connection_work, stream_done, c);
413 if (result != ASCIICHAT_OK)
414 platform_event_stream_close(stream, result);
415}
416
417static void *connection_receive(void *arg) {
418 network_connection_t *c = arg;
419 asciichat_errno_scope_t open_scope = {0};
420 if (c->server)
421 open_scope = errno_scope_begin();
422 asciichat_error_t result = connection_open(c);
423 if (result != ASCIICHAT_OK && c->server)
424 connection_notify_error(c, result);
425 if (c->server)
428 while (result == ASCIICHAT_OK && !APP_CALLBACK_BOOL(should_exit) &&
429 (!c->server || !atomic_load_bool(&c->server->stopping)) && network_connection_state(c) < NETWORK_CLOSING) {
430 if (connection_deadline(c) == 1) {
431 result = ERROR_NETWORK_TIMEOUT;
432 if (c->server)
433 connection_notify_error(c, result);
434 break;
435 }
436 void *data = NULL, *allocation = NULL;
437 size_t length = 0;
439 result = acip_transport_recv(c->transport, &data, &length, &allocation);
440 if (result == ERROR_NETWORK_TIMEOUT || result == ERROR_NETWORK_INCOMPLETE) {
441 if (result == ERROR_NETWORK_INCOMPLETE && !c->partial_since)
442 c->partial_since = time_get_ns();
444 buffer_release(allocation);
445 result = ASCIICHAT_OK;
446 continue;
447 }
448 if (result == ASCIICHAT_OK) {
449 c->input = data;
450 c->input_length = length;
451 connection_work(c);
452 result = c->result;
453 }
454 if (result != ASCIICHAT_OK && c->server)
455 connection_notify_error(c, result);
456 buffer_release(allocation);
458 }
459 log_debug("Connection %s receive stopped: %s", c->peer, asciichat_error_format(NULL, result));
460 acip_transport_close(c->transport);
461 c->result = result;
462 if (c->server) {
463 connection_finish(c);
464 connection_free(c);
465 }
466 return NULL;
467}
468static void *websocket_accept(void *arg) {
470 network_connection_t *c = connection_create(ctx->user_data, ctx->transport, ctx->client_ip, ctx->is_secure, false);
471 if (!c)
473 SAFE_FREE(ctx);
474 return c ? connection_receive(c) : NULL;
475}
476static asciichat_error_t server_adopt(network_server_t *s, acip_transport_t *transport, const char *peer, bool secure,
477 bool ready) {
478 if (!s || !transport)
479 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing adopted connection");
480 network_connection_t *c = connection_create(s, transport, peer, secure, ready);
481 if (!c)
482 return SET_ERRNO(ERROR_RESOURCE_EXHAUSTED, "Cannot admit peer connection");
483 asciichat_error_t result = thread_pool_spawn(s->peers, connection_receive, c, 0, "network_peer");
484 if (result != ASCIICHAT_OK) {
485 c->transport = NULL;
486 connection_finish(c);
487 connection_free(c);
488 }
489 return result;
490}
492 bool secure) {
493 return server_adopt(s, transport, peer, secure, true);
494}
496 bool secure) {
497 return server_adopt(s, transport, peer, secure, false);
498}
499static void *websocket_run(void *arg) {
501 return NULL;
502}
503static void server_tick(void *arg) {
504 network_server_t *s = arg;
506 size_t count = 0;
507 mutex_lock(&s->registry_mutex);
508 for (network_connection_t *c = s->connections; c; c = c->next)
510 closing[count++] = c->stream;
511 mutex_unlock(&s->registry_mutex);
512 for (size_t i = 0; i < count; ++i)
515 bool *failed[] = {&s->quic_failed, &s->webtransport_failed};
516 for (size_t listener = 0; listener < 2 && !atomic_load_bool(&s->stopping); ++listener) {
517 if (!listeners[listener] || *failed[listener])
518 continue;
519 for (unsigned i = 0; i < 16; ++i) {
520 acip_transport_t *transport = NULL;
522 asciichat_error_t result = quic_accept(listeners[listener], &transport);
523 if (result == ERROR_NETWORK_TIMEOUT) {
525 break;
526 }
527 if (result != ASCIICHAT_OK) {
528 log_warn("QUIC listener stopped accepting: %s", asciichat_error_format(NULL, result));
529 *failed[listener] = true;
531 break;
532 }
533 struct sockaddr_storage address;
534 socklen_t length = 0;
535 char peer[INET6_ADDRSTRLEN] = "";
536 result = quic_transport_peer_address(transport, &address, &length);
537 if (result == ASCIICHAT_OK)
538 result = format_ip_address(address.ss_family, (const struct sockaddr *)&address, peer, sizeof(peer));
539 if (result == ASCIICHAT_OK)
540 result = network_server_adopt_negotiating(s, transport, peer, true);
541 if (result != ASCIICHAT_OK) {
542 log_debug("Cannot admit QUIC peer: %s", asciichat_error_format(NULL, result));
543 acip_transport_destroy(transport);
544 }
546 }
547 }
548 if (atomic_load_bool(&s->stopping))
549 atomic_store_bool(&s->listener.running, false);
550 else if (s->config.tick)
552}
554 if (!config || !out || !config->callbacks.packet)
555 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing network server configuration");
556 *out = NULL;
557 network_server_t *s = SAFE_CALLOC(1, sizeof(*s), network_server_t *);
558 if (!s)
559 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate network server");
560 s->config = *config;
564 s->config.handshake_timeout_ms = 30000;
566 s->config.partial_timeout_ms = 5000;
567 asciichat_error_t result = mutex_init(&s->registry_mutex, "network_registry");
568 if (result != ASCIICHAT_OK) {
569 SAFE_FREE(s);
570 return result;
571 }
572 s->peers = thread_pool_create("network_peers");
573 if (!s->peers) {
574 mutex_destroy(&s->registry_mutex);
575 SAFE_FREE(s);
576 return SET_ERRNO(ERROR_THREAD, "Cannot create peer workers");
577 }
578 tcp_server_config_t tcp = {.port = config->port,
579 .ipv4_address = config->ipv4_address,
580 .ipv6_address = config->ipv6_address,
581 .bind_ipv4 = config->bind_ipv4,
582 .bind_ipv6 = config->bind_ipv6,
583 .event_handler = listener_accept,
584 .user_data = s,
585 .tick_fn = server_tick,
586 .tick_user_data = s};
587 result = tcp_server_init(&s->listener, &tcp);
588 s->listener_initialized = result == ASCIICHAT_OK;
589 if (result == ASCIICHAT_OK && config->websocket_enabled) {
590 websocket_server_config_t ws = {.port = config->websocket_port,
591 .bind_address = config->ipv4_address ? config->ipv4_address : config->ipv6_address,
592 .client_handler = websocket_accept,
593 .user_data = s,
594 .tls_cert_path = config->tls_cert_path,
595 .tls_key_path = config->tls_key_path};
596 result = websocket_server_init(&s->websocket, &ws);
597 s->websocket_initialized = result == ASCIICHAT_OK;
598 }
599 if (result != ASCIICHAT_OK) {
601 return result;
602 }
603 *out = s;
604 return ASCIICHAT_OK;
605}
607 if (!s || atomic_load_bool(&s->stopping))
608 return SET_ERRNO(ERROR_INVALID_STATE, "Network server stopped before startup");
610 asciichat_error_t result =
611 platform_thread_create(&s->websocket_thread, "websocket_io", websocket_run, &s->websocket);
612 if (result != ASCIICHAT_OK)
613 return result;
614 s->websocket_started = true;
615 }
616 return ASCIICHAT_OK;
617}
619 int expected = false;
620 if (!s || !atomic_cas_int(&s->executing, &expected, true))
621 return SET_ERRNO(ERROR_INVALID_STATE, "Network server already has an execution owner");
623 atomic_store_bool(&s->executing, false);
624 return result;
625}
627 if (s)
628 atomic_store_bool(&s->stopping, true);
629}
630static void stop_connection(network_connection_t *c, void *unused) {
631 (void)unused;
633}
635 if (!s)
636 return ASCIICHAT_OK;
638 if (atomic_load_bool(&s->executing))
639 return SET_ERRNO(ERROR_THREAD, "Join the network execution owner before releasing server dependencies");
640 network_server_foreach(s, stop_connection, NULL);
641 if (s->websocket_started) {
642 atomic_store_bool(&s->websocket.running, false);
644 asciichat_error_t result = thread_join(&s->websocket_thread, NULL);
645 if (result != ASCIICHAT_OK)
646 return result;
647 s->websocket_started = false;
648 }
649 if (s->websocket_initialized) {
651 s->websocket_initialized = false;
652 }
654 if (result != ASCIICHAT_OK)
655 return result;
657 while (c) {
658 network_connection_t *next = c->next;
659 if (c->cleanup_failed) {
660 connection_finish(c);
661 if (c->cleanup_failed)
662 return c->result;
663 connection_free(c);
664 }
665 c = next;
666 }
667 return s->count ? SET_ERRNO(ERROR_THREAD, "Connections still retain server dependencies") : ASCIICHAT_OK;
668}
670 if (!owner || !*owner)
671 return;
672 network_server_t *s = *owner;
674 return;
678 mutex_destroy(&s->registry_mutex);
681 SAFE_FREE(*owner);
682 *owner = NULL;
683}
685 if (!s)
686 return 0;
687 mutex_lock(&s->registry_mutex);
688 size_t count = s->count;
689 mutex_unlock(&s->registry_mutex);
690 return count;
691}
696 return s ? &s->config : NULL;
697}
698
699static bool connect_cancelled(void *data) {
700 const network_connect_config_t *config = data;
702 (config && config->should_exit_callback && config->should_exit_callback(config->callback_data));
703}
704static asciichat_error_t connection_open_tcp(const char *name, const connection_endpoint_t *endpoint,
705 const network_connect_config_t *settings, crypto_context_t *crypto,
707 (void)name;
708 socket_t socket = INVALID_SOCKET_VALUE;
709 parallel_connect_config_t config = {.hostname = endpoint->host,
710 .port = endpoint->port,
711 .timeout_ms = settings ? settings->timeout_ms : 3000,
712 .should_exit_callback = connect_cancelled,
713 .callback_data = (void *)settings};
714 asciichat_error_t result = parallel_connect(&config, &socket);
715 if (result != ASCIICHAT_OK)
716 return result;
717 *out = acip_async_tcp_client_create(&socket, crypto);
718 if (socket != INVALID_SOCKET_VALUE)
719 socket_close(socket);
720 return *out ? ASCIICHAT_OK : SET_ERRNO(ERROR_NETWORK, "Cannot adopt connected TCP socket");
721}
722
723static asciichat_error_t connection_open_quic(const connection_endpoint_t *endpoint,
724 const network_connect_config_t *settings, quic_endpoint_t **owner,
725 acip_transport_t **transport) {
726 struct sockaddr_storage remote, local;
727 socklen_t remote_length = 0, local_length = sizeof(local);
729 asciichat_error_t result = parse_numeric_socket_address(endpoint->host, endpoint->port, &remote, &remote_length);
730 if (result != ASCIICHAT_OK) {
732 char resolved[INET_ADDRSTRLEN];
733 result = platform_resolve_hostname_to_ipv4(endpoint->host, resolved, sizeof(resolved));
734 if (result != ASCIICHAT_OK)
735 return result;
736 result = parse_numeric_socket_address(resolved, endpoint->port, &remote, &remote_length);
737 } else {
739 }
740 if (result != ASCIICHAT_OK)
741 return result;
742 socket_t route = socket_create("quic_route", remote.ss_family, SOCK_DGRAM, 0);
743 if (route == INVALID_SOCKET_VALUE)
744 return SET_ERRNO(ERROR_NETWORK, "Cannot determine QUIC local route");
745 if (socket_connect(route, (const struct sockaddr *)&remote, remote_length) != 0 ||
746 socket_getsockname(route, (struct sockaddr *)&local, &local_length) != 0) {
747 socket_close(route);
748 return SET_ERRNO(ERROR_NETWORK, "Cannot determine QUIC local route");
749 }
750 socket_close(route);
751 if (local.ss_family == AF_INET)
752 ((struct sockaddr_in *)&local)->sin_port = 0;
753 else if (local.ss_family == AF_INET6)
754 ((struct sockaddr_in6 *)&local)->sin6_port = 0;
755 else
756 return SET_ERRNO(ERROR_NETWORK, "Unsupported QUIC local route");
757 quic_config_t config = {.ca_file = settings ? settings->tls_ca_file : NULL,
758 .enable_datagrams = true,
759 .prefer_audio_datagrams = settings && settings->quic_audio_datagrams,
760 .prefer_visual_datagrams = settings && settings->quic_visual_datagrams};
761 result = quic_endpoint_open((struct sockaddr *)&local, local_length, false, &config, owner);
762 if (result == ASCIICHAT_OK)
763 result = quic_connect(*owner, (struct sockaddr *)&remote, remote_length, endpoint->host, transport);
764 if (result == ASCIICHAT_OK) {
765 uint64_t start = time_get_ns();
766 uint64_t timeout = (settings ? (uint64_t)settings->timeout_ms : 10000) * NS_PER_MS_INT;
767 for (;;) {
770 result = acip_transport_get_capabilities(*transport, &capabilities);
771 if (result == ASCIICHAT_OK) {
773 break;
774 }
775 bool cancelled = connect_cancelled((void *)settings);
776 if (result != ERROR_INVALID_STATE || cancelled || time_get_ns() - start >= timeout) {
777 if (result == ERROR_INVALID_STATE) {
779 result = cancelled ? SET_ERRNO(ERROR_NETWORK_CONNECT, "QUIC connection cancelled")
780 : SET_ERRNO(ERROR_NETWORK_TIMEOUT, "QUIC TLS connection timed out");
781 } else {
783 }
784 break;
785 }
788 }
789 }
790 if (result != ASCIICHAT_OK) {
791 if (*transport) {
792 acip_transport_destroy(*transport);
793 *transport = NULL;
794 }
795 if (*owner) {
796 (void)quic_endpoint_stop(*owner);
798 if (cleanup != ASCIICHAT_OK || *owner)
799 return SET_ERRNO(ERROR_THREAD, "Cannot clean up failed QUIC connection");
800 }
801 return SET_ERRNO(result, "QUIC connection establishment failed");
802 }
803 return result;
804}
805
806asciichat_error_t network_connection_open(const char *name, const char *address, uint16_t port,
808 connection_endpoint_t *resolved) {
809 ASCII_OTEL_SCOPE(CONNECTION_CREATE);
810 if (!out)
811 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing connection output");
812 *out = NULL;
813 if (connect_cancelled((void *)settings))
814 return SET_ERRNO(ERROR_NETWORK_CONNECT, "Connection establishment cancelled");
815 connection_endpoint_t endpoint;
816 asciichat_error_t result = connection_endpoint_resolve(address, port, &endpoint);
817 if (result != ASCIICHAT_OK)
818 return result;
819 acip_transport_t *transport = NULL;
821 ASCII_OTEL_ATTR_STRING("network.connection.endpoint_protocol",
822 endpoint.protocol == CONNECTION_ENDPOINT_WEBSOCKET ? "websocket"
823 : endpoint.protocol == CONNECTION_ENDPOINT_QUIC ? "quic"
824 : "tcp");
825 ASCII_OTEL_ATTR_INT("server.port", endpoint.port);
826 ASCII_OTEL_ATTR_INT("network.connection.timeout_ms", settings ? settings->timeout_ms
827 : endpoint.protocol == CONNECTION_ENDPOINT_WEBSOCKET ? 15000
828 : 3000);
829 if (endpoint.protocol == CONNECTION_ENDPOINT_QUIC) {
830 result = connection_open_quic(&endpoint, settings, &quic_endpoint, &transport);
831 if (result != ASCIICHAT_OK)
832 return result;
833 log_info("Connected to %s:%u using native QUIC", endpoint.host, (unsigned)endpoint.port);
834 } else if (endpoint.protocol == CONNECTION_ENDPOINT_WEBSOCKET) {
835 transport = acip_websocket_client_transport_create(name, endpoint.input, NULL);
836 } else {
837 result = connection_open_tcp(name, &endpoint, settings, NULL, &transport);
838 if (result != ASCIICHAT_OK)
839 return result;
840 }
841 if (!transport)
842 return SET_ERRNO(ERROR_NETWORK_CONNECT, "Cannot establish connection to %s", address);
843 if (endpoint.protocol == CONNECTION_ENDPOINT_WEBSOCKET) {
844 uint64_t deadline =
845 time_get_ns() + (settings ? (uint64_t)settings->timeout_ms * NS_PER_MS_INT : 15 * NS_PER_SEC_INT);
846 while (!acip_transport_is_connected(transport) && time_get_ns() < deadline && !connect_cancelled((void *)settings))
848 if (!acip_transport_is_connected(transport)) {
849 acip_transport_destroy(transport);
850 return SET_ERRNO(ERROR_NETWORK_CONNECT, "WebSocket establishment failed or cancelled");
851 }
852 }
853 network_connection_t *c = connection_create(NULL, transport, endpoint.host,
855 strncmp(endpoint.input, "wss://", sizeof("wss://") - 1) == 0,
856 false);
857 if (!c) {
858 acip_transport_destroy(transport);
859 if (quic_endpoint) {
862 }
863 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate connection");
864 }
865 if (resolved)
866 *resolved = endpoint;
867 c->port = endpoint.port;
869 *out = c;
870 ascii_otel_metric(ASCII_OTEL_METRIC_CONNECTION_ESTABLISHED, 1);
871 return ASCIICHAT_OK;
872}
874 if (!transport || !out)
875 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing connection adoption parameters");
876 *out = connection_create(NULL, transport, "peer", true, true);
877 return *out ? ASCIICHAT_OK : SET_ERRNO(ERROR_MEMORY, "Cannot own authenticated transport");
878}
881 if (!transport || !out)
882 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing connection adoption parameters");
883 *out = connection_create(NULL, transport, peer, secure, false);
884 return *out ? ASCIICHAT_OK : SET_ERRNO(ERROR_MEMORY, "Cannot own negotiating transport");
885}
887 if (!owner || !*owner)
888 return;
889 connection_finish(*owner);
890 if ((*owner)->cleanup_failed)
891 return;
892 connection_free(*owner);
893 *owner = NULL;
894}
895
899
901 void *context) {
902 if (!c || c->server || !callbacks || !callbacks->packet || c->opened)
903 return SET_ERRNO(ERROR_INVALID_STATE, "Connection already has an execution owner");
904 c->callbacks = *callbacks;
905 c->callback_context = context;
906 connection_receive(c);
907 return c->result;
908}
Application-level callbacks for library code.
#define APP_CALLBACK_BOOL(callback_name)
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
asciichat_errno_scope_t errno_scope_begin(void)
const char * asciichat_error_format(const char *message, asciichat_error_t code)
#define SET_ERRNO(code,...)
@ ASCIICHAT_ERRNO_HANDLED
@ ASCIICHAT_ERRNO_DISMISSED
acip_transport_t * acip_async_tcp_transport_create(platform_event_stream_t *stream, crypto_context_t *crypto)
Definition async.c:407
void acip_async_tcp_transport_detach(acip_transport_t *transport)
Definition async.c:262
acip_transport_t * acip_async_tcp_client_create(socket_t *socket, crypto_context_t *crypto)
Definition async.c:525
#define buffer_release(data)
Definition buffer.h:91
asciichat_error_t connection_endpoint_resolve(const char *input, uint16_t default_port, connection_endpoint_t *endpoint_out)
For connection endpoint, this is the live record that ties protocol, port, has_explicit_port together...
@ CONNECTION_ENDPOINT_QUIC
@ CONNECTION_ENDPOINT_WEBSOCKET
Hardware-Accelerated CRC32 Checksum Computation.
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
Named object registry for debugging โ€” log identifiable resource names.
๐Ÿ”„ Network byte order conversion helpers
#define NET_TO_HOST_U64(val)
Definition endian.h:228
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
asciichat_error_t quic_accept(quic_endpoint_t *endpoint, acip_transport_t **out)
Definition endpoint.c:255
asciichat_error_t quic_connect(quic_endpoint_t *endpoint, const struct sockaddr *remote, socklen_t remote_length, const char *server_name, acip_transport_t **out)
Definition endpoint.c:229
asciichat_error_t quic_endpoint_open(const struct sockaddr *address, socklen_t length, bool listener, const quic_config_t *config, quic_endpoint_t **out)
Definition endpoint.c:131
asciichat_error_t quic_endpoint_stop(quic_endpoint_t *endpoint)
Definition endpoint.c:308
asciichat_error_t quic_endpoint_destroy(quic_endpoint_t **endpoint)
Definition endpoint.c:353
asciichat_error_t platform_event_stream_resume(platform_event_stream_t *stream)
Definition event.c:346
size_t platform_event_stream_queued_bytes(const platform_event_stream_t *stream)
Definition event.c:703
void platform_event_stream_close(platform_event_stream_t *stream, asciichat_error_t reason)
Definition event.c:120
asciichat_error_t platform_event_stream_peer(platform_event_stream_t *stream, struct sockaddr_storage *address, socklen_t *length)
Definition event.c:822
asciichat_error_t platform_event_stream_start(platform_event_stream_t *stream, platform_event_read_fn read, platform_event_close_fn close, void *arg)
Definition event.c:326
asciichat_error_t platform_event_stream_work(platform_event_stream_t *stream, platform_event_work_fn run, platform_event_work_done_fn done, void *arg)
Definition event.c:801
asciichat_error_t platform_event_loop_work(platform_event_loop_t *loop, platform_event_task_fn run, platform_event_task_fn done, void *arg)
Definition event.c:768
asciichat_error_t platform_event_stream_deadline(platform_event_stream_t *stream, uint64_t timeout_ms)
Definition event.c:357
struct platform_event_stream platform_event_stream_t
Definition event.h:12
unsigned short uint16_t
Definition common.h:61
#define MIN(a, b)
Return the minimum of two values.
Definition common.h:170
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_NETWORK_CONNECT
Definition error_codes.h:74
@ ERROR_NETWORK_INCOMPLETE
Definition error_codes.h:90
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_RESOURCE_EXHAUSTED
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_CRYPTO
Definition error_codes.h:93
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:75
@ ERROR_THREAD
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define NS_PER_SEC_INT
Definition time.h:169
#define NS_PER_MS_INT
Definition time.h:168
asciichat_error_t packet_decrypt_envelope(packet_envelope_t *envelope, void *crypto_ctx)
Send a clear console packet.
Definition packet.c:665
#define PACKET_MAGIC
Packet magic number (alias for MAGIC_PACKET_VALID).
Definition packet.h:270
@ PACKET_TYPE_ENCRYPTED
Encrypted packet (after handshake completion).
Definition packet.h:355
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
asciichat_error_t platform_resolve_hostname_to_ipv4(const char *hostname, char *ipv4_out, size_t ipv4_out_size)
Resolve hostname to IPv4 address.
struct acip_transport acip_transport_t
Forward declaration of ACIP transport.
Definition host.h:506
#define asciichat_crc32(data, len)
Main CRC32 dispatcher macro - use this in application code.
Definition crc32.h:147
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Format IP address from socket address structure.
int socket_t
struct acip_security acip_security_t
acip security is intentionally a small, concrete record. Fields such as handshake,...
acip_transport_type_t
Transport method table (virtual function table).
@ ACIP_TRANSPORT_TCP
Raw TCP socket.
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
@ ACIP_TRANSPORT_QUIC
QUIC/UDP (future).
struct quic_endpoint quic_endpoint_t
void(* network_connection_visit_fn)(network_connection_t *, void *)
struct network_server network_server_t
struct network_connection network_connection_t
struct tcp_server tcp_server_t
WebSocket server for accepting browser client connections.
struct thread_pool thread_pool_t
One queued function call waiting for a thread-pool worker.
struct websocket_server websocket_server_t
asciichat_error_t parse_numeric_socket_address(const char *host, uint16_t port, struct sockaddr_storage *address, socklen_t *length)
Definition ip.c:154
๐ŸŒ IP Address Parsing and Formatting Utilities
void acip_security_cleanup(acip_security_t *security)
void network_connection_destroy(network_connection_t **owner)
asciichat_error_t network_connection_open(const char *name, const char *address, uint16_t port, const network_connect_config_t *settings, network_connection_t **out, connection_endpoint_t *resolved)
void network_connection_ready(network_connection_t *c)
asciichat_error_t network_server_create(const network_server_config_t *config, network_server_t **out)
network_connection_state_t network_connection_state(network_connection_t *c)
void network_connection_request_stop(network_connection_t *c)
const char * network_connection_peer(network_connection_t *c)
void network_connection_set_context(network_connection_t *c, void *context)
asciichat_error_t network_connection_prepare_security(network_connection_t *c, bool server, const char *password)
size_t network_server_connection_count(network_server_t *s)
asciichat_error_t network_server_start(network_server_t *s)
const network_server_config_t * network_server_config(network_server_t *s)
void * network_connection_context(network_connection_t *c)
crypto_handshake_context_t * network_connection_handshake(network_connection_t *c)
bool network_connection_secure(network_connection_t *c)
acip_transport_t * network_connection_transport(network_connection_t *c)
void network_server_destroy(network_server_t **owner)
asciichat_error_t network_server_adopt(network_server_t *s, acip_transport_t *transport, const char *peer, bool secure)
void network_server_request_stop(network_server_t *s)
uint8_t network_connection_crypto_mode(network_connection_t *c)
asciichat_error_t network_connection_adopt(acip_transport_t *transport, network_connection_t **out)
#define NETWORK_CONNECTION_PEER_BUFFER_SIZE
asciichat_error_t network_server_stop(network_server_t *s)
asciichat_error_t network_server_run(network_server_t *s)
bool network_server_websocket_enabled(network_server_t *s)
asciichat_error_t network_server_adopt_negotiating(network_server_t *s, acip_transport_t *transport, const char *peer, bool secure)
asciichat_error_t network_connection_adopt_negotiating(acip_transport_t *transport, const char *peer, bool secure, network_connection_t **out)
#define NETWORK_CONNECTION_LIMIT
asciichat_error_t network_connection_run(network_connection_t *c, const network_connection_callbacks_t *callbacks, void *context)
void network_server_foreach(network_server_t *s, network_connection_visit_fn visit, void *context)
void tcp_server_destroy(tcp_server_t *server)
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
asciichat_error_t tcp_server_run(tcp_server_t *server)
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
Initialize WebSocket server.
asciichat_error_t websocket_server_run(websocket_server_t *server)
Run WebSocket server event loop.
void websocket_server_destroy(websocket_server_t *server)
Destroy WebSocket server and free resources.
void websocket_server_cancel_service(websocket_server_t *server)
Wake the WebSocket server event loop immediately.
๐Ÿ“ Logging API with multiple log levels and terminal output control
bool should_exit(void)
Definition main.c:249
๐ŸŒ Core network I/O operations with timeout support
bool packet_decoder_pending(const packet_decoder_t *decoder)
void packet_decoder_reset(packet_decoder_t *decoder)
asciichat_error_t packet_decoder_feed(packet_decoder_t *decoder, const void *data, size_t length, packet_decoder_fn emit, void *arg)
void ascii_otel_metric(ascii_otel_metric_t metric, double value)
Definition otel.c:161
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
#define ASCII_OTEL_PACKET(type, size, action, transport)
Definition otel.h:68
#define ASCII_OTEL_ATTR_INT(key, value)
Definition otel.h:67
#define ASCII_OTEL_ATTR_STRING(key, value)
Definition otel.h:66
asciichat_error_t parallel_connect(const parallel_connect_config_t *config, socket_t *out_socket)
Parallel IPv4/IPv6 connection with race-to-connect semantics.
static bool size_t gotel_query_t * out
Definition queries.h:8
asciichat_error_t quic_transport_peer_address(acip_transport_t *transport, struct sockaddr_storage *address, socklen_t *length)
asciichat_error_t acip_security_init_options(acip_security_t *security, bool server, bool transport_secure, const char *password_override)
asciichat_error_t acip_transport_get_capabilities(acip_transport_t *transport, acip_transport_capabilities_t *out)
Definition send.c:104
asciichat_error_t acip_send_error(acip_transport_t *transport, uint32_t error_code, const char *message)
Send error message packet.
Definition send.c:415
ACIP shared/bidirectional packet sending functions.
๐Ÿ”ค String Manipulation and Shell Escaping Utilities
crypto_handshake_context_t handshake
The useful part of acip transport capabilities is in independent_streams, datagrams,...
crypto_context_t * crypto_ctx
Optional encryption context.
uint64_t receive_timeout_ns
Optional TCP receive timeout override; zero uses the normal polling timeout.
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
connection_endpoint_protocol_t protocol
Endpoint cryptographic state for handshake, packet protection, authentication, and rekeying.
Cryptographic handshake context structure.
For network connect config, this is the live record that ties timeout_ms, callback_data,...
Callbacks and operations used by network connection callbacks. Packet payloads are borrowed until ret...
asciichat_error_t(* open)(network_connection_t *, void *)
asciichat_error_t(* closing)(network_connection_t *, void *)
void(* closed)(network_connection_t *, void *)
asciichat_error_t(* packet)(network_connection_t *, packet_type_t, const void *, size_t, void *)
network server is intentionally a small, concrete record. Fields such as config, listener,...
network_server_t * server
acip_transport_t * transport
acip_security_t * adopted_security
network_connection_callbacks_t callbacks
quic_endpoint_t * owned_quic_endpoint
platform_event_stream_t * stream
network_server_config_t policy
network_connection_t * next
network_connection_callbacks_t callbacks
websocket_server_t websocket
network_server_config_t config
network_connection_t * connections
thread_pool_t * peers
platform_thread_t websocket_thread
When code needs to work with packet decoder, it keeps header_used, packet, used, size here instead of...
size_t max_payload
Zero uses MAX_PACKET_SIZE; set before the first feed.
Packet envelope containing received packet data.
Definition packet.h:1185
Fixed prefix shared by every ACIP packet on the wire.
Definition packet.h:628
uint64_t magic
Magic number (PACKET_MAGIC = 0xA5C11C4A1 "ASCIICHAT" in hex) for packet validation.
Definition packet.h:630
uint32_t crc32
CRC32 checksum of payload data (0 if length == 0).
Definition packet.h:636
platform_event_loop_t * event_loop
Owned while the asynchronous server runs.
atomic_t running
Server running flag (set false to shutdown).
Listener settings and callbacks used to create a WebSocket server.
acip_transport_t * transport
ACIP transport for this client.
char client_ip[64]
Client IP address (for logging/rate limiting).
void * user_data
User-provided data from server config.
bool is_secure
True if connection is using TLS (wss://).
Listener settings and callbacks used to create a WebSocket server.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool (long-lived threads mode).
Definition thread_pool.c:92
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
Stop all threads in the pool in stop_id order.
๐Ÿงต Generic thread pool abstraction for managing worker threads
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
acip_security_t * acip_webrtc_transport_security(acip_transport_t *transport)
acip_transport_t * acip_websocket_client_transport_create(const char *name, const char *url, crypto_context_t *crypto_ctx)
Create WebSocket client transport.
asciichat_error_t acip_websocket_transport_flush(acip_transport_t *transport, uint64_t timeout_ns)
Create WebSocket client transport.