ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
server.h File Reference

Go to the source code of this file.

Data Structures

struct  acds_client_data_t
 Per-client connection data. More...
 
struct  migration_context_t
 In-memory host migration context. More...
 
struct  acds_stats_t
 Network statistics for both TCP and WebSocket connections. More...
 
struct  acds_server_t
 Discovery server state. More...
 

Functions

asciichat_error_t acds_server_init (acds_server_t *server, const acds_config_t *config)
 Initialize discovery server.
 
void acds_server_shutdown (acds_server_t *server)
 Shutdown discovery server.
 
void * acds_client_handler (void *arg)
 Per-client connection handler (thread entry point)
 
void * acds_websocket_client_handler (void *arg)
 WebSocket client handler (thread entry point)
 

Function Documentation

◆ acds_client_handler()

void * acds_client_handler ( void *  arg)

Per-client connection handler (thread entry point)

Processes ACIP packets from a connected client. Handles crypto handshake, then dispatches packets to session/signaling handlers.

Parameters
argPointer to client connection context
Returns
NULL (thread exit value)

Definition at line 967 of file src/discovery-service/server.c.

967 {
969 if (!ctx) {
970 log_error("Client handler: NULL context");
971 return NULL;
972 }
973
974 acds_server_t *server = (acds_server_t *)ctx->user_data;
975 socket_t client_socket = ctx->client_socket;
976
977 // Get client IP for logging
978 char client_ip[INET6_ADDRSTRLEN] = {0};
979 tcp_client_context_get_ip(ctx, client_ip, sizeof(client_ip));
980
981 log_info("Client handler started for %s", client_ip);
982
983 // Register client in TCP server registry with allocated client data
985 if (!client_data) {
986 tcp_server_reject_client(client_socket, "Failed to allocate client data");
987 SAFE_FREE(ctx);
988 return NULL;
989 }
990 memset(client_data, 0, sizeof(*client_data));
991 client_data->joined_session = false;
992 client_data->handshake_complete = false;
993
994 // Initialize crypto handshake context with client IP name
995 char crypto_name[96];
996 SAFE_SNPRINTF(crypto_name, sizeof(crypto_name), "crypto_discovery_client_%s", client_ip);
997 asciichat_error_t handshake_result = crypto_handshake_init(crypto_name, &client_data->handshake_ctx, true);
998 if (handshake_result != ASCIICHAT_OK) {
999 log_error("Failed to initialize crypto handshake for client %s", client_ip);
1000 SAFE_FREE(client_data);
1001 tcp_server_reject_client(client_socket, "Failed to initialize crypto handshake");
1002 SAFE_FREE(ctx);
1003 return NULL;
1004 }
1005
1006 // Set server identity keys for handshake
1009 memcpy(client_data->handshake_ctx.server_public_key.key, server->identity_public, 32);
1010 memcpy(client_data->handshake_ctx.server_private_key.key.ed25519, server->identity_secret, 64);
1011
1012 if (tcp_server_add_client(server->tcp_server, client_socket, client_data) != ASCIICHAT_OK) {
1013 SAFE_FREE(client_data);
1014 tcp_server_reject_client(client_socket, "Failed to register client in registry");
1015 SAFE_FREE(ctx);
1016 return NULL;
1017 }
1018
1019 log_debug("Client %s registered (socket=%d, total=%zu)", client_ip, client_socket,
1020 tcp_server_get_client_count(server->tcp_server));
1021
1022 // Create transport for handshake and subsequent client communication
1023 acip_transport_t *transport = acip_tcp_transport_create("acds_tcp_client", client_socket, NULL);
1024 if (!transport) {
1025 log_error("Failed to create TCP transport for client %s", client_ip);
1026 tcp_server_remove_client(server->tcp_server, client_socket);
1027 SAFE_FREE(ctx);
1028 return NULL;
1029 }
1030
1031 // Perform crypto handshake using shared server_like implementation
1032 log_debug("Performing crypto handshake with client %s", client_ip);
1033 handshake_result = session_server_like_handshake(&client_data->handshake_ctx, transport);
1034 if (handshake_result != ASCIICHAT_OK) {
1035 log_warn("Crypto handshake failed for client %s", client_ip);
1036 acip_transport_destroy(transport);
1037 tcp_server_remove_client(server->tcp_server, client_socket);
1038 SAFE_FREE(ctx);
1039 return NULL;
1040 }
1041
1042 client_data->handshake_complete = true;
1043 log_info("Crypto handshake complete for client %s", client_ip);
1044
1045 // Store transport and registry ID in client data, and link back from transport
1046 client_data->transport = transport;
1047 client_data->registry_id = client_socket;
1048 transport->user_data = client_data;
1049
1050 // Main packet processing loop
1051 uint64_t last_packet_time_ns = time_get_ns();
1052 const uint64_t idle_disconnect_ns = 60ULL * NS_PER_SEC_INT; // Disconnect after 60s idle
1053
1054 while (atomic_load_bool(&server->tcp_server->running)) {
1055 packet_type_t packet_type;
1056 void *payload = NULL;
1057 size_t payload_size = 0;
1058
1059 // Receive packet (blocking with system timeout)
1060 int result = receive_packet(client_socket, &packet_type, &payload, &payload_size);
1061
1062 if (result >= 0) {
1063 last_packet_time_ns = time_get_ns();
1064 log_info("★ ACDS_CLIENT_HANDLER: Received packet type=%u from %s, payload_size=%zu", packet_type, client_ip,
1065 payload_size);
1066
1067 // Track network stats (TCP packets)
1068 // Packet size includes header (6 bytes: magic 4 + type 1 + length 2 + CRC 4)
1069 size_t total_packet_size = payload_size + 14; // 4 (magic) + 1 (type) + 2 (length) + 4 (CRC) + 3 (client ID)
1070 atomic_fetch_add_u64(&server->stats.bytes_received, total_packet_size);
1071 atomic_fetch_add_u64(&server->stats.packets_received, 1);
1072 }
1073
1074 if (result < 0) {
1075 // Check error context to distinguish timeout from actual disconnect
1077 bool has_context = HAS_ERRNO(&err_ctx);
1078
1079 // Check if this is a timeout (non-fatal) or actual disconnect (fatal)
1080 asciichat_error_t error = GET_ERRNO();
1081 if (error == ERROR_NETWORK_TIMEOUT ||
1082 (error == ERROR_NETWORK && has_context && strstr(err_ctx.context_message, "timed out") != NULL)) {
1083 // Check if client has been idle too long (abrupt disconnect without FIN)
1084 uint64_t idle_ns = time_get_ns() - last_packet_time_ns;
1085 if (idle_ns >= idle_disconnect_ns) {
1086 log_info("Client %s: idle for %llu seconds, disconnecting", client_ip,
1087 (unsigned long long)(idle_ns / NS_PER_SEC_INT));
1088 if (payload) {
1089 buffer_pool_free(NULL, payload, payload_size);
1090 }
1091 break;
1092 }
1093 log_debug("Client %s: receive timeout, continuing to wait for packets", client_ip);
1094 if (payload) {
1095 buffer_pool_free(NULL, payload, payload_size);
1096 }
1097 continue;
1098 }
1099
1100 // Actual disconnect or fatal error
1101 log_info("Client %s disconnected", client_ip);
1102 if (payload) {
1103 buffer_pool_free(NULL, payload, payload_size);
1104 }
1105 break;
1106 }
1107
1108 log_debug("Received packet type 0x%02X from %s, length=%zu", packet_type, client_ip, payload_size);
1109
1110 // Multi-key session creation protocol: block non-PING/PONG/SESSION_CREATE messages
1111 if (client_data->in_multikey_session_create) {
1112 bool allowed = (packet_type == PACKET_TYPE_ACIP_SESSION_CREATE || packet_type == PACKET_TYPE_PING ||
1113 packet_type == PACKET_TYPE_PONG);
1114
1115 if (!allowed) {
1116 log_warn("Client %s sent packet type 0x%02X during multi-key session creation - only SESSION_CREATE/PING/PONG "
1117 "allowed",
1118 client_ip, packet_type);
1119
1120 // Send error response using persistent transport
1122 "Only SESSION_CREATE/PING/PONG allowed during multi-key session creation");
1123
1124 // Free payload and continue
1125 if (payload) {
1126 buffer_pool_free(NULL, payload, payload_size);
1127 }
1128 continue;
1129 }
1130 }
1131
1132 // O(1) ACIP array-based dispatch
1133 // Set server context for callbacks
1134 acip_acds_callbacks_t callbacks = g_acds_callbacks;
1135 callbacks.app_ctx = server;
1136
1137 asciichat_error_t dispatch_result =
1138 acip_handle_acds_packet(transport, packet_type, payload, payload_size, client_ip, &callbacks);
1139
1140 if (dispatch_result != ASCIICHAT_OK) {
1141 log_warn("ACIP handler failed for packet type 0x%02X from %s: %s", packet_type, client_ip,
1142 asciichat_error_string(dispatch_result));
1143 }
1144
1145 // Free payload (allocated by receive_packet via buffer_pool_alloc)
1146 if (payload) {
1147 buffer_pool_free(NULL, payload, payload_size);
1148 }
1149
1150 // Keep handler running after session_established
1151 // Handler continues to receive and relay WebRTC signaling packets (SDP, ICE)
1152 // Broadcasts don't go through this socket - they use tcp_server_foreach_client() to write directly
1153 }
1154
1155 // Cleanup - handler always cleans up since it runs until connection closes
1156 if (client_data->joined_session) {
1157 asciichat_error_t leave_result = acds_remove_client_from_session(server, client_data);
1158 if (leave_result != ASCIICHAT_OK) {
1159 log_warn("Could not clean up disconnected participant %02x%02x...: %s", client_data->participant_id[0],
1160 client_data->participant_id[1], asciichat_error_string(leave_result));
1161 }
1162 }
1163 tcp_server_remove_client(server->tcp_server, client_socket);
1164 log_debug("Client %s unregistered (total=%zu)", client_ip, tcp_server_get_client_count(server->tcp_server));
1165
1166 // Destroy transport before closing socket
1167 acip_transport_destroy(transport);
1168 socket_close(client_socket);
1169
1170 SAFE_FREE(ctx);
1171
1172 log_info("Client handler finished for %s", client_ip);
1173 return NULL;
1174}
asciichat_error_t acip_handle_acds_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, const char *client_ip, const acip_acds_callbacks_t *callbacks)
Handle incoming ACDS packet with O(1) dispatch.
uint64_t atomic_fetch_add_u64(atomic_t *a, uint64_t delta)
Atomically add to a uint64_t and return the previous value.
Definition atomic.c:248
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
asciichat_error_t crypto_handshake_init(const char *name, crypto_handshake_context_t *ctx, bool is_server)
Initialize named crypto handshake context.
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
unsigned long long uint64_t
Definition common.h:59
#define GET_ERRNO()
Get current error code (0 if no error)
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK
Definition error_codes.h:77
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:80
uint8_t key[32]
Definition key_types.h:71
key_type_t type
Definition key_types.h:92
uint8_t ed25519[64]
Definition key_types.h:94
union private_key_t::@14 key
key_type_t type
Definition key_types.h:70
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:797
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_PONG
Keepalive pong response.
Definition packet.h:385
@ PACKET_TYPE_ACIP_SESSION_CREATE
Create new session (Client -> Discovery Server)
Definition packet.h:407
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:383
int socket_close(socket_t sock)
Close a socket.
int socket_t
void tcp_server_reject_client(socket_t socket, const char *reason)
Reject client connection with reason.
asciichat_error_t tcp_server_remove_client(tcp_server_t *server, socket_t socket)
Remove client from registry.
const char * tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len)
Get formatted IP address from client context.
size_t tcp_server_get_client_count(tcp_server_t *server)
Get client count.
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
Add client to registry.
asciichat_error_t acip_send_error(acip_transport_t *transport, uint32_t error_code, const char *message)
Send error message packet.
Definition send.c:301
asciichat_error_t session_server_like_handshake(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Per-client connection data.
bool handshake_complete
Whether crypto handshake has completed.
uint8_t participant_id[16]
Participant UUID (valid if joined_session)
int registry_id
Socket or synthetic ID used in tcp_server registry.
crypto_handshake_context_t handshake_ctx
Handshake context for encrypted communication.
bool joined_session
Whether client has successfully joined a session.
acip_transport_t * transport
Persistent transport for this client connection.
bool in_multikey_session_create
True during multi-key SESSION_CREATE sequence.
Discovery server state.
ACDS packet handler callbacks.
void * app_ctx
Application context (passed to all callbacks)
Transport instance structure.
Definition transport.h:214
void * user_data
Application-specific context (e.g., per-client data)
Definition transport.h:219
Error context structure.
char * context_message
Optional custom message (dynamically allocated, owned by system)
socket_t client_socket
Client connection socket.
void * user_data
User-provided data from config.
acip_transport_t * acip_tcp_transport_create(const char *name, 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.

References acip_handle_acds_packet(), acip_send_error(), acip_tcp_transport_create(), acip_transport_destroy(), acip_acds_callbacks_t::app_ctx, ASCIICHAT_OK, atomic_fetch_add_u64(), atomic_load_bool(), buffer_pool_free(), acds_stats_t::bytes_received, tcp_client_context_t::client_socket, asciichat_error_context_t::context_message, crypto_handshake_init(), private_key_t::ed25519, ERROR_INVALID_PARAM, ERROR_NETWORK, ERROR_NETWORK_TIMEOUT, GET_ERRNO, acds_client_data_t::handshake_complete, acds_client_data_t::handshake_ctx, HAS_ERRNO, acds_server_t::identity_public, acds_server_t::identity_secret, acds_client_data_t::in_multikey_session_create, acds_client_data_t::joined_session, public_key_t::key, private_key_t::key, KEY_TYPE_ED25519, log_debug, log_error, log_info, log_warn, NS_PER_SEC_INT, PACKET_TYPE_ACIP_SESSION_CREATE, PACKET_TYPE_PING, PACKET_TYPE_PONG, acds_stats_t::packets_received, acds_client_data_t::participant_id, receive_packet(), acds_client_data_t::registry_id, tcp_server::running, SAFE_FREE, SAFE_MALLOC, SAFE_SNPRINTF, crypto_handshake_context_t::server_private_key, crypto_handshake_context_t::server_public_key, session_server_like_handshake(), socket_close(), acds_server_t::stats, tcp_client_context_get_ip(), acds_server_t::tcp_server, tcp_server_add_client(), tcp_server_get_client_count(), tcp_server_reject_client(), tcp_server_remove_client(), time_get_ns(), acds_client_data_t::transport, public_key_t::type, private_key_t::type, acip_transport::user_data, and tcp_client_context_t::user_data.

Referenced by acds_main().

◆ acds_server_init()

asciichat_error_t acds_server_init ( acds_server_t *  server,
const acds_config_t *  config 
)

Initialize discovery server.

Loads or generates identity keys, opens database, creates session registry, and binds TCP socket.

Parameters
serverServer structure to initialize
configConfiguration from command-line parsing
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 194 of file src/discovery-service/server.c.

194 {
195 if (!server || !config) {
196 return SET_ERRNO(ERROR_INVALID_PARAM, "server or config is NULL");
197 }
198
199 memset(server, 0, sizeof(*server));
200 memcpy(&server->config, config, sizeof(acds_config_t));
201
202 // Initialize stats
203 atomic_store_u64(&server->stats.bytes_sent, 0);
207 server->stats.start_time = time(NULL);
208
209 // Open database (SQLite as single source of truth)
210 asciichat_error_t result = database_init(config->database_path, &server->db);
211 if (result != ASCIICHAT_OK) {
212 return result;
213 }
214
215 // Initialize rate limiter with SQLite backend
216 server->rate_limiter = rate_limiter_create_sqlite(NULL); // NULL = externally managed DB
217 if (!server->rate_limiter) {
218 database_close(server->db);
219 return SET_ERRNO(ERROR_MEMORY, "Failed to create rate limiter");
220 }
221
222 // Set the database handle for the rate limiter
224
225 // Get TCP server from server_like (it owns the lifecycle)
227 if (!server->tcp_server) {
229 database_close(server->db);
230 return SET_ERRNO(ERROR_INVALID_STATE, "TCP server not available from server_like");
231 }
232
233 // Initialize background worker thread pool
234 atomic_store_bool(&server->shutdown, false);
235 server->worker_pool = thread_pool_create("acds_workers");
236 if (!server->worker_pool) {
237 log_warn("Failed to create worker thread pool");
239 database_close(server->db);
240 return SET_ERRNO(ERROR_MEMORY, "Failed to create worker thread pool");
241 }
242
243 // Spawn cleanup thread in worker pool
244 if (thread_pool_spawn(server->worker_pool, cleanup_thread_func, server, 0, "cleanup") != ASCIICHAT_OK) {
245 log_warn("Failed to spawn cleanup thread (continuing without cleanup)");
246 }
247
248 log_info("Discovery server initialized successfully");
249 return ASCIICHAT_OK;
250}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
asciichat_error_t thread_pool_spawn(void *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *name)
Spawn a worker thread in the pool.
void database_close(sqlite3 *db)
Close database.
asciichat_error_t database_init(const char *db_path, sqlite3 **db)
Initialize database and create schema.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
void rate_limiter_destroy(rate_limiter_t *limiter)
Destroy rate limiter and free resources.
Definition rate_limit.c:116
void rate_limiter_set_sqlite_db(rate_limiter_t *limiter, void *db)
Set SQLite database handle for rate limiter.
Definition rate_limit.c:107
rate_limiter_t * rate_limiter_create_sqlite(const char *db_path)
Create SQLite-backed rate limiter.
Definition rate_limit.c:90
tcp_server_t * session_server_like_get_tcp_server(void)
Discovery server configuration.
char database_path[512]
SQLite database path.
acds_config_t config
Runtime configuration.
atomic_t shutdown
Shutdown flag for worker threads.
tcp_server_t * tcp_server
TCP server (owned by server_like)
sqlite3 * db
SQLite database handle.
acds_stats_t stats
Network statistics for all connections.
struct rate_limiter_s * rate_limiter
SQLite-backed rate limiter.
thread_pool_t * worker_pool
Thread pool for background workers.
atomic_t packets_received
Total packets received (TCP + WebSocket)
atomic_t packets_sent
Total packets sent (TCP + WebSocket)
atomic_t bytes_sent
Total bytes sent (TCP + WebSocket)
atomic_t bytes_received
Total bytes received (TCP + WebSocket)
time_t start_time
Server start time.
thread_pool_t * thread_pool_create(int num_threads)

References ASCIICHAT_OK, atomic_store_bool(), atomic_store_u64(), acds_stats_t::bytes_received, acds_stats_t::bytes_sent, acds_server_t::config, database_close(), database_init(), acds_config_t::database_path, acds_server_t::db, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, log_info, log_warn, acds_stats_t::packets_received, acds_stats_t::packets_sent, acds_server_t::rate_limiter, rate_limiter_create_sqlite(), rate_limiter_destroy(), rate_limiter_set_sqlite_db(), session_server_like_get_tcp_server(), SET_ERRNO, acds_server_t::shutdown, acds_stats_t::start_time, acds_server_t::stats, acds_server_t::tcp_server, thread_pool_create(), thread_pool_spawn(), and acds_server_t::worker_pool.

◆ acds_server_shutdown()

void acds_server_shutdown ( acds_server_t *  server)

Shutdown discovery server.

Closes listen socket, stops accepting connections, waits for handler threads to exit, closes database, and frees resources.

Parameters
serverServer structure to clean up

Definition at line 252 of file src/discovery-service/server.c.

252 {
253 if (!server) {
254 return;
255 }
256
257 // Signal shutdown to worker threads
258 atomic_store_bool(&server->shutdown, true);
259
260 // Wait for all client handler threads to exit
261 // TCP server is still alive (server_like destroys it after cleanup_fn)
262 if (server->tcp_server) {
263 size_t remaining_clients;
264 int shutdown_attempts = 0;
265 const int max_shutdown_attempts = 100; // 10 seconds (100 * 100ms)
266
267 while ((remaining_clients = tcp_server_get_client_count(server->tcp_server)) > 0 &&
268 shutdown_attempts < max_shutdown_attempts) {
269 log_debug("Waiting for %zu client handler threads to exit (attempt %d/%d)", remaining_clients,
270 shutdown_attempts + 1, max_shutdown_attempts);
271 APP_CALLBACK_VOID(platform_pump_events);
273 shutdown_attempts++;
274 }
275
276 if (remaining_clients > 0) {
277 log_warn("Server shutdown: %zu client handler threads still running after 10 seconds", remaining_clients);
278 } else if (shutdown_attempts > 0) {
279 log_debug("All client handler threads exited gracefully");
280 }
281 }
282
283 // Stop and destroy worker thread pool (cleanup thread, etc.)
284 if (server->worker_pool) {
286 server->worker_pool = NULL;
287 log_debug("Worker thread pool stopped");
288 }
289
290 // Destroy rate limiter
291 if (server->rate_limiter) {
293 server->rate_limiter = NULL;
294 }
295
296 // Close database
297 if (server->db) {
298 database_close(server->db);
299 server->db = NULL;
300 }
301
302 log_info("Server shutdown complete");
303}
#define APP_CALLBACK_VOID(callback_name)
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.

References APP_CALLBACK_VOID, atomic_store_bool(), database_close(), acds_server_t::db, log_debug, log_info, log_warn, platform_sleep_ms(), acds_server_t::rate_limiter, rate_limiter_destroy(), acds_server_t::shutdown, acds_server_t::tcp_server, tcp_server_get_client_count(), thread_pool_destroy(), and acds_server_t::worker_pool.

◆ acds_websocket_client_handler()

void * acds_websocket_client_handler ( void *  arg)

WebSocket client handler (thread entry point)

Processes ACIP packets from a WebSocket client connection. Similar to TCP handler but uses transport-based APIs for crypto handshake.

Parameters
argPointer to websocket_client_context_t
Returns
NULL (thread exit value)

Definition at line 1210 of file src/discovery-service/server.c.

1210 {
1212 if (!ctx) {
1213 log_error("WebSocket client handler: NULL context");
1214 return NULL;
1215 }
1216
1217 acds_server_t *server = (acds_server_t *)ctx->user_data;
1218 acip_transport_t *transport = ctx->transport;
1219 char *client_ip = ctx->client_ip;
1220
1221 log_info("WebSocket client handler started for %s", client_ip);
1222
1223 // Allocate client_data
1225 if (!client_data) {
1226 log_error("Failed to allocate client data for WebSocket client %s", client_ip);
1227 SAFE_FREE(ctx);
1228 return NULL;
1229 }
1230
1231 client_data->transport = transport;
1232 client_data->joined_session = false;
1233 client_data->handshake_complete = false;
1234 transport->user_data = client_data;
1235
1236 // Initialize crypto handshake context with client IP name
1237 char crypto_name[96];
1238 SAFE_SNPRINTF(crypto_name, sizeof(crypto_name), "crypto_discovery_websocket_%s", client_ip);
1239 asciichat_error_t hs_init_result = crypto_handshake_init(crypto_name, &client_data->handshake_ctx, true);
1240 if (hs_init_result != ASCIICHAT_OK) {
1241 log_error("Failed to initialize crypto handshake for WebSocket client %s", client_ip);
1242 SAFE_FREE(client_data);
1243 SAFE_FREE(ctx);
1244 return NULL;
1245 }
1246
1247 // Set server identity keys for handshake
1250 memcpy(client_data->handshake_ctx.server_public_key.key, server->identity_public, 32);
1251 memcpy(client_data->handshake_ctx.server_private_key.key.ed25519, server->identity_secret, 64);
1252
1253 // Generate synthetic socket ID for tcp_server registry
1254 static _Atomic int g_ws_synthetic_id = INT_MAX;
1255 int synthetic_id = atomic_fetch_sub(&g_ws_synthetic_id, 1);
1256 client_data->registry_id = synthetic_id;
1257
1258 // Register in tcp_server with synthetic ID (so callbacks can find client_data)
1259 if (tcp_server_add_client(server->tcp_server, synthetic_id, client_data) != ASCIICHAT_OK) {
1260 log_error("Failed to register WebSocket client %s in registry", client_ip);
1261 SAFE_FREE(client_data);
1262 SAFE_FREE(ctx);
1263 return NULL;
1264 }
1265
1266 log_debug("WebSocket client %s registered (synthetic_id=%d, total=%zu)", client_ip, synthetic_id,
1267 tcp_server_get_client_count(server->tcp_server));
1268
1269 // Determine if client authentication is required by checking server configuration
1270 bool auth_required = server->config.require_client_identity;
1271 ctx->auth_required = auth_required;
1272
1273 // Plain WebSocket signaling requires the ACIP crypto handshake. TLS can
1274 // replace application encryption when identity authentication is not needed.
1275 bool skip_handshake = ctx->is_secure && !auth_required;
1276
1277 if (skip_handshake) {
1278 client_data->handshake_complete = true;
1279 log_info("Secure WebSocket connection from %s - using TLS without custom crypto handshake", client_ip);
1280 } else {
1281 log_info("WebSocket connection from %s - proceeding with crypto handshake%s", client_ip,
1282 auth_required ? " (client authentication required)" : "");
1283
1284 // Perform crypto handshake using shared server_like implementation
1285 log_debug("Performing crypto handshake with WebSocket client %s", client_ip);
1286 asciichat_error_t hs_result = session_server_like_handshake(&client_data->handshake_ctx, transport);
1287 if (hs_result != ASCIICHAT_OK) {
1288 log_warn("Crypto handshake failed for WebSocket client %s", client_ip);
1289 tcp_server_remove_client(server->tcp_server, synthetic_id);
1290 SAFE_FREE(ctx);
1291 return NULL;
1292 }
1293
1294 client_data->handshake_complete = true;
1295 log_debug("Crypto handshake completed for WebSocket client %s", client_ip);
1296 transport->crypto_ctx = &client_data->handshake_ctx.crypto_ctx;
1297 }
1298
1299 // Main packet processing loop using transport recv
1300 while (atomic_load_bool(&server->tcp_server->running)) {
1301 void *recv_buffer = NULL;
1302 size_t recv_len = 0;
1303 void *alloc_buffer = NULL;
1304
1305 asciichat_error_t recv_result = acds_receive_websocket_packet(transport, &recv_buffer, &recv_len, &alloc_buffer);
1306 if (recv_result != ASCIICHAT_OK) {
1307 // Check if this is a timeout or disconnect
1308 if (recv_result == ERROR_NETWORK_TIMEOUT) {
1309 log_debug("WebSocket client %s: receive timeout, continuing to wait for packets", client_ip);
1310 if (alloc_buffer) {
1311 buffer_pool_free(NULL, alloc_buffer, 0);
1312 }
1313 continue;
1314 }
1315
1316 // Actual disconnect or fatal error
1317 log_info("WebSocket client %s disconnected", client_ip);
1318 if (alloc_buffer) {
1319 buffer_pool_free(NULL, alloc_buffer, 0);
1320 }
1321 break;
1322 }
1323
1324 // Parse packet header from received data
1325 if (recv_len < sizeof(packet_header_t)) {
1326 log_warn("WebSocket client %s: received packet too small (%zu bytes)", client_ip, recv_len);
1327 if (alloc_buffer) {
1328 buffer_pool_free(NULL, alloc_buffer, 0);
1329 }
1330 continue;
1331 }
1332
1333 const packet_header_t *header = (const packet_header_t *)recv_buffer;
1334 packet_type_t packet_type = (packet_type_t)NET_TO_HOST_U16(header->type);
1335 size_t payload_size = NET_TO_HOST_U32(header->length);
1336 const void *payload = (const uint8_t *)recv_buffer + sizeof(packet_header_t);
1337
1338 // Validate payload size
1339 if (recv_len < sizeof(packet_header_t) + payload_size) {
1340 log_warn("WebSocket client %s: packet size mismatch (expected %zu, got %zu)", client_ip,
1341 sizeof(packet_header_t) + payload_size, recv_len);
1342 if (alloc_buffer) {
1343 buffer_pool_free(NULL, alloc_buffer, 0);
1344 }
1345 continue;
1346 }
1347
1348 log_debug("Received packet type 0x%02X from WebSocket client %s, length=%zu", packet_type, client_ip, payload_size);
1349
1350 // Multi-key session creation protocol: block non-PING/PONG/SESSION_CREATE messages
1351 if (client_data->in_multikey_session_create) {
1352 bool allowed = (packet_type == PACKET_TYPE_ACIP_SESSION_CREATE || packet_type == PACKET_TYPE_PING ||
1353 packet_type == PACKET_TYPE_PONG);
1354
1355 if (!allowed) {
1356 log_warn("WebSocket client %s sent packet type 0x%02X during multi-key session creation - only "
1357 "SESSION_CREATE/PING/PONG allowed",
1358 client_ip, packet_type);
1360 "Only SESSION_CREATE/PING/PONG allowed during multi-key session creation");
1361 if (alloc_buffer) {
1362 buffer_pool_free(NULL, alloc_buffer, 0);
1363 }
1364 continue;
1365 }
1366 }
1367
1368 // O(1) ACIP array-based dispatch
1369 acip_acds_callbacks_t callbacks = g_acds_callbacks;
1370 callbacks.app_ctx = server;
1371
1372 asciichat_error_t dispatch_result =
1373 acip_handle_acds_packet(transport, packet_type, payload, payload_size, client_ip, &callbacks);
1374
1375 if (dispatch_result != ASCIICHAT_OK) {
1376 log_warn("ACIP handler failed for packet type 0x%02X from WebSocket client %s: %s", packet_type, client_ip,
1377 asciichat_error_string(dispatch_result));
1378 }
1379
1380 if (alloc_buffer) {
1381 buffer_pool_free(NULL, alloc_buffer, 0);
1382 }
1383 }
1384
1385 // Cleanup
1386 if (client_data->joined_session) {
1387 asciichat_error_t leave_result = acds_remove_client_from_session(server, client_data);
1388 if (leave_result != ASCIICHAT_OK) {
1389 log_warn("Could not clean up disconnected WebSocket participant %02x%02x...: %s", client_data->participant_id[0],
1390 client_data->participant_id[1], asciichat_error_string(leave_result));
1391 }
1392 }
1393 tcp_server_remove_client(server->tcp_server, synthetic_id);
1394 log_debug("WebSocket client %s unregistered (total=%zu)", client_ip, tcp_server_get_client_count(server->tcp_server));
1395
1396 // Transport is owned by WebSocket server, don't destroy it here
1397
1398 log_info("WebSocket client handler finished for %s", client_ip);
1399
1400 SAFE_FREE(ctx);
1401
1402 return NULL;
1403}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned char uint8_t
Definition common.h:56
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
Network packet header structure.
Definition packet.h:598
uint32_t length
Payload data length in bytes (0 for header-only packets)
Definition packet.h:604
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602
Context passed to client handler thread.
acip_transport_t * transport
ACIP transport for this client.
char client_ip[64]
Client IP address (for logging/rate limiting)
bool auth_required
True if client authentication is required.
void * user_data
User-provided data from server config.
bool is_secure
True if connection is using TLS (wss://)

References acip_handle_acds_packet(), acip_send_error(), acip_acds_callbacks_t::app_ctx, ASCIICHAT_OK, atomic_load_bool(), websocket_client_context_t::auth_required, buffer_pool_free(), websocket_client_context_t::client_ip, acds_server_t::config, crypto_handshake_context_t::crypto_ctx, acip_transport::crypto_ctx, crypto_handshake_init(), private_key_t::ed25519, ERROR_INVALID_PARAM, ERROR_NETWORK_TIMEOUT, acds_client_data_t::handshake_complete, acds_client_data_t::handshake_ctx, acds_server_t::identity_public, acds_server_t::identity_secret, acds_client_data_t::in_multikey_session_create, websocket_client_context_t::is_secure, acds_client_data_t::joined_session, public_key_t::key, private_key_t::key, KEY_TYPE_ED25519, packet_header_t::length, log_debug, log_error, log_info, log_warn, NET_TO_HOST_U16, NET_TO_HOST_U32, PACKET_TYPE_ACIP_SESSION_CREATE, PACKET_TYPE_PING, PACKET_TYPE_PONG, acds_client_data_t::participant_id, acds_client_data_t::registry_id, acds_config_t::require_client_identity, tcp_server::running, SAFE_CALLOC, SAFE_FREE, SAFE_SNPRINTF, crypto_handshake_context_t::server_private_key, crypto_handshake_context_t::server_public_key, session_server_like_handshake(), acds_server_t::tcp_server, tcp_server_add_client(), tcp_server_get_client_count(), tcp_server_remove_client(), websocket_client_context_t::transport, acds_client_data_t::transport, public_key_t::type, private_key_t::type, packet_header_t::type, acip_transport::user_data, and websocket_client_context_t::user_data.

Referenced by acds_main().