ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
connection_attempt.c
Go to the documentation of this file.
1
22#include "connection_state.h"
23#include "server.h"
24#include "protocol.h"
25#include "crypto.h"
26#include "main.h"
27#include "../main.h" // Global exit API
28#include <ascii-chat/common.h>
29#include <ascii-chat/log/logging.h>
30#include <ascii-chat/options/options.h>
31#include <ascii-chat/util/url.h> // For WebSocket URL detection
32#include <ascii-chat/options/rcu.h>
33#include <ascii-chat/network/acip/client.h>
34#include <ascii-chat/network/tcp/client.h>
35#include <ascii-chat/network/websocket/client.h>
36#include <ascii-chat/platform/abstraction.h>
37
38#include <time.h>
39#include <string.h>
40#include <memory.h>
41#include <stdio.h>
42
43/* ============================================================================
44 * State Machine Helper Functions
45 * ============================================================================ */
46
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}
66
67/* ============================================================================
68 * Context Management
69 * ============================================================================ */
70
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}
98
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}
130
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}
148
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}
166
167/* ============================================================================
168 * TCP Connection
169 * ============================================================================ */
170
177asciichat_error_t connection_attempt_tcp(connection_attempt_context_t *ctx, const char *server_address,
178 uint16_t server_port, struct tcp_client *pre_created_tcp_client) {
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}
416
423asciichat_error_t connection_attempt_websocket(connection_attempt_context_t *ctx, const char *ws_url) {
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}
bool should_exit(void)
Definition main.c:90
asciichat_error_t connection_context_init(connection_attempt_context_t *ctx)
Initialize connection attempt context.
asciichat_error_t connection_attempt_websocket(connection_attempt_context_t *ctx, const char *ws_url)
Attempt WebSocket connection (ws:// or wss://)
void connection_context_cleanup(connection_attempt_context_t *ctx)
Cleanup connection attempt context.
bool connection_check_timeout(const connection_attempt_context_t *ctx)
Check if connection attempt has exceeded timeout.
asciichat_error_t connection_state_transition(connection_attempt_context_t *ctx, connection_state_t new_state)
Transition to next connection state with validation.
const char * connection_state_name(connection_state_t state)
Get human-readable state name for logging.
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.
Connection state machine for TCP client connections.
#define CONN_TIMEOUT_TCP
TCP connection timeout (3s)
connection_state_t
Simple TCP connection state machine.
@ CONN_STATE_CONNECTED
Successfully connected via TCP.
@ CONN_STATE_IDLE
Not connected, no attempt in progress.
@ CONN_STATE_DISCONNECTED
Clean disconnect (user initiated)
@ 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.
Server cryptographic operations and per-client handshake management.
ascii-chat Server Mode Entry Point Header
Server packet processing and protocol implementation.
Context for TCP connection attempts.
connection_state_t current_state
Current connection state.
connection_state_t previous_state
Previous state (for debugging)
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.
uint32_t total_transitions
Total state transitions (for metrics)
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 acip_transport_destroy(acip_transport_t *transport)
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