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

Go to the source code of this file.

Macros

#define MAX_RECONNECT_DELAY   (5LL * NS_PER_SEC_INT)
 

Functions

tcp_client_t * tcp_client_create (void)
 Create and initialize TCP client.
 
void tcp_client_destroy (tcp_client_t **client_ptr)
 Destroy TCP client and free resources.
 
bool tcp_client_is_active (const tcp_client_t *client)
 Check if connection is currently active.
 
bool tcp_client_is_lost (const tcp_client_t *client)
 Check if connection was lost.
 
socket_t tcp_client_get_socket (const tcp_client_t *client)
 Get current socket descriptor.
 
uint32_t tcp_client_get_id (const tcp_client_t *client)
 Get client ID assigned by server.
 
void tcp_client_signal_lost (tcp_client_t *client)
 Signal that connection was lost (triggers reconnection)
 
void tcp_client_close (tcp_client_t *client)
 Close connection gracefully.
 
void tcp_client_shutdown (tcp_client_t *client)
 Shutdown connection forcefully (for signal handlers)
 
int tcp_client_send_packet (tcp_client_t *client, packet_type_t type, const void *data, size_t len)
 Send packet with thread-safe mutex protection.
 
int tcp_client_send_ping (tcp_client_t *client)
 Send ping packet.
 
int tcp_client_send_pong (tcp_client_t *client)
 Send pong packet.
 
int tcp_client_connect (tcp_client_t *client, const char *address, int port, int reconnect_attempt, bool first_connection, bool has_ever_connected)
 Establish TCP connection to server.
 

Macro Definition Documentation

◆ MAX_RECONNECT_DELAY

#define MAX_RECONNECT_DELAY   (5LL * NS_PER_SEC_INT)

Maximum delay between reconnection attempts (nanoseconds)

Definition at line 56 of file lib/network/tcp/client.c.

Function Documentation

◆ tcp_client_close()

void tcp_client_close ( tcp_client_t *  client)

Close connection gracefully.

Definition at line 202 of file lib/network/tcp/client.c.

202 {
203 if (!client)
204 return;
205
206 log_debug("Closing client connection");
207
208 // Mark connection as inactive
209 atomic_store(&client->connection_active, false);
210
211 // Close socket
212 if (socket_is_valid(client->sockfd)) {
213 close_socket_safe(client->sockfd);
214 client->sockfd = INVALID_SOCKET_VALUE;
215 }
216
217 // Reset client ID
218 client->my_client_id = 0;
219}

◆ tcp_client_connect()

int tcp_client_connect ( tcp_client_t *  client,
const char *  address,
int  port,
int  reconnect_attempt,
bool  first_connection,
bool  has_ever_connected 
)

Establish TCP connection to server.

Performs full connection lifecycle:

  • DNS resolution with IPv4/IPv6 dual-stack support
  • Socket creation and connection with timeout
  • Crypto handshake (if enabled)
  • Initial capability exchange
  • Client ID assignment from local port
Parameters
clientTCP client instance
addressServer hostname or IP address
portServer port number
reconnect_attemptCurrent reconnection attempt (0 for first, 1+ for retries)
first_connectionTrue if this is the very first connection since program start
has_ever_connectedTrue if client has successfully connected at least once
Returns
0 on success, negative on error

Definition at line 311 of file lib/network/tcp/client.c.

