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

TCP connection handler for client mode. More...

Go to the source code of this file.

Functions

const char * connection_state_name (connection_state_t state)
 Get human-readable state name for logging.
 
asciichat_error_t connection_context_init (connection_attempt_context_t *ctx)
 Initialize connection attempt context.
 
void connection_context_cleanup (connection_attempt_context_t *ctx)
 Cleanup connection attempt context.
 
asciichat_error_t connection_state_transition (connection_attempt_context_t *ctx, connection_state_t new_state)
 Transition to next connection state with validation.
 
bool connection_check_timeout (const connection_attempt_context_t *ctx)
 Check if connection attempt has exceeded timeout.
 
asciichat_error_t connection_attempt_tcp (connection_attempt_context_t *ctx, const char *server_address, uint16_t server_port, struct tcp_client *pre_created_tcp_client)
 Attempt direct TCP connection.
 
asciichat_error_t connection_attempt_websocket (connection_attempt_context_t *ctx, const char *ws_url)
 Attempt WebSocket connection (ws:// or wss://)
 

Detailed Description

TCP connection handler for client mode.

Implements direct TCP connection for client mode. Client mode uses direct TCP connections only - no WebRTC fallback.

Features:

  • Direct TCP connection to server
  • Timeout management
  • Proper resource cleanup
  • Detailed logging of connection attempts

Integration Points:

  • Called from src/client/main.c connection loop
  • Returns active transport when connection succeeds
Date
January 2026
Version
2.0

Definition in file connection_attempt.c.

Function Documentation

◆ connection_attempt_tcp()

asciichat_error_t connection_attempt_tcp ( connection_attempt_context_t ctx,
const char *  server_address,
uint16_t  server_port,
struct tcp_client *  pre_created_tcp_client 
)

Attempt direct TCP connection.

Attempt TCP connection.

Connects to server via TCP, performs crypto handshake if enabled, and creates ACIP transport for protocol communication.

Definition at line 177 of file connection_attempt.c.

178 {
179 log_info("=== connection_attempt_tcp CALLED: address='%s', port=%u, pre_created=%p ===", server_address, server_port,
180 (void *)pre_created_tcp_client);
181
182 if (!ctx || !server_address) {
183 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
184 }
185
186 // Check if shutdown was requested before attempting connection
187 if (should_exit()) {
188 return SET_ERRNO(ERROR_NETWORK, "Connection attempt aborted due to shutdown request");
189 }
190
191 // Check for WebSocket URL - handle separately from TCP
192 log_debug("connection_attempt_tcp: server_address='%s', port=%u", server_address, server_port);
193
194 if (url_is_websocket(server_address)) {
195 // WebSocket connection path
196 const char *ws_url = server_address;
197
198 // Parse for debug logging
199 url_parts_t url_parts = {0};
200 if (url_parse(server_address, &url_parts) == ASCIICHAT_OK) {
201 log_debug("WebSocket URL parsed: host=%s, port=%d, scheme=%s", url_parts.host, url_parts.port, url_parts.scheme);
202 }
203
204 log_info("Attempting WebSocket connection to %s", ws_url);
205
206 // Transition to attempting state
207 asciichat_error_t result = connection_state_transition(ctx, CONN_STATE_ATTEMPTING);
208 if (result != ASCIICHAT_OK) {
209 url_parts_destroy(&url_parts);
210 return result;
211 }
212
213 // Set timeout for this attempt
215 ctx->timeout_ns = CONN_TIMEOUT_TCP; // Use same timeout as TCP
216
217 // Compute crypto mode from CLI options
218 // Determine if client has authentication material (keys or password)
219 bool has_auth_material =
220 (GET_OPTION(encrypt_key)[0] != '\0' || GET_OPTION(num_identity_keys) > 0 || GET_OPTION(password)[0] != '\0');
221 bool no_encrypt = GET_OPTION(no_encrypt);
222
223 uint8_t crypto_mode;
224 if (!no_encrypt && !has_auth_material)
225 crypto_mode = ACIP_CRYPTO_ENCRYPT; // default: encrypt only
226 else if (!no_encrypt && has_auth_material)
227 crypto_mode = ACIP_CRYPTO_FULL; // full: encrypt + auth
228 else if (no_encrypt && has_auth_material)
229 crypto_mode = ACIP_CRYPTO_AUTH; // auth-only mode
230 else
231 crypto_mode = ACIP_CRYPTO_NONE; // no crypto
232
233 log_debug("WebSocket crypto mode computed: 0x%02x (encrypt=%d, auth=%d)", crypto_mode,
234 ACIP_CRYPTO_HAS_ENCRYPT(crypto_mode), ACIP_CRYPTO_HAS_AUTH(crypto_mode));
235
236 // Set crypto mode before initialization
237 client_crypto_set_mode(crypto_mode);
238
239 // Initialize crypto context if mode requires handshake (not ACIP_CRYPTO_NONE)
240 if (crypto_mode != ACIP_CRYPTO_NONE) {
241 log_debug("Initializing crypto context for WebSocket...");
242 if (client_crypto_init() != 0) {
243 log_error("Failed to initialize crypto context");
244 url_parts_destroy(&url_parts);
245 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
246 }
247 log_debug("Crypto context initialized successfully");
248 }
249
250 // Get crypto context
251 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
252
253 // Create WebSocket client instance
254 websocket_client_t *ws_client = websocket_client_create();
255 if (!ws_client) {
256 log_error("Failed to create WebSocket client");
258 url_parts_destroy(&url_parts);
259 return SET_ERRNO(ERROR_NETWORK, "WebSocket client creation failed");
260 }
261
262 // Connect via WebSocket
263 acip_transport_t *transport = websocket_client_connect(ws_client, ws_url, (crypto_context_t *)crypto_ctx);
264 if (!transport) {
265 log_error("Failed to create WebSocket ACIP transport");
266 websocket_client_destroy(&ws_client);
268 url_parts_destroy(&url_parts);
269 return SET_ERRNO(ERROR_NETWORK, "WebSocket connection failed");
270 }
271
272 log_info("WebSocket connection established to %s", ws_url);
274 ctx->active_transport = transport;
275 ctx->ws_client_instance = ws_client;
276 url_parts_destroy(&url_parts);
277 return ASCIICHAT_OK;
278 }
279
280 // TCP connection path (original logic)
281 log_info("Attempting TCP connection to %s:%u (3s timeout)", server_address, server_port);
282
283 // Transition to attempting state
284 asciichat_error_t result = connection_state_transition(ctx, CONN_STATE_ATTEMPTING);
285 if (result != ASCIICHAT_OK) {
286 return result;
287 }
288
289 // Use pre-created TCP client if provided, otherwise create one
290 tcp_client_t *tcp_client = pre_created_tcp_client;
291 bool created_tcp_client = false;
292 if (!tcp_client) {
293 tcp_client = tcp_client_create();
294 created_tcp_client = true;
295 if (!tcp_client) {
296 log_error("Failed to create TCP client");
298 return SET_ERRNO(ERROR_NETWORK, "TCP client creation failed");
299 }
300 log_debug("Created TCP client locally (not pre-created by framework)");
301 } else {
302 log_debug("Using pre-created TCP client from framework");
303 }
304
305 // Set timeout for this attempt
308
309 // Attempt TCP connection (reconnect_attempt is 0-based, convert for tcp_client_connect)
310 int tcp_result = tcp_client_connect(tcp_client, server_address, server_port, (int)ctx->reconnect_attempt,
311 ctx->reconnect_attempt == 0, ctx->reconnect_attempt > 0);
312
313 if (tcp_result != 0) {
314 log_debug("TCP connection failed (tcp_client_connect returned %d)", tcp_result);
315 if (created_tcp_client) {
316 tcp_client_destroy(&tcp_client);
317 }
319 return SET_ERRNO(ERROR_NETWORK, "TCP connection failed after %u attempts", ctx->reconnect_attempt);
320 }
321
322 // Extract socket from TCP client for crypto handshake
323 socket_t sockfd = tcp_client_get_socket(tcp_client);
324 if (sockfd == INVALID_SOCKET_VALUE) {
325 log_error("Failed to get socket from TCP client");
326 if (created_tcp_client) {
327 tcp_client_destroy(&tcp_client);
328 }
329 return SET_ERRNO(ERROR_NETWORK, "Invalid socket after TCP connection");
330 }
331
332 // Extract and set server IP for crypto context initialization
333 // TCP client already resolved and connected to the server IP, stored in tcp_client->server_ip
334 if (tcp_client->server_ip[0] != '\0') {
335 server_connection_set_ip(tcp_client->server_ip);
336 log_debug("Server IP extracted from TCP client: %s", tcp_client->server_ip);
337 } else {
338 log_warn("TCP client did not populate server_ip field");
339 }
340
341 // Compute crypto mode from CLI options
342 // Determine if client has authentication material (keys or password)
343 bool has_auth_material =
344 (GET_OPTION(encrypt_key)[0] != '\0' || GET_OPTION(num_identity_keys) > 0 || GET_OPTION(password)[0] != '\0');
345 bool no_encrypt = GET_OPTION(no_encrypt);
346
347 uint8_t crypto_mode;
348 if (!no_encrypt && !has_auth_material)
349 crypto_mode = ACIP_CRYPTO_ENCRYPT; // default: encrypt only
350 else if (!no_encrypt && has_auth_material)
351 crypto_mode = ACIP_CRYPTO_FULL; // full: encrypt + auth
352 else if (no_encrypt && has_auth_material)
353 crypto_mode = ACIP_CRYPTO_AUTH; // auth-only mode
354 else
355 crypto_mode = ACIP_CRYPTO_NONE; // no crypto
356
357 log_debug("TCP crypto mode computed: 0x%02x (encrypt=%d, auth=%d)", crypto_mode, ACIP_CRYPTO_HAS_ENCRYPT(crypto_mode),
358 ACIP_CRYPTO_HAS_AUTH(crypto_mode));
359
360 // Set crypto mode before initialization
361 client_crypto_set_mode(crypto_mode);
362
363 // Initialize crypto context if mode requires handshake (not ACIP_CRYPTO_NONE)
364 // This must happen AFTER setting server IP, as crypto init reads server IP/port
365 if (crypto_mode != ACIP_CRYPTO_NONE) {
366 log_debug("Initializing crypto context...");
367 if (client_crypto_init() != 0) {
368 log_error("Failed to initialize crypto context");
369 if (created_tcp_client) {
370 tcp_client_destroy(&tcp_client);
371 }
372 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
373 }
374 log_debug("Crypto context initialized successfully");
375
376 // Perform crypto handshake with server
377 log_debug("Performing crypto handshake with server...");
378 if (client_crypto_handshake(sockfd) != 0) {
379 log_error("Crypto handshake failed");
380 if (created_tcp_client) {
381 tcp_client_destroy(&tcp_client);
382 }
383 return SET_ERRNO(ERROR_NETWORK, "Crypto handshake failed");
384 }
385 log_debug("Crypto handshake completed successfully");
386 }
387
388 // Get crypto context after handshake
389 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
390
391 // Create ACIP transport for protocol-agnostic packet sending/receiving
392 acip_transport_t *transport = acip_tcp_transport_create(sockfd, (crypto_context_t *)crypto_ctx);
393 if (!transport) {
394 log_error("Failed to create ACIP transport for TCP");
395 if (created_tcp_client) {
396 tcp_client_destroy(&tcp_client);
397 }
398 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACIP transport");
399 }
400
401 log_info("TCP connection established to %s:%u", server_address, server_port);
403 ctx->active_transport = transport;
404
405 // Store tcp_client in context for proper lifecycle management only if we created it locally
406 // If it's pre-created by the framework, the framework will manage its lifecycle
407 if (created_tcp_client) {
408 ctx->tcp_client_instance = tcp_client;
409 log_debug("TCP client instance stored in connection context for cleanup");
410 } else {
411 log_debug("Using framework-managed TCP client, not storing in context");
412 }
413
414 return ASCIICHAT_OK;
415}
bool should_exit(void)
Definition main.c:90
asciichat_error_t connection_state_transition(connection_attempt_context_t *ctx, connection_state_t new_state)
Transition to next connection state with validation.
#define CONN_TIMEOUT_TCP
TCP connection timeout (3s)
@ CONN_STATE_CONNECTED
Successfully connected via TCP.
@ CONN_STATE_FAILED
Connection attempt failed.
@ CONN_STATE_ATTEMPTING
Attempting TCP connection.
void server_connection_set_ip(const char *ip)
Set the server IP address.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
void client_crypto_set_mode(uint8_t mode)
Set crypto mode for handshake (encryption + authentication)
int client_crypto_init(void)
Initialize client crypto handshake.
int client_crypto_handshake(socket_t socket)
Perform crypto handshake with server.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
int socket_t
tcp_client_t * tcp_client_create(void)
Create and initialize TCP client.
socket_t tcp_client_get_socket(const tcp_client_t *client)
Get current socket descriptor.
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.
void tcp_client_destroy(tcp_client_t **client_ptr)
Destroy TCP client and free resources.
acip_transport_t * websocket_client_connect(websocket_client_t *client, const char *url, struct crypto_context_t *crypto_ctx)
Establish WebSocket connection to server.
websocket_client_t * websocket_client_create(void)
Create and initialize WebSocket client.
void websocket_client_destroy(websocket_client_t **client_ptr)
Destroy WebSocket client and free resources.
uint32_t reconnect_attempt
Reconnection attempt number (1st, 2nd, etc.)
struct tcp_client * tcp_client_instance
TCP client instance - owned by context.
acip_transport_t * active_transport
Currently active transport.
uint64_t timeout_ns
Timeout for connection attempt.
struct websocket_client * ws_client_instance
WebSocket client instance - owned by context.
uint64_t attempt_start_time_ns
When current attempt began.
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
void url_parts_destroy(url_parts_t *parts)
Definition url.c:281
bool url_is_websocket(const char *url)
Definition url.c:307
asciichat_error_t url_parse(const char *url, url_parts_t *parts_out)
Definition url.c:166
uint64_t time_get_realtime_ns(void)
Definition util/time.c:59

References acip_tcp_transport_create(), connection_attempt_context_t::active_transport, connection_attempt_context_t::attempt_start_time_ns, client_crypto_handshake(), client_crypto_init(), client_crypto_set_mode(), CONN_STATE_ATTEMPTING, CONN_STATE_CONNECTED, CONN_STATE_FAILED, CONN_TIMEOUT_TCP, connection_state_transition(), crypto_client_get_context(), crypto_client_is_ready(), connection_attempt_context_t::reconnect_attempt, server_connection_set_ip(), should_exit(), tcp_client_connect(), tcp_client_create(), tcp_client_destroy(), tcp_client_get_socket(), connection_attempt_context_t::tcp_client_instance, time_get_realtime_ns(), connection_attempt_context_t::timeout_ns, url_is_websocket(), url_parse(), url_parts_destroy(), websocket_client_connect(), websocket_client_create(), websocket_client_destroy(), and connection_attempt_context_t::ws_client_instance.

◆ connection_attempt_websocket()

asciichat_error_t connection_attempt_websocket ( connection_attempt_context_t ctx,
const char *  ws_url 
)

Attempt WebSocket connection (ws:// or wss://)

Attempt WebSocket connection.

Connects to server via WebSocket, performs crypto handshake if enabled, and creates ACIP transport for protocol communication.

Definition at line 423 of file connection_attempt.c.

423 {
424 log_info("=== connection_attempt_websocket CALLED: url='%s' ===", ws_url);
425
426 if (!ctx || !ws_url) {
427 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
428 }
429
430 // Check if shutdown was requested
431 if (should_exit()) {
432 return SET_ERRNO(ERROR_NETWORK, "Connection attempt aborted due to shutdown request");
433 }
434
435 log_info("Attempting WebSocket connection to %s", ws_url);
436
437 // Transition to attempting state
438 asciichat_error_t result = connection_state_transition(ctx, CONN_STATE_ATTEMPTING);
439 if (result != ASCIICHAT_OK) {
440 return result;
441 }
442
443 // Set timeout for this attempt
446
447 // Initialize crypto context if encryption is enabled
448 if (!GET_OPTION(no_encrypt)) {
449 log_debug("Initializing crypto context for WebSocket...");
450 if (client_crypto_init() != 0) {
451 log_error("Failed to initialize crypto context");
452 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
453 }
454 log_debug("Crypto context initialized successfully");
455 }
456
457 // Get crypto context
458 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
459
460 // Create WebSocket client instance
461 websocket_client_t *ws_client = websocket_client_create();
462 if (!ws_client) {
463 log_error("Failed to create WebSocket client");
465 return SET_ERRNO(ERROR_NETWORK, "WebSocket client creation failed");
466 }
467
468 // Connect via WebSocket
469 acip_transport_t *transport = websocket_client_connect(ws_client, ws_url, (crypto_context_t *)crypto_ctx);
470 if (!transport) {
471 log_error("Failed to create WebSocket ACIP transport");
472 websocket_client_destroy(&ws_client);
474 return SET_ERRNO(ERROR_NETWORK, "WebSocket connection failed");
475 }
476
477 log_info("WebSocket connection established to %s", ws_url);
479 ctx->active_transport = transport;
480 ctx->ws_client_instance = ws_client;
481 log_debug("WebSocket client instance stored in connection context");
482
483 return ASCIICHAT_OK;
484}

References connection_attempt_context_t::active_transport, connection_attempt_context_t::attempt_start_time_ns, client_crypto_init(), CONN_STATE_ATTEMPTING, CONN_STATE_CONNECTED, CONN_STATE_FAILED, CONN_TIMEOUT_TCP, connection_state_transition(), crypto_client_get_context(), crypto_client_is_ready(), should_exit(), time_get_realtime_ns(), connection_attempt_context_t::timeout_ns, websocket_client_connect(), websocket_client_create(), websocket_client_destroy(), and connection_attempt_context_t::ws_client_instance.

◆ connection_check_timeout()

bool connection_check_timeout ( const connection_attempt_context_t ctx)

Check if connection attempt has exceeded timeout.

Compares elapsed time since attempt start against timeout.

Parameters
ctxConnection context
Returns
true if timeout exceeded, false otherwise

Definition at line 152 of file connection_attempt.c.

152 {
153 if (!ctx)
154 return false;
155
156 uint64_t elapsed_ns = time_get_realtime_ns() - ctx->attempt_start_time_ns;
157 bool timeout_exceeded = elapsed_ns > ctx->timeout_ns;
158
159 if (timeout_exceeded) {
160 log_warn("Connection timeout exceeded: elapsed %.3f seconds > %.3f seconds limit", time_ns_to_s(elapsed_ns),
161 time_ns_to_s(ctx->timeout_ns));
162 }
163
164 return timeout_exceeded;
165}

References connection_attempt_context_t::attempt_start_time_ns, time_get_realtime_ns(), and connection_attempt_context_t::timeout_ns.

◆ connection_context_cleanup()

void connection_context_cleanup ( connection_attempt_context_t ctx)

Cleanup connection attempt context.

Closes and releases any active transports, frees WebRTC peer manager. Called on connection success, failure, or shutdown.

Parameters
ctxConnection context to cleanup

Definition at line 102 of file connection_attempt.c.

102 {
103 if (!ctx)
104 return;
105
106 // Destroy TCP client instance if created
107 if (ctx->tcp_client_instance) {
109 ctx->tcp_client_instance = NULL;
110 log_debug("TCP client instance destroyed");
111 }
112
113 // Destroy WebSocket client instance if created
114 if (ctx->ws_client_instance) {
116 ctx->ws_client_instance = NULL;
117 log_debug("WebSocket client instance destroyed");
118 }
119
120 // Close active transport if still open
121 if (ctx->active_transport) {
122 acip_transport_close(ctx->active_transport);
124 ctx->active_transport = NULL;
125 log_debug("Transport connection closed");
126 }
127
128 log_debug("Connection context cleaned up");
129}
void acip_transport_destroy(acip_transport_t *transport)

References acip_transport_destroy(), connection_attempt_context_t::active_transport, tcp_client_destroy(), connection_attempt_context_t::tcp_client_instance, websocket_client_destroy(), and connection_attempt_context_t::ws_client_instance.

Referenced by client_main().

◆ connection_context_init()

asciichat_error_t connection_context_init ( connection_attempt_context_t ctx)

Initialize connection attempt context.

Sets up initial state, resets timeouts, and prepares for connection attempt.

Parameters
ctxConnection context to initialize
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 74 of file connection_attempt.c.

74 {
75 if (!ctx) {
76 return SET_ERRNO(ERROR_INVALID_PARAM, "Context pointer is NULL");
77 }
78
79 // Reset context
80 memset(ctx, 0, sizeof(connection_attempt_context_t));
81
82 // Initialize state
85
86 // Initialize timeout
89
90 // Initialize counters
91 ctx->reconnect_attempt = 1;
92 ctx->total_transitions = 0;
93
94 log_debug("Connection context initialized");
95
96 return ASCIICHAT_OK;
97}
@ CONN_STATE_IDLE
Not connected, no attempt in progress.
Context for TCP connection attempts.
connection_state_t current_state
Current connection state.
connection_state_t previous_state
Previous state (for debugging)
uint32_t total_transitions
Total state transitions (for metrics)

References connection_attempt_context_t::attempt_start_time_ns, CONN_STATE_IDLE, CONN_TIMEOUT_TCP, connection_attempt_context_t::current_state, connection_attempt_context_t::previous_state, connection_attempt_context_t::reconnect_attempt, time_get_realtime_ns(), connection_attempt_context_t::timeout_ns, and connection_attempt_context_t::total_transitions.

Referenced by client_main().

◆ connection_state_name()

const char * connection_state_name ( connection_state_t  state)

Get human-readable state name for logging.

Parameters
stateConnection state
Returns
Static string representation (e.g., "DIRECT_TCP_CONNECTED")

Definition at line 50 of file connection_attempt.c.

50 {
51 switch (state) {
52 case CONN_STATE_IDLE:
53 return "IDLE";
55 return "ATTEMPTING";
57 return "CONNECTED";
59 return "DISCONNECTED";
61 return "FAILED";
62 default:
63 return "UNKNOWN";
64 }
65}
@ CONN_STATE_DISCONNECTED
Clean disconnect (user initiated)

References CONN_STATE_ATTEMPTING, CONN_STATE_CONNECTED, CONN_STATE_DISCONNECTED, CONN_STATE_FAILED, and CONN_STATE_IDLE.

Referenced by connection_state_transition().

◆ connection_state_transition()

asciichat_error_t connection_state_transition ( connection_attempt_context_t ctx,
connection_state_t  new_state 
)

Transition to next connection state with validation.

Transition to next connection state.

Definition at line 134 of file connection_attempt.c.

134 {
135 if (!ctx) {
136 return SET_ERRNO(ERROR_INVALID_PARAM, "Context pointer is NULL");
137 }
138
139 // Store previous state
140 ctx->previous_state = ctx->current_state;
141 ctx->current_state = new_state;
142 ctx->total_transitions++;
143
144 log_debug("State transition: %s → %s", connection_state_name(ctx->previous_state), connection_state_name(new_state));
145
146 return ASCIICHAT_OK;
147}
const char * connection_state_name(connection_state_t state)
Get human-readable state name for logging.

References connection_state_name(), connection_attempt_context_t::current_state, connection_attempt_context_t::previous_state, and connection_attempt_context_t::total_transitions.

Referenced by connection_attempt_tcp(), and connection_attempt_websocket().