ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
server.c File Reference

Go to the source code of this file.

Macros

#define ACDS_CREATE_TRANSPORT(socket, transport_var)
 
#define ACDS_DESTROY_TRANSPORT(transport_var)   acip_transport_destroy(transport_var)
 

Functions

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

Macro Definition Documentation

◆ ACDS_CREATE_TRANSPORT

#define ACDS_CREATE_TRANSPORT (   socket,
  transport_var 
)
Value:
acip_transport_t *transport_var = acip_tcp_transport_create(socket, NULL); \
if (!transport_var) { \
log_error("Failed to create ACDS transport"); \
return; \
}
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)

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

324 { \
325 log_error("Failed to create ACDS transport"); \
326 return; \
327 }

◆ ACDS_DESTROY_TRANSPORT

#define ACDS_DESTROY_TRANSPORT (   transport_var)    acip_transport_destroy(transport_var)

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

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 821 of file src/discovery-service/server.c.

821 {
822 tcp_client_context_t *ctx = (tcp_client_context_t *)arg;
823 if (!ctx) {
824 log_error("Client handler: NULL context");
825 return NULL;
826 }
827
828 acds_server_t *server = (acds_server_t *)ctx->user_data;
829 socket_t client_socket = ctx->client_socket;
830
831 // Get client IP for logging
832 char client_ip[INET6_ADDRSTRLEN] = {0};
833 tcp_client_context_get_ip(ctx, client_ip, sizeof(client_ip));
834
835 log_info("Client handler started for %s", client_ip);
836
837 // Register client in TCP server registry with allocated client data
838 acds_client_data_t *client_data = SAFE_MALLOC(sizeof(acds_client_data_t), acds_client_data_t *);
839 if (!client_data) {
840 tcp_server_reject_client(client_socket, "Failed to allocate client data");
841 SAFE_FREE(ctx);
842 return NULL;
843 }
844 memset(client_data, 0, sizeof(*client_data));
845 client_data->joined_session = false;
846 client_data->handshake_complete = false;
847
848 // Initialize crypto handshake context
849 asciichat_error_t handshake_result = crypto_handshake_init(&client_data->handshake_ctx, true);
850 if (handshake_result != ASCIICHAT_OK) {
851 log_error("Failed to initialize crypto handshake for client %s", client_ip);
852 SAFE_FREE(client_data);
853 tcp_server_reject_client(client_socket, "Failed to initialize crypto handshake");
854 SAFE_FREE(ctx);
855 return NULL;
856 }
857
858 // Set server identity keys for handshake
859 client_data->handshake_ctx.server_public_key.type = KEY_TYPE_ED25519;
860 client_data->handshake_ctx.server_private_key.type = KEY_TYPE_ED25519;
861 memcpy(client_data->handshake_ctx.server_public_key.key, server->identity_public, 32);
862 memcpy(client_data->handshake_ctx.server_private_key.key.ed25519, server->identity_secret, 64);
863
864 if (tcp_server_add_client(&server->tcp_server, client_socket, client_data) != ASCIICHAT_OK) {
865 SAFE_FREE(client_data);
866 tcp_server_reject_client(client_socket, "Failed to register client in registry");
867 SAFE_FREE(ctx);
868 return NULL;
869 }
870
871 log_debug("Client %s registered (socket=%d, total=%zu)", client_ip, client_socket,
872 tcp_server_get_client_count(&server->tcp_server));
873
874 // Perform crypto handshake (three-step process)
875 log_debug("Performing crypto handshake with client %s", client_ip);
876
877 // Step 1: Start handshake (send server key, receive client key)
878 handshake_result = crypto_handshake_server_start_socket(&client_data->handshake_ctx, client_socket);
879 if (handshake_result != ASCIICHAT_OK) {
880 log_warn("Crypto handshake start failed for client %s", client_ip);
881 tcp_server_remove_client(&server->tcp_server, client_socket);
882 SAFE_FREE(ctx);
883 return NULL;
884 }
885
886 // Step 2: Authentication challenge (if required)
887 handshake_result = crypto_handshake_server_auth_challenge_socket(&client_data->handshake_ctx, client_socket);
888 if (handshake_result != ASCIICHAT_OK) {
889 log_warn("Crypto handshake auth challenge failed for client %s", client_ip);
890 tcp_server_remove_client(&server->tcp_server, client_socket);
891 SAFE_FREE(ctx);
892 return NULL;
893 }
894
895 // Step 3: Complete handshake (verify and finalize)
896 handshake_result = crypto_handshake_server_complete_socket(&client_data->handshake_ctx, client_socket);
897 if (handshake_result != ASCIICHAT_OK) {
898 log_warn("Crypto handshake complete failed for client %s", client_ip);
899 tcp_server_remove_client(&server->tcp_server, client_socket);
900 SAFE_FREE(ctx);
901 return NULL;
902 }
903
904 client_data->handshake_complete = true;
905 log_info("Crypto handshake complete for client %s", client_ip);
906
907 // Main packet processing loop
908 while (atomic_load(&server->tcp_server.running)) {
909 packet_type_t packet_type;
910 void *payload = NULL;
911 size_t payload_size = 0;
912
913 // Receive packet (blocking with system timeout)
914 int result = receive_packet(client_socket, &packet_type, &payload, &payload_size);
915 if (result < 0) {
916 // Check error context to distinguish timeout from actual disconnect
917 asciichat_error_context_t err_ctx;
918 bool has_context = HAS_ERRNO(&err_ctx);
919
920 // Check if this is a timeout (non-fatal) or actual disconnect (fatal)
921 asciichat_error_t error = GET_ERRNO();
922 if (error == ERROR_NETWORK_TIMEOUT ||
923 (error == ERROR_NETWORK && has_context && strstr(err_ctx.context_message, "timed out") != NULL)) {
924 // Timeout waiting for next packet - this is normal, continue waiting
925 log_debug("Client %s: receive timeout, continuing to wait for packets", client_ip);
926 if (payload) {
927 buffer_pool_free(NULL, payload, payload_size);
928 }
929 continue;
930 }
931
932 // Actual disconnect or fatal error
933 log_info("Client %s disconnected", client_ip);
934 if (payload) {
935 buffer_pool_free(NULL, payload, payload_size);
936 }
937 break;
938 }
939
940 log_debug("Received packet type 0x%02X from %s, length=%zu", packet_type, client_ip, payload_size);
941
942 // Multi-key session creation protocol: block non-PING/PONG/SESSION_CREATE messages
943 if (client_data->in_multikey_session_create) {
944 bool allowed =
945 (packet_type == PACKET_TYPE_ACIP_SESSION_CREATE || packet_type == PACKET_TYPE_ACIP_DISCOVERY_PING ||
946 packet_type == PACKET_TYPE_PING || packet_type == PACKET_TYPE_PONG);
947
948 if (!allowed) {
949 log_warn("Client %s sent packet type 0x%02X during multi-key session creation - only SESSION_CREATE/PING/PONG "
950 "allowed",
951 client_ip, packet_type);
952
953 // Send error response
954 acip_transport_t *error_transport = acip_tcp_transport_create(client_socket, NULL);
955 if (error_transport) {
956 acip_send_error(error_transport, ERROR_INVALID_PARAM,
957 "Only SESSION_CREATE/PING/PONG allowed during multi-key session creation");
958 ACDS_DESTROY_TRANSPORT(error_transport);
959 }
960
961 // Free payload and continue
962 if (payload) {
963 buffer_pool_free(NULL, payload, payload_size);
964 }
965 continue;
966 }
967 }
968
969 // O(1) ACIP array-based dispatch
970 // Set server context for callbacks
971 acip_acds_callbacks_t callbacks = g_acds_callbacks;
972 callbacks.app_ctx = server;
973
974 asciichat_error_t dispatch_result =
975 acip_handle_acds_packet(NULL, packet_type, payload, payload_size, client_socket, client_ip, &callbacks);
976
977 if (dispatch_result != ASCIICHAT_OK) {
978 log_warn("ACIP handler failed for packet type 0x%02X from %s: %s", packet_type, client_ip,
979 asciichat_error_string(dispatch_result));
980 }
981
982 // Free payload (allocated by receive_packet via buffer_pool_alloc)
983 if (payload) {
984 buffer_pool_free(NULL, payload, payload_size);
985 }
986 }
987
988 // Cleanup
989 tcp_server_remove_client(&server->tcp_server, client_socket);
990 log_debug("Client %s unregistered (total=%zu)", client_ip, tcp_server_get_client_count(&server->tcp_server));
991
992 socket_close(client_socket);
993 SAFE_FREE(ctx);
994
995 log_info("Client handler finished for %s", client_ip);
996 return NULL;
997}
asciichat_error_t acip_handle_acds_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, int client_socket, const char *client_ip, const acip_acds_callbacks_t *callbacks)
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server)
int socket_t
asciichat_error_t crypto_handshake_server_auth_challenge_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Auth challenge using socket (TCP clients only)
asciichat_error_t crypto_handshake_server_start_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Start handshake using socket (TCP clients only)
asciichat_error_t crypto_handshake_server_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Complete handshake using socket (TCP clients only)
void tcp_server_reject_client(socket_t socket, const char *reason)
asciichat_error_t tcp_server_remove_client(tcp_server_t *server, socket_t socket)
const char * tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len)
size_t tcp_server_get_client_count(tcp_server_t *server)
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:766
asciichat_error_t acip_send_error(acip_transport_t *transport, uint32_t error_code, const char *message)
Definition send.c:233
#define ACDS_DESTROY_TRANSPORT(transport_var)
Per-client connection data.
bool handshake_complete
Whether crypto handshake has completed.
crypto_handshake_context_t handshake_ctx
Handshake context for encrypted communication.
bool joined_session
Whether client has successfully joined a session.
bool in_multikey_session_create
True during multi-key SESSION_CREATE sequence.
Discovery server state.