312 {
313 (void)first_connection; // Currently unused
314 (void)has_ever_connected; // Currently unused
315
316 if (!client || !address || port <= 0) {
317 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid client, address, or port");
318 }
319
320 // Close any existing connection
321 if (socket_is_valid(client->sockfd)) {
322 close_socket_safe(client->sockfd);
323 client->sockfd = INVALID_SOCKET_VALUE;
324 }
325
326 // Apply reconnection delay if this is a retry
327 if (reconnect_attempt > 0) {
328 uint64_t delay_ns = get_reconnect_delay(reconnect_attempt);
329 platform_sleep_ns(delay_ns);
330 }
331
332 // Resolve server address using getaddrinfo() for IPv4/IPv6 support
333 // Special handling for localhost: ensure we try both IPv6 (::1) and IPv4 (127.0.0.1)
334 bool is_localhost = (strcmp(address, "localhost") == 0 || is_localhost_ipv4(address) || is_localhost_ipv6(address));
335
336 struct addrinfo hints, *res = NULL, *addr_iter;
337 memset(&hints, 0, sizeof(hints));
338 hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
339 hints.ai_socktype = SOCK_STREAM;
340 if (is_localhost) {
341 hints.ai_flags = AI_NUMERICSERV; // Optimize for localhost
342 }
343
344 char port_str[16];
345 SAFE_SNPRINTF(port_str, sizeof(port_str), "%d", port);
346
347 // For localhost, try IPv6 loopback (::1) first, then fall back to IPv4
348 if (is_localhost) {
349 log_debug("Localhost detected - trying IPv6 loopback [::1]:%s first...", port_str);
350 hints.ai_family = AF_INET6;
351 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
352
353 int ipv6_result = getaddrinfo("::1", port_str, &hints, &res);
354 if (ipv6_result == 0 && res != NULL) {
355 // Try IPv6 loopback connection
356 client->sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
357 if (client->sockfd != INVALID_SOCKET_VALUE) {
358 log_debug("Trying IPv6 loopback connection to [::1]:%s...", port_str);
359 if (connect_with_timeout(client->sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
360 log_debug("Connection successful using IPv6 loopback");
361 SAFE_STRNCPY(client->server_ip, "::1", sizeof(client->server_ip));
362 freeaddrinfo(res);
363 res = NULL; // Prevent double-free at connection_success label
364 goto connection_success;
365 }
366 close_socket_safe(client->sockfd);
367 client->sockfd = INVALID_SOCKET_VALUE;
368 }
369 freeaddrinfo(res);
370 res = NULL;
371 }
372
373 // IPv6 failed, try IPv4 loopback (127.0.0.1)
374 log_debug("IPv6 failed, trying IPv4 loopback 127.0.0.1:%s...", port_str);
375 hints.ai_family = AF_INET;
376
377 int ipv4_result = getaddrinfo("127.0.0.1", port_str, &hints, &res);
378 if (ipv4_result == 0 && res != NULL) {
379 client->sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
380 if (client->sockfd != INVALID_SOCKET_VALUE) {
381 log_debug("Trying IPv4 loopback connection to 127.0.0.1:%s...", port_str);
382 if (connect_with_timeout(client->sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
383 log_debug("Connection successful using IPv4 loopback");
384 SAFE_STRNCPY(client->server_ip, "127.0.0.1", sizeof(client->server_ip));
385 freeaddrinfo(res);
386 res = NULL;
387 goto connection_success;
388 }
389 close_socket_safe(client->sockfd);
390 client->sockfd = INVALID_SOCKET_VALUE;
391 }
392 freeaddrinfo(res);
393 res = NULL;
394 }
395
396 // Both IPv6 and IPv4 loopback failed for localhost
397 log_warn("Could not connect to localhost using either IPv6 or IPv4 loopback");
398 return -1;
399 }
400
401 // For non-localhost addresses, use standard resolution
402 log_debug("Resolving server address '%s' port %s...", address, port_str);
403 hints.ai_family = AF_UNSPEC;
404 hints.ai_flags = 0;
405 int getaddr_result = getaddrinfo(address, port_str, &hints, &res);
406 if (getaddr_result != 0) {
407 log_error("Failed to resolve server address '%s': %s", address, gai_strerror(getaddr_result));
408 return -1;
409 }
410
411 // Try each address returned by getaddrinfo() - prefer IPv6, fall back to IPv4
412 for (int address_family = AF_INET6; address_family >= AF_INET; address_family -= (AF_INET6 - AF_INET)) {
413 for (addr_iter = res; addr_iter != NULL; addr_iter = addr_iter->ai_next) {
414 if (addr_iter->ai_family != address_family) {
415 continue;
416 }
417
418 client->sockfd = socket_create(addr_iter->ai_family, addr_iter->ai_socktype, addr_iter->ai_protocol);
419 if (client->sockfd == INVALID_SOCKET_VALUE) {
420 continue;
421 }
422
423 if (addr_iter->ai_family == AF_INET) {
424 log_debug("Trying IPv4 connection...");
425 } else if (addr_iter->ai_family == AF_INET6) {
426 log_debug("Trying IPv6 connection...");
427 }
428
429 if (connect_with_timeout(client->sockfd, addr_iter->ai_addr, addr_iter->ai_addrlen, CONNECT_TIMEOUT)) {
430 log_debug("Connection successful using %s", addr_iter->ai_family == AF_INET ? "IPv4"
431 : addr_iter->ai_family == AF_INET6 ? "IPv6"
432 : "unknown protocol");
433
434 // Extract server IP address for known_hosts
435 if (format_ip_address(addr_iter->ai_family, addr_iter->ai_addr, client->server_ip, sizeof(client->server_ip)) ==
436 ASCIICHAT_OK) {
437 log_debug("Resolved server IP: %s", client->server_ip);
438 } else {
439 log_warn("Failed to format server IP address");
440 }
441
442 goto connection_success;
443 }
444
445 close_socket_safe(client->sockfd);
446 client->sockfd = INVALID_SOCKET_VALUE;
447 }
448 }
449
450connection_success:
451
452 if (res) {
453 freeaddrinfo(res);
454 }
455
456 // If we exhausted all addresses without success, fail
457 if (client->sockfd == INVALID_SOCKET_VALUE) {
458 log_warn("Could not connect to server %s:%d (tried all addresses)", address, port);
459 return -1;
460 }
461
462 // Extract local port for client ID
463 struct sockaddr_storage local_addr = {0};
464 socklen_t addr_len = sizeof(local_addr);
465 if (getsockname(client->sockfd, (struct sockaddr *)&local_addr, &addr_len) == -1) {
466 log_error("Failed to get local socket address: %s", network_error_string());
467 close_socket_safe(client->sockfd);
468 client->sockfd = INVALID_SOCKET_VALUE;
469 return -1;
470 }
471
472 // Extract port from either IPv4 or IPv6 address
473 int local_port = 0;
474 if (((struct sockaddr *)&local_addr)->sa_family == AF_INET) {
475 local_port = NET_TO_HOST_U16(((struct sockaddr_in *)&local_addr)->sin_port);
476 } else if (((struct sockaddr *)&local_addr)->sa_family == AF_INET6) {
477 local_port = NET_TO_HOST_U16(((struct sockaddr_in6 *)&local_addr)->sin6_port);
478 }
479 client->my_client_id = (uint32_t)local_port;
480
481 // Mark connection as active
482 atomic_store(&client->connection_active, true);
483 atomic_store(&client->connection_lost, false);
484 atomic_store(&client->should_reconnect, false);
485
486 // Initialize crypto (application must set crypto_initialized flag)
487 // This is done outside this function by calling client_crypto_init()
488
489 // Configure socket options
490 if (socket_set_keepalive(client->sockfd, true) < 0) {
491 log_warn("Failed to set socket keepalive: %s", network_error_string());
492 }
493
494 asciichat_error_t sock_config_result = socket_configure_buffers(client->sockfd);
495 if (sock_config_result != ASCIICHAT_OK) {
496 log_warn("Failed to configure socket: %s", network_error_string());
497 }
498
499 log_debug("Connection established successfully to %s:%d (client_id=%u)", address, port, client->my_client_id);
500 return 0;
501}
int is_localhost_ipv6(const char *ip)
Definition ip.c:1320
int is_localhost_ipv4(const char *ip)
Definition ip.c:1299
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Definition ip.c:196
bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
Connect with timeout.
const char * network_error_string()
Get human-readable error string for network errors.
asciichat_error_t socket_configure_buffers(socket_t sockfd)
Configure socket buffers and TCP_NODELAY for optimal performance.

References connect_with_timeout(), format_ip_address(), is_localhost_ipv4(), is_localhost_ipv6(), network_error_string(), and socket_configure_buffers().

Referenced by connection_attempt_tcp().

◆ tcp_client_create()

tcp_client_t * tcp_client_create ( void  )

Create and initialize TCP client.

Allocates tcp_client_t and initializes all fields to safe defaults.

Definition at line 95 of file lib/network/tcp/client.c.

95 {
96 tcp_client_t *client = SAFE_MALLOC(sizeof(tcp_client_t), tcp_client_t *);
97 if (!client) {
98 log_error("Failed to allocate tcp_client_t");
99 return NULL;
100 }
101
102 memset(client, 0, sizeof(*client));
103
104 client->sockfd = INVALID_SOCKET_VALUE;
105 atomic_store(&client->connection_active, false);
106 atomic_store(&client->connection_lost, false);
107 atomic_store(&client->should_reconnect, false);
108 client->my_client_id = 0;
109 memset(client->server_ip, 0, sizeof(client->server_ip));
110 client->encryption_enabled = false;
111
112 if (mutex_init(&client->send_mutex) != 0) {
113 log_error("Failed to initialize send mutex");
114 SAFE_FREE(client);
115 return NULL;
116 }
117
118 log_debug("TCP client created successfully");
119 return client;
120}
int mutex_init(mutex_t *mutex)
Definition threading.c:16

References mutex_init().

Referenced by connection_attempt_tcp(), and session_client_like_run().

◆ tcp_client_destroy()

void tcp_client_destroy ( tcp_client_t **  client_ptr)

Destroy TCP client and free resources.

Must be called AFTER all threads have been joined.

Definition at line 127 of file lib/network/tcp/client.c.

127 {
128 if (!client_ptr || !*client_ptr) {
129 return;
130 }
131
132 tcp_client_t *client = *client_ptr;
133
134 if (socket_is_valid(client->sockfd)) {
135 close_socket_safe(client->sockfd);
136 client->sockfd = INVALID_SOCKET_VALUE;
137 }
138
139 mutex_destroy(&client->send_mutex);
140 SAFE_FREE(*client_ptr);
141
142 log_debug("TCP client destroyed");
143}
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21

References mutex_destroy().

Referenced by connection_attempt_tcp(), connection_context_cleanup(), and session_client_like_run().

◆ tcp_client_get_id()

uint32_t tcp_client_get_id ( const tcp_client_t *  client)

Get client ID assigned by server.

Definition at line 177 of file lib/network/tcp/client.c.

177 {
178 return client ? client->my_client_id : 0;
179}

◆ tcp_client_get_socket()

socket_t tcp_client_get_socket ( const tcp_client_t *  client)

Get current socket descriptor.

Definition at line 170 of file lib/network/tcp/client.c.

170 {
171 return client ? client->sockfd : INVALID_SOCKET_VALUE;
172}

Referenced by connection_attempt_tcp().

◆ tcp_client_is_active()

bool tcp_client_is_active ( const tcp_client_t *  client)

Check if connection is currently active.

Definition at line 152 of file lib/network/tcp/client.c.

152 {
153 if (!client)
154 return false;
155 return atomic_load(&client->connection_active);
156}

◆ tcp_client_is_lost()

bool tcp_client_is_lost ( const tcp_client_t *  client)

Check if connection was lost.

Definition at line 161 of file lib/network/tcp/client.c.

161 {
162 if (!client)
163 return false;
164 return atomic_load(&client->connection_lost);
165}

◆ tcp_client_send_packet()

int tcp_client_send_packet ( tcp_client_t *  client,
packet_type_t  type,
const void *  data,
size_t  len 
)

Send packet with thread-safe mutex protection.

All packet transmission goes through this function to ensure packets aren't interleaved on the wire.

Definition at line 246 of file lib/network/tcp/client.c.

246 {
247 if (!client) {
248 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client");
249 }
250
251 if (!atomic_load(&client->connection_active)) {
252 return SET_ERRNO(ERROR_NETWORK, "Connection not active");
253 }
254
255 // Acquire send mutex for thread-safe transmission
256 mutex_lock(&client->send_mutex);
257
258 // Send packet without encryption (crypto is handled at app_client layer)
259 asciichat_error_t result = (asciichat_error_t)send_packet(client->sockfd, type, data, len);
260
261 mutex_unlock(&client->send_mutex);
262
263 if (result != ASCIICHAT_OK) {
264 log_debug("Failed to send packet type %d: %s", type, asciichat_error_string(result));
265 return -1;
266 }
267
268 return 0;
269}
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
Definition packet.c:753

References send_packet().

Referenced by tcp_client_send_ping(), and tcp_client_send_pong().

◆ tcp_client_send_ping()

int tcp_client_send_ping ( tcp_client_t *  client)

Send ping packet.

Definition at line 274 of file lib/network/tcp/client.c.

274 {
275 if (!client)
276 return -1;
277 return tcp_client_send_packet(client, PACKET_TYPE_PING, NULL, 0);
278}
int tcp_client_send_packet(tcp_client_t *client, packet_type_t type, const void *data, size_t len)
Send packet with thread-safe mutex protection.

References tcp_client_send_packet().

◆ tcp_client_send_pong()

int tcp_client_send_pong ( tcp_client_t *  client)

Send pong packet.

Definition at line 283 of file lib/network/tcp/client.c.

283 {
284 if (!client)
285 return -1;
286 return tcp_client_send_packet(client, PACKET_TYPE_PONG, NULL, 0);
287}

References tcp_client_send_packet().

◆ tcp_client_shutdown()

void tcp_client_shutdown ( tcp_client_t *  client)

Shutdown connection forcefully (for signal handlers)

Definition at line 224 of file lib/network/tcp/client.c.

224 {
225 if (!client)
226 return;
227
228 atomic_store(&client->connection_active, false);
229
230 // Shutdown socket for reading/writing to interrupt blocking calls
231 if (socket_is_valid(client->sockfd)) {
232 socket_shutdown(client->sockfd, SHUT_RDWR);
233 }
234}

◆ tcp_client_signal_lost()

void tcp_client_signal_lost ( tcp_client_t *  client)

Signal that connection was lost (triggers reconnection)

Definition at line 188 of file lib/network/tcp/client.c.

188 {
189 if (!client)
190 return;
191
192 if (!atomic_load(&client->connection_lost)) {
193 atomic_store(&client->connection_lost, true);
194 atomic_store(&client->connection_active, false);
195 log_info("Connection lost signaled");
196 }
197}