103#include <stdatomic.h>
109#include <netinet/tcp.h>
149#define DEBUG_NETWORK 1
150#define DEBUG_THREADS 1
151#define DEBUG_MEMORY 1
154static inline void cleanup_client_all_buffers(
client_info_t *client);
156static void handle_client_error_packet(
client_info_t *client,
const void *data,
size_t len) {
165 log_warn(
"Failed to parse error packet from client %u: %s", client_id, asciichat_error_string(parse_result));
169 log_error(
"Client %u reported error %d (%s): %s", client_id, reported_error, asciichat_error_string(reported_error),
245 if (client_id == 0) {
260 log_warn(
"Client not found for ID %u", client_id);
319 log_warn(
"Failed to set socket keepalive for client %u: %s", client_id, asciichat_error_string(keepalive_result));
323 const int SOCKET_SEND_BUFFER_SIZE = 1024 * 1024;
324 const int SOCKET_RECV_BUFFER_SIZE = 1024 * 1024;
326 if (
socket_setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &SOCKET_SEND_BUFFER_SIZE,
sizeof(SOCKET_SEND_BUFFER_SIZE)) < 0) {
330 if (
socket_setsockopt(socket, SOL_SOCKET, SO_RCVBUF, &SOCKET_RECV_BUFFER_SIZE,
sizeof(SOCKET_RECV_BUFFER_SIZE)) < 0) {
335 const int TCP_NODELAY_VALUE = 1;
336 if (
socket_setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &TCP_NODELAY_VALUE,
sizeof(TCP_NODELAY_VALUE)) < 0) {
343 const
char *client_ip,
int port) {
348 int existing_count = 0;
360 if (existing_count >=
GET_OPTION(max_clients)) {
364 log_error(
"Maximum client limit reached (%d/%d active clients)", existing_count,
GET_OPTION(max_clients));
368 const char *reject_msg =
"SERVER_FULL: Maximum client limit reached\n";
369 ssize_t send_result =
socket_send(socket, reject_msg, strlen(reject_msg), 0);
370 if (send_result < 0) {
384 const char *reject_msg =
"SERVER_FULL: Maximum client limit reached\n";
385 ssize_t send_result =
socket_send(socket, reject_msg, strlen(reject_msg), 0);
386 if (send_result < 0) {
402 atomic_store(&client->
client_id, new_client_id);
405 atomic_store(&client->
active,
true);
406 log_info(
"Added new client ID=%u from %s:%d (socket=%d, slot=%d)", new_client_id, client_ip, port, socket, slot);
410 log_debug(
"Client slot assigned: client_id=%u assigned to slot %d, socket=%d", atomic_load(&client->
client_id), slot,
424 configure_client_socket(socket, atomic_load(&client->
client_id));
431 log_error(
"Failed to register client %u socket with tcp_server", atomic_load(&client->
client_id));
441 log_error(
"Failed to create video buffer for client %u", atomic_load(&client->
client_id));
452 log_error(
"Failed to create audio buffer for client %u", atomic_load(&client->
client_id));
479 log_error(
"Failed to allocate send buffer for client %u", atomic_load(&client->
client_id));
491 log_debug(
"Added client %u to uthash table", cid);
496 log_warn(
"Failed to add client %u to audio mixer", atomic_load(&client->
client_id));
507 log_error(
"Failed to initialize client state mutex for client %u", atomic_load(&client->
client_id));
513 log_error(
"Failed to initialize send mutex for client %u", atomic_load(&client->
client_id));
524 const int HANDSHAKE_TIMEOUT_SECONDS = 30;
527 log_warn(
"Failed to set handshake timeout for client %u: %s", atomic_load(&client->
client_id),
528 asciichat_error_string(timeout_result));
533 if (crypto_result != 0) {
536 log_error(
"Failed to remove client after crypto handshake failure");
545 log_warn(
"Failed to clear handshake timeout for client %u: %s", atomic_load(&client->
client_id),
546 asciichat_error_string(clear_timeout_result));
550 log_debug(
"Crypto handshake completed successfully for client %u", atomic_load(&client->
client_id));
557 log_error(
"Failed to create ACIP transport for client %u", atomic_load(&client->
client_id));
559 log_error(
"Failed to remove client after transport creation failure");
563 log_debug(
"Created ACIP transport for client %u with crypto context", atomic_load(&client->
client_id));
572 bool used_pending_packet =
false;
576 log_info(
"Client %u using --no-encrypt mode - processing pending packet type %u", atomic_load(&client->
client_id),
583 used_pending_packet =
true;
591 log_debug(
"Waiting for initial capabilities packet from client %u", atomic_load(&client->
client_id));
606 log_error(
"Failed to receive initial capabilities packet from client %u: result=%d",
607 atomic_load(&client->
client_id), result);
612 log_error(
"Failed to remove client after crypto handshake failure");
619 log_error(
"Expected PACKET_TYPE_CLIENT_CAPABILITIES but got packet type %d from client %u", envelope.
type,
625 log_error(
"Failed to remove client after crypto handshake failure");
631 log_debug(
"Processing initial capabilities packet from client %u (from %s)", atomic_load(&client->
client_id),
632 used_pending_packet ?
"pending packet" :
"network");
639 log_debug(
"Successfully received and processed initial capabilities for client %u",
648 char thread_name[64];
649 snprintf(thread_name,
sizeof(thread_name),
"receive_%u", atomic_load(&client->
client_id));
655 log_error(
"Failed to remove client after receive thread creation failure");
661 snprintf(thread_name,
sizeof(thread_name),
"send_%u", atomic_load(&client->
client_id));
668 log_error(
"Failed to remove client after send thread creation failure");
675 log_warn(
"Failed to send initial server state to client %u", atomic_load(&client->
client_id));
678 log_info(
"Sent initial server state to client %u", atomic_load(&client->
client_id));
702 log_warn(
"Failed to send initial server state to client %u: %s", atomic_load(&client->
client_id),
703 asciichat_error_string(packet_send_result));
705 log_debug(
"Sent initial server state to client %u: %u connected clients", atomic_load(&client->
client_id),
712 log_debug(
"Creating render threads for client %u", client_id_snapshot);
714 log_error(
"Failed to create render threads for client %u", client_id_snapshot);
716 log_error(
"Failed to remove client after render thread creation failure");
720 log_debug(
"Successfully created render threads for client %u", client_id_snapshot);
726 return (
int)client_id_snapshot;
759 const
char *client_ip) {
760 if (!server_ctx || !transport || !client_ip) {
769 int existing_count = 0;
781 if (existing_count >=
GET_OPTION(max_clients)) {
785 log_error(
"Maximum client limit reached (%d/%d active clients)", existing_count,
GET_OPTION(max_clients));
807 atomic_store(&client->
client_id, new_client_id);
810 atomic_store(&client->
active,
true);
811 log_info(
"Added new WebRTC client ID=%u from %s (transport=%p, slot=%d)", new_client_id, client_ip, transport, slot);
815 log_debug(
"WebRTC client slot assigned: client_id=%u assigned to slot %d", atomic_load(&client->
client_id), slot);
833 log_error(
"Failed to create video buffer for WebRTC client %u", atomic_load(&client->
client_id));
834 goto error_cleanup_webrtc;
841 log_error(
"Failed to create audio buffer for WebRTC client %u", atomic_load(&client->
client_id));
842 goto error_cleanup_webrtc;
849 goto error_cleanup_webrtc;
855 LOG_ERRNO_IF_SET(
"Failed to create outgoing video buffer for WebRTC client");
856 goto error_cleanup_webrtc;
863 log_error(
"Failed to allocate send buffer for WebRTC client %u", atomic_load(&client->
client_id));
864 goto error_cleanup_webrtc;
868 log_debug(
"Client count updated: now %d clients (added WebRTC client_id=%u to slot %d)",
874 log_debug(
"Added WebRTC client %u to uthash table", cid);
879 log_warn(
"Failed to add WebRTC client %u to audio mixer", atomic_load(&client->
client_id));
889 log_error(
"Failed to initialize client state mutex for WebRTC client %u", atomic_load(&client->
client_id));
890 goto error_cleanup_webrtc;
895 log_error(
"Failed to initialize send mutex for WebRTC client %u", atomic_load(&client->
client_id));
896 goto error_cleanup_webrtc;
904 log_debug(
"WebRTC client %u initialized - receive thread will process capabilities", atomic_load(&client->
client_id));
909 char thread_name[64];
913 snprintf(thread_name,
sizeof(thread_name),
"webrtc_recv_%u", client_id_snapshot);
916 log_error(
"Failed to create receive thread for WebRTC client %u: %s", client_id_snapshot,
917 asciichat_error_string(recv_result));
919 log_error(
"Failed to remove WebRTC client after receive thread creation failure");
923 log_debug(
"Created receive thread for WebRTC client %u", client_id_snapshot);
926 snprintf(thread_name,
sizeof(thread_name),
"webrtc_send_%u", client_id_snapshot);
929 log_error(
"Failed to create send thread for WebRTC client %u: %s", client_id_snapshot,
930 asciichat_error_string(send_result));
932 log_error(
"Failed to remove WebRTC client after send thread creation failure");
936 log_debug(
"Created send thread for WebRTC client %u", client_id_snapshot);
940 log_warn(
"Failed to send initial server state to WebRTC client %u", client_id_snapshot);
943 log_info(
"Sent initial server state to WebRTC client %u", client_id_snapshot);
965 log_warn(
"Failed to send initial server state to WebRTC client %u: %s", client_id_snapshot,
966 asciichat_error_string(packet_send_result));
968 log_debug(
"Sent initial server state to WebRTC client %u: %u connected clients", client_id_snapshot,
973 log_debug(
"Creating render threads for WebRTC client %u", client_id_snapshot);
975 log_error(
"Failed to create render threads for WebRTC client %u", client_id_snapshot);
977 log_error(
"Failed to remove WebRTC client after render thread creation failure");
981 log_debug(
"Successfully created render threads for WebRTC client %u", client_id_snapshot);
987 return (
int)client_id_snapshot;
1029 log_debug(
"SOCKET_DEBUG: Attempting to remove client %d", client_id);
1035 if (cid == client_id && cid != 0) {
1037 log_info(
"Removing client %d (socket=%d) - marking inactive and clearing video flags", client_id, client->
socket);
1039 atomic_store(&client->
active,
false);
1042 target_client = client;
1049 client_socket = client->
socket;
1069 if (!target_client) {
1071 log_warn(
"Cannot remove client %u: not found", client_id);
1082 log_debug(
"Stopping all threads for client %u (socket %d)", client_id, client_socket);
1089 log_warn(
"Failed to stop threads for TCP client %u: error %d", client_id, stop_result);
1094 log_debug(
"Stopping WebRTC client %u threads (receive and send)", client_id);
1095 if (target_client) {
1097 void *recv_result = NULL;
1100 log_warn(
"Failed to join receive thread for WebRTC client %u: error %d", client_id, recv_join_result);
1102 log_debug(
"Joined receive thread for WebRTC client %u", client_id);
1105 void *send_result = NULL;
1108 log_warn(
"Failed to join send thread for WebRTC client %u: error %d", client_id, send_join_result);
1110 log_debug(
"Joined send thread for WebRTC client %u", client_id);
1118 if (target_client && target_client->
transport) {
1121 log_debug(
"Destroyed ACIP transport for client %u", client_id);
1126 log_debug(
"SOCKET_DEBUG: Closing socket %d for client %u after thread cleanup", client_socket, client_id);
1138 log_debug(
"SOCKET_DEBUG: Client %u socket set to INVALID", client_id);
1142 cleanup_client_all_buffers(target_client);
1148 log_debug(
"Removed client %u from audio mixer", client_id);
1153 if (target_client) {
1155 log_debug(
"Removed client %u from uthash table", client_id);
1157 log_warn(
"Failed to remove client %u from hash table (client not found)", client_id);
1164 log_debug(
"Crypto context cleaned up for client %u", client_id);
1171 int retry_count = 0;
1172 const int max_retries = 5;
1178 uint32_t delay_ms = 10 * (1 << retry_count);
1179 log_warn(
"Client %u: Some threads still appear initialized (attempt %d/%d), waiting %ums", client_id,
1180 retry_count + 1, max_retries, delay_ms);
1185 if (retry_count == max_retries) {
1186 log_error(
"Client %u: Threads did not terminate after %d retries, proceeding with cleanup anyway", client_id,
1195 atomic_store(&target_client->
client_id, 0);
1213 int remaining_count = 0;
1221 log_debug(
"Client removed: client_id=%u (%s) removed, remaining clients: %d", client_id, display_name_copy,
1247 log_error(
"Invalid client info in receive thread (NULL pointer)");
1252 log_debug(
"Receive thread for client %u exiting before start (protocol disconnect requested)",
1259 if (atomic_load(&client->
client_id) == 0) {
1260 log_debug(
"Receive thread: client_id is 0, client struct may have been zeroed, exiting");
1266 log_error(
"Invalid client socket in receive thread");
1278 bool is_active = atomic_load(&client->
active);
1280 log_debug(
"RECV_THREAD_START: Client %u conditions: should_exit=%d, active=%d, socket=%d (INVALID=%d)",
1289 if (atomic_load(&client->
client_id) == 0) {
1290 log_debug(
"Client client_id reset, exiting receive thread");
1316 "SECURITY VIOLATION: Unencrypted packet when encryption required - terminating connection");
1324 asciichat_error_string(acip_result));
1331 atomic_store(&client->
active,
false);
1340 log_debug(
"Receive thread for client %u terminated, signaled all threads to stop", client->
client_id);
1356 log_error(
"Invalid client info in send thread (NULL pointer)");
1362 if (atomic_load(&client->
client_id) == 0) {
1363 log_debug(
"Send thread: client_id is 0, client struct may have been zeroed, exiting");
1369 log_error(
"Invalid client socket in send thread");
1380 const uint64_t video_send_interval_us = 16666;
1384#define MAX_AUDIO_BATCH 8
1385 int loop_iteration_count = 0;
1388 loop_iteration_count++;
1391 bool sent_something =
false;
1396 int audio_packet_count = 0;
1402 if (audio_packets[i]) {
1403 audio_packet_count++;
1411 if (audio_packet_count > 0) {
1425 if (audio_packet_count == 1) {
1446 size_t total_opus_size = 0;
1447 for (
int i = 0; i < audio_packet_count; i++) {
1448 total_opus_size += audio_packets[i]->
data_len;
1455 if (batched_opus && frame_sizes) {
1458 for (
int i = 0; i < audio_packet_count; i++) {
1459 frame_sizes[i] = (
uint16_t)audio_packets[i]->data_len;
1460 memcpy(batched_opus + offset, audio_packets[i]->data, audio_packets[i]->data_len);
1461 offset += audio_packets[i]->
data_len;
1474 log_error(
"Failed to allocate buffer for Opus batch");
1484 size_t total_samples = 0;
1485 for (
int i = 0; i < audio_packet_count; i++) {
1486 total_samples += audio_packets[i]->
data_len /
sizeof(float);
1490 float *batched_audio =
SAFE_MALLOC(total_samples *
sizeof(
float),
float *);
1491 if (batched_audio) {
1494 for (
int i = 0; i < audio_packet_count; i++) {
1495 size_t packet_samples = audio_packets[i]->
data_len /
sizeof(float);
1496 memcpy(batched_audio + offset, audio_packets[i]->data, audio_packets[i]->data_len);
1497 offset += packet_samples;
1509 audio_packet_count, total_samples, client->
client_id);
1511 log_error(
"Failed to allocate buffer for audio batch");
1518 for (
int i = 0; i < audio_packet_count; i++) {
1524 log_error(
"Failed to send audio to client %u: %s", client->
client_id, asciichat_error_string(result));
1529 sent_something =
true;
1532 if (audio_packet_count > 0) {
1547 log_debug(
"Rekey threshold reached for client %u, initiating session rekey", client->
client_id);
1556 log_error(
"Failed to send REKEY_REQUEST to client %u: %d", client->
client_id, result);
1560 log_info_client(client,
"Session rekey initiated - rotating encryption keys");
1570 log_debug(
"Client %u send thread exiting: outgoing_video_buffer is NULL", client->
client_id);
1577 log_debug(
"Send thread: video_frame_get_latest returned %p for client %u", (
void *)frame, client->
client_id);
1581 log_debug(
"Client %u send thread: video_frame_get_latest returned NULL, buffer may be destroyed",
1588 struct timespec now_ts, frame_start, frame_end, step1, step2, step3, step4, step5;
1589 (void)clock_gettime(CLOCK_MONOTONIC, &now_ts);
1591 uint64_t time_since_last_send = current_time - last_video_send_time;
1592 log_debug(
"Send thread timing check: time_since_last=%lu us, interval=%lu us, should_send=%d", time_since_last_send,
1593 video_send_interval_us, (time_since_last_send >= video_send_interval_us));
1595 if (current_time - last_video_send_time >= video_send_interval_us) {
1596 (void)clock_gettime(CLOCK_MONOTONIC, &frame_start);
1603 if (rendered_sources != sent_sources && rendered_sources > 0) {
1610 client->
client_id, sent_sources, rendered_sources);
1612 sent_something =
true;
1615 log_debug(
"Send thread: frame validation - frame=%p, frame->data=%p, frame->size=%zu", (
void *)frame,
1616 (
void *)frame->
data, frame->size);
1621 log_debug(
"Send thread: Skipping frame send due to NULL frame->data");
1625 if (frame->data && frame->size == 0) {
1628 log_debug(
"Send thread: Skipping frame send due to frame->size == 0");
1635 const char *frame_data = (
const char *)frame->data;
1638 (void)clock_gettime(CLOCK_MONOTONIC, &step1);
1639 (void)clock_gettime(CLOCK_MONOTONIC, &step2);
1640 (void)clock_gettime(CLOCK_MONOTONIC, &step3);
1641 (void)clock_gettime(CLOCK_MONOTONIC, &step4);
1645 log_debug(
"Send thread: About to send frame to client %u (width=%u, height=%u, data=%p)", client->
client_id,
1646 width, height, (
void *)frame_data);
1650 (void)clock_gettime(CLOCK_MONOTONIC, &step5);
1655 asciichat_error_string(send_result));
1657 log_debug(
"Send thread: Frame send FAILED for client %u: result=%d", client->
client_id, send_result);
1662 sent_something =
true;
1663 last_video_send_time = current_time;
1665 (void)clock_gettime(CLOCK_MONOTONIC, &frame_end);
1667 ((
uint64_t)frame_start.tv_sec * 1000000 + (
uint64_t)frame_start.tv_nsec / 1000);
1668 if (frame_time_us > 15000) {
1670 ((
uint64_t)frame_start.tv_sec * 1000000 + (
uint64_t)frame_start.tv_nsec / 1000);
1681 "SEND_THREAD: Frame send took %.2fms for client %u | Snapshot: %.2fms | Memcpy: %.2fms | CRC32: %.2fms | "
1682 "Header: %.2fms | send_packet_secure: %.2fms",
1683 frame_time_us / 1000.0, client->
client_id, step1_us / 1000.0, step2_us / 1000.0, step3_us / 1000.0,
1684 step4_us / 1000.0, step5_us / 1000.0);
1689 if (!sent_something) {
1716 } client_snapshot_t;
1719 int snapshot_count = 0;
1720 int active_video_count = 0;
1722 struct timespec lock_start, lock_end;
1723 (void)clock_gettime(CLOCK_MONOTONIC, &lock_start);
1725 (void)clock_gettime(CLOCK_MONOTONIC, &lock_end);
1727 ((
uint64_t)lock_start.tv_sec * 1000000 + (
uint64_t)lock_start.tv_nsec / 1000);
1728 if (lock_time_us > 1000) {
1729 double lock_time_ms = lock_time_us / 1000.0;
1730 char duration_str[32];
1732 log_warn(
"broadcast_server_state: rwlock_rdlock took %s", duration_str);
1740 active_video_count++;
1748 log_debug(
"Skipping server_state broadcast to client %u: crypto handshake not ready",
1755 client_snapshots[snapshot_count].crypto_ctx = crypto_ctx;
1774 (void)clock_gettime(CLOCK_MONOTONIC, &lock_end);
1776 ((
uint64_t)lock_start.tv_sec * 1000000 + (
uint64_t)lock_start.tv_nsec / 1000);
1781 for (
int i = 0; i < snapshot_count; i++) {
1782 log_debug(
"BROADCAST_DEBUG: Sending SERVER_STATE to client %u (socket %d) with crypto_ctx=%p",
1783 client_snapshots[i].client_id, client_snapshots[i].socket, (
void *)client_snapshots[i].crypto_ctx);
1790 if (atomic_load(&target->
client_id) != client_snapshots[i].client_id) {
1791 log_warn(
"Client %u ID mismatch during broadcast (found %u), skipping send", client_snapshots[i].client_id,
1799 if (atomic_load(&target->
client_id) != client_snapshots[i].client_id) {
1801 log_warn(
"Client %u was removed during broadcast send (now %u), skipping", client_snapshots[i].client_id,
1811 log_error(
"Failed to send server state to client %u: %s", client_snapshots[i].client_id,
1812 asciichat_error_string(result));
1814 log_debug(
"Sent server state to client %u: %u connected, %u active", client_snapshots[i].client_id,
1818 log_warn(
"Client %u removed before broadcast send could complete", client_snapshots[i].client_id);
1822 if (lock_held_us > 1000) {
1823 log_warn(
"broadcast_server_state: rwlock held for %.2fms (includes network I/O)", lock_held_us / 1000.0);
1839 atomic_store(&client->
active,
false);
1908static inline void cleanup_client_all_buffers(
client_info_t *client) {
1926 log_error(
"Received encrypted packet but crypto not ready for client %u", client->
client_id);
1933 size_t original_alloc_size = *len;
1935 size_t decrypted_len;
1937 (
uint8_t *)decrypted_data, original_alloc_size, &decrypted_len);
1939 if (decrypt_result != 0) {
1952 *data = decrypted_data;
1953 *len = decrypted_len;
1980static void acip_server_on_image_frame(
const image_frame_packet_t *header,
const void *pixel_data,
size_t data_len,
1981 void *client_ctx,
void *app_ctx);
1982static void acip_server_on_audio(
const void *audio_data,
size_t audio_len,
void *client_ctx,
void *app_ctx);
1983static void acip_server_on_audio_batch(
const audio_batch_packet_t *header,
const float *samples,
size_t num_samples,
1984 void *client_ctx,
void *app_ctx);
1985static void acip_server_on_audio_opus(
const void *opus_data,
size_t opus_len,
void *client_ctx,
void *app_ctx);
1986static void acip_server_on_audio_opus_batch(
const void *batch_data,
size_t batch_len,
void *client_ctx,
void *app_ctx);
1987static void acip_server_on_client_join(
const void *join_data,
size_t data_len,
void *client_ctx,
void *app_ctx);
1988static void acip_server_on_client_leave(
void *client_ctx,
void *app_ctx);
1989static void acip_server_on_stream_start(
uint32_t stream_types,
void *client_ctx,
void *app_ctx);
1990static void acip_server_on_stream_stop(
uint32_t stream_types,
void *client_ctx,
void *app_ctx);
1991static void acip_server_on_capabilities(
const void *cap_data,
size_t data_len,
void *client_ctx,
void *app_ctx);
1992static void acip_server_on_ping(
void *client_ctx,
void *app_ctx);
1993static void acip_server_on_pong(
void *client_ctx,
void *app_ctx);
1994static void acip_server_on_error(
const error_packet_t *header,
const char *message,
void *client_ctx,
void *app_ctx);
1995static void acip_server_on_remote_log(
const remote_log_packet_t *header,
const char *message,
void *client_ctx,
1997static void acip_server_on_crypto_rekey_request(
const void *payload,
size_t payload_len,
void *client_ctx,
1999static void acip_server_on_crypto_rekey_response(
const void *payload,
size_t payload_len,
void *client_ctx,
2001static void acip_server_on_crypto_rekey_complete(
const void *payload,
size_t payload_len,
void *client_ctx,
2012 .on_image_frame = acip_server_on_image_frame,
2013 .on_audio = acip_server_on_audio,
2014 .on_audio_batch = acip_server_on_audio_batch,
2015 .on_audio_opus = acip_server_on_audio_opus,
2016 .on_audio_opus_batch = acip_server_on_audio_opus_batch,
2017 .on_client_join = acip_server_on_client_join,
2018 .on_client_leave = acip_server_on_client_leave,
2019 .on_stream_start = acip_server_on_stream_start,
2020 .on_stream_stop = acip_server_on_stream_stop,
2021 .on_capabilities = acip_server_on_capabilities,
2022 .on_ping = acip_server_on_ping,
2023 .on_pong = acip_server_on_pong,
2024 .on_error = acip_server_on_error,
2025 .on_remote_log = acip_server_on_remote_log,
2026 .on_crypto_rekey_request = acip_server_on_crypto_rekey_request,
2027 .on_crypto_rekey_response = acip_server_on_crypto_rekey_response,
2028 .on_crypto_rekey_complete = acip_server_on_crypto_rekey_complete,
2041static void acip_server_on_image_frame(
const image_frame_packet_t *header,
const void *pixel_data,
size_t data_len,
2042 void *client_ctx,
void *app_ctx) {
2047 "ACIP callback received IMAGE_FRAME: width=%u, height=%u, pixel_format=%u, compressed_size=%u, data_len=%zu",
2052 log_error(
"Invalid image dimensions: %ux%u (width and height must be > 0)", header->
width, header->
height);
2059 if (header->
width > MAX_WIDTH || header->
height > MAX_HEIGHT) {
2060 log_error(
"Image dimensions too large: %ux%u (max: %ux%u)", header->
width, header->
height, MAX_WIDTH, MAX_HEIGHT);
2067 if (!was_sending_video) {
2068 if (atomic_compare_exchange_strong(&client->
is_sending_video, &was_sending_video,
true)) {
2069 log_info(
"Client %u auto-enabled video stream (received IMAGE_FRAME)", atomic_load(&client->
client_id));
2070 log_info_client(client,
"First video frame received - streaming active");
2079 log_debug(
"Client %u has sent %u IMAGE_FRAME packets (%s)", atomic_load(&client->
client_id),
2087 const size_t legacy_header_size =
sizeof(
uint32_t) * 2;
2088 size_t total_len = legacy_header_size + data_len;
2092 log_error(
"Failed to allocate buffer for IMAGE_FRAME reconstruction");
2099 memcpy(full_packet, &width_net,
sizeof(
uint32_t));
2102 memcpy(full_packet + legacy_header_size, pixel_data, data_len);
2105 log_debug(
"Reconstructed old-format packet (total_len=%zu), calling legacy handler", total_len);
2110static void acip_server_on_audio(
const void *audio_data,
size_t audio_len,
void *client_ctx,
void *app_ctx) {
2116static void acip_server_on_audio_batch(
const audio_batch_packet_t *header,
const float *samples,
size_t num_samples,
2117 void *client_ctx,
void *app_ctx) {
2128 log_debug(
"Ignoring audio batch - client %u not in audio streaming mode", client->
client_id);
2136 log_error(
"Failed to write decoded audio batch to buffer: %s", asciichat_error_string(write_result));
2141static void acip_server_on_audio_opus(
const void *opus_data,
size_t opus_len,
void *client_ctx,
void *app_ctx) {
2148 if (opus_len < 16) {
2149 log_warn(
"AUDIO_OPUS packet too small: %zu bytes", opus_len);
2158 size_t actual_opus_size = opus_len - 16;
2160 if (actual_opus_size > 0 && actual_opus_size <= 1024 && sample_rate == 48000 && frame_duration == 20) {
2162 uint8_t batch_buffer[1024 + 20];
2163 uint8_t *batch_ptr = batch_buffer;
2172 memset(batch_ptr, 0, 4);
2180 memcpy(batch_ptr, payload + 16, actual_opus_size);
2181 batch_ptr += actual_opus_size;
2184 size_t batch_size = (size_t)(batch_ptr - batch_buffer);
2189static void acip_server_on_audio_opus_batch(
const void *batch_data,
size_t batch_len,
void *client_ctx,
void *app_ctx) {
2195static void acip_server_on_client_join(
const void *join_data,
size_t data_len,
void *client_ctx,
void *app_ctx) {
2201static void acip_server_on_client_leave(
void *client_ctx,
void *app_ctx) {
2207static void acip_server_on_stream_start(
uint32_t stream_types,
void *client_ctx,
void *app_ctx) {
2216static void acip_server_on_stream_stop(
uint32_t stream_types,
void *client_ctx,
void *app_ctx) {
2225static void acip_server_on_capabilities(
const void *cap_data,
size_t data_len,
void *client_ctx,
void *app_ctx) {
2231static void acip_server_on_ping(
void *client_ctx,
void *app_ctx) {
2243 asciichat_error_string(pong_result));
2247static void acip_server_on_pong(
void *client_ctx,
void *app_ctx) {
2253static void acip_server_on_error(
const error_packet_t *header,
const char *message,
void *client_ctx,
void *app_ctx) {
2258 size_t msg_len = strlen(message);
2259 size_t total_len =
sizeof(*header) + msg_len;
2262 log_error(
"Failed to allocate buffer for ERROR_MESSAGE reconstruction");
2266 memcpy(full_packet, header,
sizeof(*header));
2267 memcpy(full_packet +
sizeof(*header), message, msg_len);
2269 handle_client_error_packet(client, full_packet, total_len);
2273static void acip_server_on_remote_log(
const remote_log_packet_t *header,
const char *message,
void *client_ctx,
2279 size_t msg_len = strlen(message);
2280 size_t total_len =
sizeof(*header) + msg_len;
2283 log_error(
"Failed to allocate buffer for REMOTE_LOG reconstruction");
2287 memcpy(full_packet, header,
sizeof(*header));
2288 memcpy(full_packet +
sizeof(*header), message, msg_len);
2294static void acip_server_on_crypto_rekey_request(
const void *payload,
size_t payload_len,
void *client_ctx,
2308 log_error(
"Failed to process REKEY_REQUEST from client %u: %d", client->
client_id, crypto_result);
2321 log_error(
"Failed to send REKEY_RESPONSE to client %u: %d", client->
client_id, crypto_result);
2327static void acip_server_on_crypto_rekey_response(
const void *payload,
size_t payload_len,
void *client_ctx,
2341 log_error(
"Failed to process REKEY_RESPONSE from client %u: %d", client->
client_id, crypto_result);
2354 log_error(
"Failed to send REKEY_COMPLETE to client %u: %d", client->
client_id, crypto_result);
2356 log_debug(
"Sent REKEY_COMPLETE to client %u - session rekeying complete", client->
client_id);
2360static void acip_server_on_crypto_rekey_complete(
const void *payload,
size_t payload_len,
void *client_ctx,
2374 log_error(
"Failed to process REKEY_COMPLETE from client %u: %d", client->
client_id, crypto_result);
2376 log_debug(
"Session rekeying completed successfully with client %u", client->
client_id);
2378 log_info_client(client,
"Session rekey complete - new encryption keys active");
2425 size_t opus_size = len - 16;
2427 if (opus_size > 0 && opus_size <= 1024 && sample_rate == 48000 && frame_duration == 20) {
2430 uint8_t batch_buffer[1024 + 20];
2431 uint8_t *batch_ptr = batch_buffer;
2440 memset(batch_ptr, 0, 4);
2448 memcpy(batch_ptr, payload + 16, opus_size);
2449 batch_ptr += opus_size;
2452 size_t batch_size = (size_t)(batch_ptr - batch_buffer);
2490 asciichat_error_string(pong_result));
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
⚠️‼️ Error and/or exit() when things go bad.
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
bool should_exit()
Check if client should exit.
Hardware-Accelerated CRC32 Checksum Computation.
asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, socket_t socket)
Send REKEY_COMPLETE packet (initiator side)
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
asciichat_error_t crypto_handshake_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet (responder side)
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
Check if handshake is complete and encryption is ready.
bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx)
Check if rekeying should be triggered for this handshake context.
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, socket_t socket)
Send REKEY_RESPONSE packet (responder side)
void crypto_handshake_cleanup(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
asciichat_error_t crypto_handshake_process_rekey_response(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_RESPONSE packet (initiator side)
asciichat_error_t crypto_handshake_process_rekey_complete(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_COMPLETE packet (responder side)
asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, socket_t socket)
Send REKEY_REQUEST packet (initiator side)
Common declarations and data structures for cryptographic handshake.
🔄 Network byte order conversion helpers
#define HOST_TO_NET_U16(val)
#define HOST_TO_NET_U32(val)
#define NET_TO_HOST_U16(val)
#define NET_TO_HOST_U32(val)
bool check_and_record_packet_rate_limit(rate_limiter_t *rate_limiter, const char *client_ip, socket_t client_socket, packet_type_t packet_type)
Map packet type to rate event type and check rate limit.
Network error handling utilities.
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
#define AUDIO_SAMPLE_RATE
Audio sample rate (48kHz professional quality, Opus-compatible)
void audio_ring_buffer_destroy(audio_ring_buffer_t *rb)
Destroy an audio ring buffer.
int mixer_add_source(mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer)
Add an audio source to the mixer.
audio_ring_buffer_t * audio_ring_buffer_create_for_capture(void)
Create a new audio ring buffer for capture (without jitter buffering)
void opus_codec_destroy(opus_codec_t *codec)
Destroy an Opus codec instance.
void mixer_remove_source(mixer_t *mixer, uint32_t client_id)
Remove an audio source from the mixer.
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define SAFE_MALLOC_ALIGNED(size, alignment, cast)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SAFE_STRERROR(errnum)
#define read_u32_unaligned
unsigned long long uint64_t
#define write_u32_unaligned
#define write_u16_unaligned
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
void asciichat_errno_cleanup(void)
Cleanup error system resources.
asciichat_error_t
Error and exit codes - unified status values (0-255)
@ ERROR_RESOURCE_EXHAUSTED
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
#define log_warn(...)
Log a WARN message.
#define log_info_client(client, fmt,...)
Server sends INFO log message to client.
#define log_error(...)
Log an ERROR message.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_error_client(client, fmt,...)
Server sends ERROR log message to client.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
int format_duration_ms(double milliseconds, char *buffer, size_t buffer_size)
Format milliseconds as human-readable duration string.
uint32_t pixel_format
Pixel format enum (0=RGB24, 1=RGBA32, 2=BGR24, etc.)
asciichat_error_t set_socket_timeout(socket_t sockfd, int timeout_seconds)
Set socket timeout.
uint32_t reserved[6]
Reserved fields for future use (must be zero)
asciichat_error_t set_socket_keepalive(socket_t sockfd)
Set socket keepalive.
uint32_t client_id
Client ID (0 = server, >0 = client identifier)
uint32_t active_client_count
Number of clients actively sending video/audio streams.
void * allocated_buffer
Buffer that needs to be freed by caller (may be NULL if not allocated)
uint32_t connected_client_count
Total number of currently connected clients.
asciichat_error_t send_audio_batch_packet(socket_t sockfd, const float *samples, int num_samples, int batch_count, crypto_context_t *crypto_ctx)
Send a batched audio packet with encryption support.
asciichat_error_t packet_parse_error_message(const void *data, size_t len, asciichat_error_t *out_error_code, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Parse an error packet payload into components.
asciichat_error_t av_send_audio_opus_batch(socket_t sockfd, const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int sample_rate, int frame_duration, int frame_count, crypto_context_t *crypto_ctx)
Send Opus-encoded audio batch packet with encryption support.
size_t allocated_size
Size of allocated buffer in bytes.
size_t len
Length of payload data in bytes.
void * data
Packet payload data (decrypted and decompressed if applicable)
uint32_t compressed_size
Compressed data size (0 = not compressed, >0 = compressed)
packet_recv_result_t receive_packet_secure(socket_t sockfd, void *crypto_ctx, bool enforce_encryption, packet_envelope_t *envelope)
Receive a packet with decryption and decompression support.
const char * network_error_string()
Get human-readable error string for network errors.
uint16_t type
Packet type (packet_type_t enumeration)
uint32_t height
Image height in pixels.
packet_type_t type
Packet type (from packet_types.h)
uint32_t width
Image width in pixels.
packet_recv_result_t
Packet reception result codes.
@ PACKET_RECV_SUCCESS
Packet received successfully.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
void packet_queue_shutdown(packet_queue_t *queue)
Signal queue shutdown (causes dequeue to return NULL)
packet_queue_t * packet_queue_create_with_pools(size_t max_size, size_t node_pool_size, bool use_buffer_pool)
Create a packet queue with both node and buffer pools.
size_t data_len
Length of payload data in bytes.
void packet_queue_free_packet(queued_packet_t *packet)
Free a dequeued packet.
queued_packet_t * packet_queue_try_dequeue(packet_queue_t *queue)
Try to dequeue a packet without blocking.
void packet_queue_destroy(packet_queue_t *queue)
Destroy a packet queue and free all resources.
void * data
Packet payload data (can be NULL for header-only packets)
packet_type_t
Network protocol packet type enumeration.
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes)
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
@ PACKET_TYPE_AUDIO_OPUS
Opus-encoded single audio frame.
@ PACKET_TYPE_CLIENT_LEAVE
Clean disconnect notification.
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
@ PACKET_TYPE_PONG
Keepalive pong response.
@ PACKET_TYPE_STREAM_START
Client requests to start sending video/audio.
@ PACKET_TYPE_AUDIO
Single audio packet (legacy)
@ PACKET_TYPE_REMOTE_LOG
Bidirectional remote logging packet.
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
@ PACKET_TYPE_CLIENT_JOIN
Client announces capability to send media.
@ PACKET_TYPE_CLIENT_CAPABILITIES
Client reports terminal capabilities.
@ PACKET_TYPE_PING
Keepalive ping packet.
@ PACKET_TYPE_AUDIO_BATCH
Batched audio packets for efficiency.
@ PACKET_TYPE_STREAM_STOP
Client stops sending media.
void handle_audio_opus_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_OPUS_BATCH packet - efficient Opus-encoded audio batch from client.
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)
void video_frame_buffer_destroy(video_frame_buffer_t *vfb)
Destroy frame buffer and free all resources.
video_frame_buffer_t * video_frame_buffer_create(uint32_t client_id)
Create a double-buffered video frame manager.
const video_frame_t * video_frame_get_latest(video_frame_buffer_t *vfb)
Reader API: Get latest frame if available.
ACIP protocol packet handlers (transport-agnostic)
🔊 Audio Capture and Playback Interface for ascii-chat
asciichat_error_t acip_send_server_state(acip_transport_t *transport, const server_state_packet_t *state)
Send server state update to client (server → client)
asciichat_error_t acip_server_receive_and_dispatch(acip_transport_t *transport, void *client_ctx, const acip_server_callbacks_t *callbacks)
Receive packet from client and dispatch to callbacks.
asciichat_error_t acip_send_ascii_frame(acip_transport_t *transport, const char *frame_data, uint32_t width, uint32_t height)
Send ASCII frame to client (server → client)
asciichat_error_t acip_send_clear_console(acip_transport_t *transport)
Send clear console command to client (server → client)
ACIP server-side protocol library.
asciichat_error_t tcp_server_spawn_thread(tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread for a client.
asciichat_error_t tcp_server_stop_client_threads(tcp_server_t *server, socket_t client_socket)
Stop all threads for a client in stop_id order.
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
Add client to registry.
Multi-Source Audio Mixing and Processing System.
Network logging macros and remote log direction enumeration.
🌐 Core network I/O operations with timeout support
⚙️ Command-line options parsing and configuration management for ascii-chat
Opus audio codec wrapper for real-time encoding/decoding.
Packet protocol implementation with encryption and compression support.
📬 Thread-safe packet queue system for per-client send threads
int create_client_render_threads(server_context_t *server_ctx, client_info_t *client)
Create and initialize per-client rendering threads.
Per-client rendering threads with rate limiting.
asciichat_error_t acip_send_audio_opus(acip_transport_t *transport, const void *opus_data, size_t opus_len)
Send Opus-encoded audio packet.
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len)
Send packet via transport with proper header (exported for generic wrappers)
asciichat_error_t acip_send_pong(acip_transport_t *transport)
Send pong packet.
ACIP shared/bidirectional packet sending functions.
rate_limiter_t * g_rate_limiter
Global rate limiter for connection attempts and packet processing.
ascii-chat Server Mode Entry Point Header
void handle_client_join_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_JOIN packet - client announces identity and capabilities.
void handle_protocol_version_packet(client_info_t *client, const void *data, size_t len)
Process PROTOCOL_VERSION packet - validate protocol compatibility.
void handle_image_frame_packet(client_info_t *client, void *data, size_t len)
Process IMAGE_FRAME packet - store client's video data for rendering.
void handle_audio_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_BATCH packet - store efficiently batched audio samples.
void handle_audio_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO packet - store single audio sample batch (legacy format)
void handle_client_leave_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_LEAVE packet - handle clean client disconnect.
void handle_stream_stop_packet(client_info_t *client, const void *data, size_t len)
Process STREAM_STOP packet - client requests to halt media transmission.
int send_server_state_to_client(client_info_t *client)
Send current server state to a specific client.
void handle_client_capabilities_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_CAPABILITIES packet - configure client-specific rendering.
void handle_stream_start_packet(client_info_t *client, const void *data, size_t len)
Process STREAM_START packet - client requests to begin media transmission.
void handle_remote_log_packet_from_client(client_info_t *client, const void *data, size_t len)
Cross-platform socket interface for ascii-chat.
void * client_send_thread_func(void *arg)
Client packet send thread.
void cleanup_client_media_buffers(client_info_t *client)
void process_decrypted_packet(client_info_t *client, packet_type_t type, void *data, size_t len)
mixer_t * g_audio_mixer
Global audio mixer from main.c.
void cleanup_client_packet_queues(client_info_t *client)
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
void * client_receive_thread(void *arg)
void stop_client_threads(client_info_t *client)
client_info_t * find_client_by_socket(socket_t socket)
Find client by socket descriptor using linear search.
int process_encrypted_packet(client_info_t *client, packet_type_t *type, void **data, size_t *len, uint32_t *sender_id)
atomic_bool g_server_should_exit
Global shutdown flag from main.c.
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
void broadcast_server_state_to_all_clients(void)
Notify all clients of state changes.
Per-client state management and lifecycle orchestration.
client_info_t * find_client_by_id(uint32_t client_id)
int add_client(server_context_t *server_ctx, socket_t socket, const char *client_ip, int port)
int remove_client(server_context_t *server_ctx, uint32_t client_id)
int add_webrtc_client(server_context_t *server_ctx, acip_transport_t *transport, const char *client_ip)
int crypto_server_decrypt_packet(uint32_t client_id, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
const crypto_context_t * crypto_server_get_context(uint32_t client_id)
int server_crypto_init(void)
bool crypto_server_is_ready(uint32_t client_id)
int server_crypto_handshake(client_info_t *client)
Server cryptographic operations and per-client handshake management.
Server packet processing and protocol implementation.
Multi-client video mixing and ASCII frame generation.
Server-side packet handler callbacks.
void(* on_protocol_version)(const protocol_version_packet_t *version, void *client_ctx, void *app_ctx)
Called when client sends protocol version.
Transport instance structure.
char * context_message
Optional custom message (dynamically allocated, owned by system)
asciichat_error_t code
Error code (asciichat_error_t enum value)
Audio batch packet structure (Packet Type 28)
Per-client state structure for server-side client management.
atomic_bool video_render_thread_running
acip_transport_t * transport
char display_name[MAX_DISPLAY_NAME_LEN]
atomic_int last_sent_grid_sources
asciichat_thread_t audio_render_thread
uint32_t frames_received_logged
video_frame_buffer_t * outgoing_video_buffer
asciichat_thread_t send_thread
crypto_handshake_context_t crypto_handshake_ctx
video_frame_buffer_t * incoming_video_buffer
audio_ring_buffer_t * incoming_audio_buffer
atomic_bool protocol_disconnect_requested
mutex_t client_state_mutex
atomic_bool is_sending_audio
atomic_bool audio_render_thread_running
atomic_int last_rendered_grid_sources
asciichat_thread_t receive_thread
packet_queue_t * audio_queue
char client_ip[INET_ADDRSTRLEN]
atomic_bool shutting_down
packet_type_t pending_packet_type
asciichat_thread_t video_render_thread
atomic_bool is_sending_video
void * pending_packet_payload
size_t pending_packet_length
atomic_bool send_thread_running
Global client manager structure for server-side client coordination.
_Atomic uint32_t next_client_id
Monotonic counter for unique client IDs (atomic for thread-safety)
client_info_t * clients_by_id
uthash head pointer for O(1) client_id -> client_info_t* lookups
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
int client_count
Current number of active clients.
Cryptographic context structure.
Error packet structure carrying error code and textual description.
Image frame packet structure (Packet Type 3)
Main mixer structure for multi-source audio processing.
Opus codec context for encoding or decoding.
Packet envelope containing received packet data.
Protocol version negotiation packet structure (Packet Type 1)
Single packet ready to send (header already in network byte order)
Remote log packet structure carrying log level and message text.
Server context - encapsulates all server state.
Server state packet structure.
void * data
Frame data pointer (points to pre-allocated buffer)
⏱️ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
Create TCP transport from existing socket.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
#️⃣ Wrapper for uthash.h that ensures common.h is included first
🔤 String Manipulation and Shell Escaping Utilities
Common SIMD utilities and structures.