References ACDS_DESTROY_TRANSPORT, acip_handle_acds_packet(), acip_send_error(), acip_tcp_transport_create(), buffer_pool_free(), crypto_handshake_init(), crypto_handshake_server_auth_challenge_socket(), crypto_handshake_server_complete_socket(), crypto_handshake_server_start_socket(), 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, acds_client_data_t::joined_session, receive_packet(), tcp_client_context_get_ip(), acds_server_t::tcp_server, tcp_server_add_client(), tcp_server_get_client_count(), tcp_server_reject_client(), and tcp_server_remove_client().

Referenced by acds_server_init().

◆ 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 189 of file src/discovery-service/server.c.

189 {
190 if (!server || !config) {
191 return SET_ERRNO(ERROR_INVALID_PARAM, "server or config is NULL");
192 }
193
194 memset(server, 0, sizeof(*server));
195 memcpy(&server->config, config, sizeof(acds_config_t));
196
197 // Open database (SQLite as single source of truth)
198 asciichat_error_t result = database_init(config->database_path, &server->db);
199 if (result != ASCIICHAT_OK) {
200 return result;
201 }
202
203 // Initialize rate limiter with SQLite backend
204 server->rate_limiter = rate_limiter_create_sqlite(NULL); // NULL = externally managed DB
205 if (!server->rate_limiter) {
206 database_close(server->db);
207 return SET_ERRNO(ERROR_MEMORY, "Failed to create rate limiter");
208 }
209
210 // Set the database handle for the rate limiter
212
213 // Configure TCP server
214 tcp_server_config_t tcp_config = {
215 .port = config->port,
216 .ipv4_address = (config->address[0] != '\0') ? config->address : NULL,
217 .ipv6_address = (config->address6[0] != '\0') ? config->address6 : NULL,
218 .bind_ipv4 = (config->address[0] != '\0') || (config->address[0] == '\0' && config->address6[0] == '\0'),
219 .bind_ipv6 = (config->address6[0] != '\0') || (config->address[0] == '\0' && config->address6[0] == '\0'),
220 .accept_timeout_sec = 1,
221 .client_handler = acds_client_handler,
222 .user_data = server,
223 };
224
225 // Initialize TCP server
226 result = tcp_server_init(&server->tcp_server, &tcp_config);
227 if (result != ASCIICHAT_OK) {
229 database_close(server->db);
230 return result;
231 }
232
233 // Initialize background worker thread pool
234 atomic_store(&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");
240 database_close(server->db);
241 return SET_ERRNO(ERROR_MEMORY, "Failed to create worker thread pool");
242 }
243
244 // Spawn cleanup thread in worker pool
245 if (thread_pool_spawn(server->worker_pool, cleanup_thread_func, server, 0, "cleanup") != ASCIICHAT_OK) {
246 log_warn("Failed to spawn cleanup thread (continuing without cleanup)");
247 }
248
249 log_info("Discovery server initialized successfully");
250 return ASCIICHAT_OK;
251}
void database_close(sqlite3 *db)
asciichat_error_t database_init(const char *db_path, sqlite3 **db)
void tcp_server_destroy(tcp_server_t *server)
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
void rate_limiter_destroy(rate_limiter_t *limiter)
Definition rate_limit.c:116
void rate_limiter_set_sqlite_db(rate_limiter_t *limiter, void *db)
Definition rate_limit.c:107
rate_limiter_t * rate_limiter_create_sqlite(const char *db_path)
Definition rate_limit.c:90
void * acds_client_handler(void *arg)
Per-client connection handler (thread entry point)
Discovery server configuration.
char address[256]
IPv4 bind address (empty = all interfaces)
char database_path[512]
SQLite database path.
int port
TCP listen port (default 27225)
acds_config_t config
Runtime configuration.
atomic_bool shutdown
Shutdown flag for worker threads.
tcp_server_t tcp_server
TCP server abstraction.
sqlite3 * db
SQLite database handle.
struct rate_limiter_s * rate_limiter
SQLite-backed rate limiter.
thread_pool_t * worker_pool
Thread pool for background workers.
thread_pool_t * thread_pool_create(const char *pool_name)
Definition thread_pool.c:17
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Definition thread_pool.c:70

