ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
connection_attempt.c
Go to the documentation of this file.
1
27#include <ascii-chat/common.h>
28#include <ascii-chat/log/log.h>
30#include <ascii-chat/util/url.h>
37
38#include <string.h>
39#include <memory.h>
40
41static void destroy_tcp_client_owner(void *owner) {
44}
45
46static void destroy_websocket_client_owner(void *owner) {
47 websocket_client_t *ws_client = (websocket_client_t *)owner;
48 websocket_client_destroy(&ws_client);
49}
50
51/* ============================================================================
52 * State Machine Helper Functions
53 * ============================================================================ */
54
59 switch (state) {
60 case CONN_STATE_IDLE:
61 return "IDLE";
63 return "ATTEMPTING";
65 return "CONNECTED";
67 return "DISCONNECTED";
69 return "FAILED";
70 default:
71 return "UNKNOWN";
72 }
73}
74
75/* ============================================================================
76 * Context Management
77 * ============================================================================ */
78
83 if (!ctx) {
84 return SET_ERRNO(ERROR_INVALID_PARAM, "Context pointer is NULL");
85 }
86
87 // Reset context
88 memset(ctx, 0, sizeof(connection_attempt_context_t));
90
91 // Initialize state
94
95 // Initialize timeout
98
99 // Initialize counters
100 ctx->reconnect_attempt = 1;
101 ctx->total_transitions = 0;
102
103 log_debug("Connection context initialized");
104
105 return ASCIICHAT_OK;
106}
107
112 if (!ctx)
113 return;
114
116
117 log_debug("Connection context cleaned up");
118}
119
124 if (!ctx) {
125 return SET_ERRNO(ERROR_INVALID_PARAM, "Context pointer is NULL");
126 }
127
128 // Store previous state
129 ctx->previous_state = ctx->current_state;
130 ctx->current_state = new_state;
131 ctx->total_transitions++;
132
133 log_debug("State transition: %s → %s", connection_state_name(ctx->previous_state), connection_state_name(new_state));
134
135 return ASCIICHAT_OK;
136}
137
142 if (!ctx)
143 return false;
144
146 bool timeout_exceeded = elapsed_ns > ctx->timeout_ns;
147
148 if (timeout_exceeded) {
149 char elapsed_str[32], timeout_str[32];
150 time_pretty(elapsed_ns, -1, elapsed_str, sizeof(elapsed_str));
151 time_pretty(ctx->timeout_ns, -1, timeout_str, sizeof(timeout_str));
152 log_warn("Connection timeout exceeded: elapsed %s > %s limit", elapsed_str, timeout_str);
153 }
154
155 return timeout_exceeded;
156}
157
158/* ============================================================================
159 * TCP Connection
160 * ============================================================================ */
161
169 uint16_t server_port) {
170 log_info("=== connection_attempt_tcp CALLED: address='%s', port=%u ===", server_address, server_port);
171
172 if (!ctx || !server_address) {
173 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
174 }
175
176 // Check if shutdown was requested before attempting connection (via callback if available)
178 return SET_ERRNO(ERROR_NETWORK, "Connection attempt aborted due to shutdown request");
179 }
180
181 // Normalize the endpoint once so TCP and WebSocket paths use the same rules.
182 connection_endpoint_t endpoint = {0};
183 asciichat_error_t endpoint_result = connection_endpoint_resolve(server_address, server_port, &endpoint);
184 if (endpoint_result != ASCIICHAT_OK) {
185 return endpoint_result;
186 }
187
188 log_debug("connection_attempt_tcp: server_address='%s', normalized=%s host=%s port=%u", server_address,
189 connection_endpoint_protocol_name(endpoint.protocol), endpoint.host, endpoint.port);
190
191 if (endpoint.protocol == CONNECTION_ENDPOINT_WEBSOCKET) {
192 // WebSocket connection path
193 const char *ws_url = endpoint.input;
194
195 // Set server IP/port for crypto handshake known_hosts verification
198 log_debug("Set server IP=%s, port=%u for WebSocket crypto handshake", endpoint.host, endpoint.port);
199
200 log_info("Attempting WebSocket connection to %s", ws_url);
201
202 // Transition to attempting state
204 if (result != ASCIICHAT_OK) {
205 return result;
206 }
207
208 // Set timeout for this attempt
210 ctx->timeout_ns = CONN_TIMEOUT_TCP; // Use same timeout as TCP
211
212 // Compute crypto mode from CLI options
213 // Determine if client has authentication material (keys or password)
214 bool has_auth_material =
215 (GET_OPTION(encrypt_key)[0] != '\0' || GET_OPTION(num_identity_keys) > 0 || GET_OPTION(password)[0] != '\0');
216 bool no_encrypt = GET_OPTION(no_encrypt);
217 bool no_auth = GET_OPTION(no_auth);
218
219 uint8_t crypto_mode;
220 if (!no_encrypt && !no_auth && !has_auth_material)
221 crypto_mode = ACIP_CRYPTO_ENCRYPT; // default: encrypt only
222 else if (!no_encrypt && !no_auth && has_auth_material)
223 crypto_mode = ACIP_CRYPTO_FULL; // full: encrypt + auth
224 else if (no_encrypt && !no_auth && has_auth_material)
225 crypto_mode = ACIP_CRYPTO_AUTH; // auth-only mode
226 else
227 crypto_mode = ACIP_CRYPTO_NONE; // no crypto
228
229 log_debug("WebSocket crypto mode computed: 0x%02x (encrypt=%d, auth=%d)", crypto_mode,
230 ACIP_CRYPTO_HAS_ENCRYPT(crypto_mode), ACIP_CRYPTO_HAS_AUTH(crypto_mode));
231
232 // Set crypto mode before initialization via callback
233 // NOTE: Always initialize crypto context, even in NONE mode!
234 // The server always sends KEY_EXCHANGE_INIT packets, and the context
235 // must be initialized to handle them (even if in disabled/none mode).
236 const crypto_context_t *crypto_ctx = NULL;
237
238 log_debug("Initializing crypto context for WebSocket with mode 0x%02x...", crypto_mode);
239
241
243 log_error("Failed to initialize crypto context");
244 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
245 }
246 log_debug("Crypto context initialized successfully");
247
248 // Get crypto context if ready
251 }
252
253 // Create WebSocket client instance
254 websocket_client_t *ws_client = websocket_client_create("discovery_connection");
255 if (!ws_client) {
256 log_error("Failed to create WebSocket client");
258 return SET_ERRNO(ERROR_NETWORK, "WebSocket client creation failed");
259 }
260
261 // Connect via WebSocket
262 acip_transport_t *transport = websocket_client_connect(ws_client, ws_url, (crypto_context_t *)crypto_ctx);
263 if (!transport) {
264 log_error("Failed to create WebSocket ACIP transport");
265 websocket_client_destroy(&ws_client);
267 return SET_ERRNO(ERROR_NETWORK, "WebSocket connection failed");
268 }
269
270 // CRITICAL: Wait for the WebSocket handshake to actually complete
271 // The transport is created but the TLS/crypto handshake happens in the service thread
272 log_info("WebSocket transport created, waiting for TLS handshake completion...");
273 const int MAX_HANDSHAKE_WAIT_ITERATIONS = 300; // 300 * 50ms = 15 seconds
274 int handshake_wait_count = 0;
275 while (!acip_transport_is_connected(transport) && handshake_wait_count < MAX_HANDSHAKE_WAIT_ITERATIONS) {
276 platform_sleep_us(50000); // Sleep 50ms between checks
277 handshake_wait_count++;
278 if (handshake_wait_count % 20 == 0) {
279 log_info("WebSocket handshake waiting... (%d seconds elapsed)", handshake_wait_count / 20);
280 }
281 }
282
283 if (!acip_transport_is_connected(transport)) {
284 log_error("WebSocket TLS handshake failed or timed out after 15 seconds");
285 websocket_client_destroy(&ws_client);
287 return SET_ERRNO(ERROR_NETWORK, "WebSocket handshake timeout");
288 }
289
290 log_info("WebSocket connection established to %s", ws_url);
292 ctx->connection.transport = transport;
294 ctx->connection.owner = ws_client;
295 ctx->connection.owner_destroy = destroy_websocket_client_owner;
296
297 return ASCIICHAT_OK;
298 }
299
300 // TCP connection path (original logic)
301 log_info("Attempting TCP connection to %s:%u (3s timeout)", endpoint.host, endpoint.port);
302
303 // Transition to attempting state
305 if (result != ASCIICHAT_OK) {
306 return result;
307 }
308
309 // Create TCP client internally so connection ownership stays in this layer.
311 if (!tcp_client) {
312 log_error("Failed to create TCP client");
314 return SET_ERRNO(ERROR_NETWORK, "TCP client creation failed");
315 }
316
317 // Set timeout for this attempt
320
321 // Attempt TCP connection (reconnect_attempt is 0-based, convert for tcp_client_connect)
322 // Use effective_address/port which may be parsed from tcp:// URL scheme
323 int tcp_result = tcp_client_connect(tcp_client, endpoint.host, endpoint.port, (int)ctx->reconnect_attempt,
324 ctx->reconnect_attempt == 0, ctx->reconnect_attempt > 0);
325
326 if (tcp_result != 0) {
327 log_debug("TCP connection failed (tcp_client_connect returned %d)", tcp_result);
330 return SET_ERRNO(ERROR_NETWORK, "TCP connection failed after %u attempts", ctx->reconnect_attempt);
331 }
332
333 // Extract socket from TCP client for crypto handshake
335 if (sockfd == INVALID_SOCKET_VALUE) {
336 log_error("Failed to get socket from TCP client");
338 return SET_ERRNO(ERROR_NETWORK, "Invalid socket after TCP connection");
339 }
340
341 // Note: TCP client stores server IP in tcp_client->server_ip for potential crypto context use
342 if (tcp_client->server_ip[0] != '\0') {
343 log_debug("Server IP available from TCP client: %s", tcp_client->server_ip);
345 } else {
346 log_warn("TCP client did not populate server_ip field");
347 }
348
349 // Compute crypto mode from CLI options
350 // Determine if client has authentication material (keys or password)
351 bool has_auth_material =
352 (GET_OPTION(encrypt_key)[0] != '\0' || GET_OPTION(num_identity_keys) > 0 || GET_OPTION(password)[0] != '\0');
353 bool no_encrypt = GET_OPTION(no_encrypt);
354 bool no_auth = GET_OPTION(no_auth);
355
356 uint8_t crypto_mode;
357 if (!no_encrypt && !no_auth && !has_auth_material)
358 crypto_mode = ACIP_CRYPTO_ENCRYPT; // default: encrypt only
359 else if (!no_encrypt && !no_auth && has_auth_material)
360 crypto_mode = ACIP_CRYPTO_FULL; // full: encrypt + auth
361 else if (no_encrypt && !no_auth && has_auth_material)
362 crypto_mode = ACIP_CRYPTO_AUTH; // auth-only mode
363 else
364 crypto_mode = ACIP_CRYPTO_NONE; // no crypto
365
366 log_debug("TCP crypto mode computed: 0x%02x (encrypt=%d, auth=%d)", crypto_mode, ACIP_CRYPTO_HAS_ENCRYPT(crypto_mode),
367 ACIP_CRYPTO_HAS_AUTH(crypto_mode));
368
369 // Setup crypto operations via callbacks
370 const crypto_context_t *crypto_ctx = NULL;
371 acip_transport_t *transport = NULL;
372
373 if (crypto_mode != ACIP_CRYPTO_NONE) {
375
376 // Initialize crypto context if mode requires handshake (not ACIP_CRYPTO_NONE)
377 // This must happen AFTER setting server IP, as crypto init reads server IP/port
378 log_debug("Initializing crypto context...");
380 log_error("Failed to initialize crypto context");
382 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
383 }
384 log_debug("Crypto context initialized successfully");
385
386 // Create transport before handshake (starts with NULL crypto, set after)
387 transport = acip_tcp_transport_create("connection", sockfd, NULL);
388 if (!transport) {
389 log_error("Failed to create ACIP transport for TCP");
391 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACIP transport");
392 }
393
394 // Perform crypto handshake with server
395 log_debug("Performing crypto handshake with server...");
397 log_error("Crypto handshake failed");
398 acip_transport_destroy(transport);
400 return SET_ERRNO(ERROR_NETWORK, "Crypto handshake failed");
401 }
402 log_debug("Crypto handshake completed successfully");
403
404 // Get crypto context after handshake and set it on the transport
407 transport->crypto_ctx = (crypto_context_t *)crypto_ctx;
408 }
409 } else {
410 // No crypto — create transport without encryption
411 transport = acip_tcp_transport_create("connection", sockfd, NULL);
412 if (!transport) {
413 log_error("Failed to create ACIP transport for TCP");
415 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACIP transport");
416 }
417 }
418
419 log_info("TCP connection established to %s:%u", endpoint.host, endpoint.port);
421 ctx->connection.transport = transport;
424 ctx->connection.owner_destroy = destroy_tcp_client_owner;
425 log_debug("TCP owner stored in connection context for cleanup");
426
427 return ASCIICHAT_OK;
428}
429
437 log_info("=== connection_attempt_websocket CALLED: url='%s' ===", ws_url);
438
439 if (!ctx || !ws_url) {
440 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
441 }
442
443 // Check if shutdown was requested (via callback if available)
445 return SET_ERRNO(ERROR_NETWORK, "Connection attempt aborted due to shutdown request");
446 }
447
448 log_info("Attempting WebSocket connection to %s", ws_url);
449
450 // Transition to attempting state
452 if (result != ASCIICHAT_OK) {
453 return result;
454 }
455
456 // Set timeout for this attempt
459
460 // Initialize crypto context if encryption is enabled
461 const crypto_context_t *crypto_ctx = NULL;
462 if (!GET_OPTION(no_encrypt)) {
463 log_debug("Initializing crypto context for WebSocket...");
464
466 log_error("Failed to initialize crypto context");
467 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
468 }
469 log_debug("Crypto context initialized successfully");
470
471 // Get crypto context if ready
474 }
475 }
476
477 // Create WebSocket client instance
478 websocket_client_t *ws_client = websocket_client_create("connection");
479 if (!ws_client) {
480 log_error("Failed to create WebSocket client");
482 return SET_ERRNO(ERROR_NETWORK, "WebSocket client creation failed");
483 }
484
485 // Connect via WebSocket
486 acip_transport_t *transport = websocket_client_connect(ws_client, ws_url, (crypto_context_t *)crypto_ctx);
487 if (!transport) {
488 log_error("Failed to create WebSocket ACIP transport");
489 websocket_client_destroy(&ws_client);
491 return SET_ERRNO(ERROR_NETWORK, "WebSocket connection failed");
492 }
493
494 log_info("WebSocket connection established to %s", ws_url);
496 ctx->connection.transport = transport;
498 ctx->connection.owner = ws_client;
499 ctx->connection.owner_destroy = destroy_websocket_client_owner;
500 log_debug("WebSocket owner stored in connection context");
501
502 return ASCIICHAT_OK;
503}
Platform abstraction layer umbrella header providing unified cross-platform API.
Application-level callbacks for library code.
#define APP_CALLBACK_PTR(callback_name)
#define APP_CALLBACK_BOOL(callback_name)
#define APP_CALLBACK_VOID_INT(callback_name, value)
#define APP_CALLBACK_INT(callback_name)
#define APP_CALLBACK_INT_TRANSPORT(callback_name, transport)
#define APP_CALLBACK_VOID_UINT8(callback_name, value)
#define APP_CALLBACK_VOID_STR(callback_name, value)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
void tcp_client_t
asciichat_error_t connection_attempt_tcp(connection_attempt_context_t *ctx, const char *server_address, uint16_t server_port)
Attempt direct TCP connection.
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.
Connection state machine and attempt logic for TCP/WebSocket clients.
#define CONN_TIMEOUT_TCP
TCP/WebSocket timeout (3s)
@ CONN_STATE_CONNECTED
Successfully connected.
@ 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 connection.
asciichat_error_t connection_endpoint_resolve(const char *input, uint16_t default_port, connection_endpoint_t *endpoint_out)
const char * connection_endpoint_protocol_name(connection_endpoint_protocol_t protocol)
Shared endpoint normalization for TCP and WebSocket connections.
@ CONNECTION_ENDPOINT_WEBSOCKET
void connection_handle_cleanup(connection_handle_t *handle)
void connection_handle_init(connection_handle_t *handle)
Shared connection ownership handle for TCP and WebSocket clients.
void server_connection_set_ip(const char *ip)
Set the server IP address.
void server_connection_set_port(int port)
Set the server port.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
int client_crypto_handshake(acip_transport_t *transport)
Perform crypto handshake with server.
void client_crypto_set_mode(uint8_t mode)
Set crypto mode for handshake (encryption + authentication)
int client_crypto_init(void)
Initialize client crypto handshake.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
unsigned short uint16_t
Definition common.h:57
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
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_CRYPTO
Definition error_codes.h:96
@ ERROR_INVALID_PARAM
#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_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
#define ACIP_CRYPTO_NONE
No encryption, no authentication (plaintext only)
Definition packet.h:1082
#define ACIP_CRYPTO_HAS_AUTH(mode)
Check if crypto mode includes authentication.
Definition packet.h:1099
#define ACIP_CRYPTO_HAS_ENCRYPT(mode)
Check if crypto mode includes payload encryption.
Definition packet.h:1092
#define ACIP_CRYPTO_ENCRYPT
Encryption only (no identity verification)
Definition packet.h:1083
#define ACIP_CRYPTO_AUTH
Authentication only (no payload encryption)
Definition packet.h:1084
#define ACIP_CRYPTO_FULL
Full crypto: encryption + authentication.
Definition packet.h:1085
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
int socket_t
ACIP client-side protocol API.
WebSocket client abstraction for ascii-chat connections.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
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(const char *name)
Create and initialize named WebSocket client.
void websocket_client_destroy(websocket_client_t **client_ptr)
Destroy WebSocket client and free resources.
📝 Logging API with multiple log levels and terminal output control
bool should_exit(void)
Definition misc.c:131
struct websocket_client_t websocket_client_t
connection_state_t
Transport instance structure.
Definition transport.h:214
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
Context for connection attempts (TCP or WebSocket)
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.)
uint64_t timeout_ns
Timeout for connection attempt.
uint32_t total_transitions
Total state transitions (for metrics)
uint64_t attempt_start_time_ns
When current attempt began.
connection_handle_t connection
Shared transport/client ownership handle.
connection_endpoint_protocol_t protocol
acip_transport_t * transport
void(* owner_destroy)(void *owner)
acip_transport_type_t transport_type
Cryptographic context structure.
TCP client connection state (network-layer only)
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.
@ ACIP_TRANSPORT_TCP
Raw TCP socket.
Definition transport.h:94
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
Definition transport.h:95
🌐 Production-Grade URL Validation and Parsing