References acds_client_handler(), acds_config_t::address, acds_config_t::address6, acds_server_t::config, database_close(), database_init(), acds_config_t::database_path, acds_server_t::db, acds_config_t::port, acds_server_t::rate_limiter, rate_limiter_create_sqlite(), rate_limiter_destroy(), rate_limiter_set_sqlite_db(), acds_server_t::shutdown, acds_server_t::tcp_server, tcp_server_destroy(), tcp_server_init(), thread_pool_create(), thread_pool_spawn(), and acds_server_t::worker_pool.

Referenced by acds_main().

◆ acds_server_run()

asciichat_error_t acds_server_run ( acds_server_t server)

Run discovery server main loop.

Accepts client connections and spawns handler threads. Blocks until shutdown signal received.

Parameters
serverInitialized server structure
Returns
ASCIICHAT_OK on success, error code otherwise

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

253 {
254 if (!server) {
255 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
256 }
257
258 log_info("Discovery server accepting connections on port %d", server->config.port);
259
260 // Delegate to TCP server abstraction
261 return tcp_server_run(&server->tcp_server);
262}
asciichat_error_t tcp_server_run(tcp_server_t *server)

References acds_server_t::config, acds_config_t::port, acds_server_t::tcp_server, and tcp_server_run().

Referenced by acds_main().

◆ 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 264 of file src/discovery-service/server.c.

264 {
265 if (!server) {
266 return;
267 }
268
269 // Signal shutdown to worker threads
270 atomic_store(&server->shutdown, true);
271
272 // Shutdown TCP server (closes listen sockets, stops accept loop)
274
275 // Wait for all client handler threads to exit
276 size_t remaining_clients;
277 int shutdown_attempts = 0;
278 const int max_shutdown_attempts = 100; // 10 seconds (100 * 100ms)
279
280 while ((remaining_clients = tcp_server_get_client_count(&server->tcp_server)) > 0 &&
281 shutdown_attempts < max_shutdown_attempts) {
282 log_debug("Waiting for %zu client handler threads to exit (attempt %d/%d)", remaining_clients,
283 shutdown_attempts + 1, max_shutdown_attempts);
285 shutdown_attempts++;
286 }
287
288 if (remaining_clients > 0) {
289 log_warn("Server shutdown: %zu client handler threads still running after 10 seconds", remaining_clients);
290 } else if (shutdown_attempts > 0) {
291 log_debug("All client handler threads exited gracefully");
292 }
293
294 // Stop and destroy worker thread pool (cleanup thread, etc.)
295 if (server->worker_pool) {
297 server->worker_pool = NULL;
298 log_debug("Worker thread pool stopped");
299 }
300
301 // Destroy rate limiter
302 if (server->rate_limiter) {
304 server->rate_limiter = NULL;
305 }
306
307 // Close database
308 if (server->db) {
309 database_close(server->db);
310 server->db = NULL;
311 }
312
313 log_info("Server shutdown complete");
314}
void platform_sleep_ms(unsigned int ms)
void thread_pool_destroy(thread_pool_t *pool)
Definition thread_pool.c:48

References database_close(), acds_server_t::db, platform_sleep_ms(), acds_server_t::rate_limiter, rate_limiter_destroy(), acds_server_t::shutdown, acds_server_t::tcp_server, tcp_server_destroy(), tcp_server_get_client_count(), thread_pool_destroy(), and acds_server_t::worker_pool.

Referenced by acds_main().