ascii-chat 0.6.0
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
26#include "connection_state.h"
27#include "server.h"
28#include "protocol.h"
29#include "webrtc.h"
30#include "crypto.h"
31#include "common.h"
32#include "log/logging.h"
33#include "options/options.h"
34#include "options/rcu.h"
36#include "network/acip/acds.h"
37#include "network/acip/client.h"
38#include "network/tcp/client.h"
41
42#include <time.h>
43#include <string.h>
44#include <memory.h>
45#include <stdio.h>
46
47/* ============================================================================
48 * State Machine Helper Functions
49 * ============================================================================ */
50
55 switch (state) {
56 case CONN_STATE_IDLE:
57 return "IDLE";
59 return "CONNECTED";
61 return "DISCONNECTED";
63 return "FAILED";
64
66 return "ATTEMPTING_DIRECT_TCP";
68 return "DIRECT_TCP_CONNECTED";
70 return "DIRECT_TCP_FAILED";
71
73 return "ATTEMPTING_WEBRTC_STUN";
75 return "WEBRTC_STUN_SIGNALING";
77 return "WEBRTC_STUN_CONNECTED";
79 return "WEBRTC_STUN_FAILED";
80
82 return "ATTEMPTING_WEBRTC_TURN";
84 return "WEBRTC_TURN_SIGNALING";
86 return "WEBRTC_TURN_CONNECTED";
88 return "WEBRTC_TURN_FAILED";
89
90 default:
91 return "UNKNOWN";
92 }
93}
94
100 return 1;
101 }
103 return 2;
104 }
106 return 3;
107 }
108 return 0; // Idle or terminal state
109}
110
111/* ============================================================================
112 * Context Management
113 * ============================================================================ */
114
119 bool webrtc_skip_stun, bool webrtc_disable_turn) {
120 if (!ctx) {
121 return SET_ERRNO(ERROR_INVALID_PARAM, "Context pointer is NULL");
122 }
123
124 // Reset context
125 memset(ctx, 0, sizeof(connection_attempt_context_t));
126
127 // Initialize state
130
131 // Set CLI preferences
132 ctx->prefer_webrtc = prefer_webrtc;
133 ctx->no_webrtc = no_webrtc;
134 ctx->webrtc_skip_stun = webrtc_skip_stun;
135 ctx->webrtc_disable_turn = webrtc_disable_turn;
136
137 // Initialize timeout
138 ctx->stage_start_time = time(NULL);
140
141 // Initialize counters
142 ctx->reconnect_attempt = 1;
143 ctx->stage_failures = 0;
144 ctx->total_transitions = 0;
145
146 log_debug(
147 "Connection context initialized (prefer_webrtc=%d, no_webrtc=%d, webrtc_skip_stun=%d, webrtc_disable_turn=%d)",
148 prefer_webrtc, no_webrtc, webrtc_skip_stun, webrtc_disable_turn);
149
150 return ASCIICHAT_OK;
151}
152
157 if (!ctx)
158 return;
159
160 // Destroy TCP client instance if created
161 if (ctx->tcp_client_instance) {
163 ctx->tcp_client_instance = NULL;
164 log_debug("TCP client instance destroyed");
165 }
166
167 // Close TCP transport if still open
168 if (ctx->tcp_transport) {
169 acip_transport_close(ctx->tcp_transport);
171 ctx->tcp_transport = NULL;
172 }
173
174 // Close WebRTC transport if still open
175 if (ctx->webrtc_transport) {
176 acip_transport_close(ctx->webrtc_transport);
178 ctx->webrtc_transport = NULL;
179 }
180
181 // Cleanup peer manager
182 if (ctx->peer_manager) {
184 ctx->peer_manager = NULL;
185 }
186
187 ctx->active_transport = NULL;
188 log_debug("Connection context cleaned up");
189}
190
195 if (!ctx) {
196 return SET_ERRNO(ERROR_INVALID_PARAM, "Context pointer is NULL");
197 }
198
199 // Store previous state
200 ctx->previous_state = ctx->current_state;
201 ctx->current_state = new_state;
202 ctx->total_transitions++;
203
204 // Update timeout based on new stage
205 uint32_t new_stage = connection_get_stage(new_state);
207
208 if (new_stage != old_stage && new_stage > 0) {
209 ctx->stage_start_time = time(NULL);
210 switch (new_stage) {
211 case 1:
213 break;
214 case 2:
216 break;
217 case 3:
219 break;
220 default:
221 break;
222 }
223 }
224
225 log_debug("State transition: %s → %s (stage %u → %u)", connection_state_name(ctx->previous_state),
226 connection_state_name(new_state), old_stage, new_stage);
227
228 return ASCIICHAT_OK;
229}
230
235 if (!ctx)
236 return false;
237
238 time_t elapsed = time(NULL) - ctx->stage_start_time;
239 bool timeout_exceeded = elapsed > (time_t)ctx->current_stage_timeout_seconds;
240
241 if (timeout_exceeded) {
242 log_warn("Stage timeout exceeded: stage %u, elapsed %ld seconds > %u seconds limit",
244 }
245
246 return timeout_exceeded;
247}
248
249/* ============================================================================
250 * Stage 1: Direct TCP Connection
251 * ============================================================================ */
252
259static asciichat_error_t attempt_direct_tcp(connection_attempt_context_t *ctx, const char *server_address,
260 uint16_t server_port) {
261 if (!ctx || !server_address) {
262 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
263 }
264
265 log_info("Stage 1/3: Attempting direct TCP connection to %s:%u (3s timeout)", server_address, server_port);
266
267 // Transition to attempting state
269 if (result != ASCIICHAT_OK) {
270 return result;
271 }
272
273 // Create TCP client
275 if (!tcp_client) {
276 log_error("Failed to create TCP client");
278 ctx->stage_failures++;
279 return SET_ERRNO(ERROR_NETWORK, "TCP client creation failed");
280 }
281
282 // Set stage timeout for this attempt
283 ctx->stage_start_time = time(NULL);
285
286 // Attempt TCP connection (reconnect_attempt is 0-based, convert for tcp_client_connect)
287 int tcp_result = tcp_client_connect(tcp_client, server_address, server_port, (int)ctx->reconnect_attempt,
288 ctx->reconnect_attempt == 0, ctx->reconnect_attempt > 0);
289
290 if (tcp_result != 0) {
291 log_debug("Direct TCP connection failed (tcp_client_connect returned %d)", tcp_result);
294 ctx->stage_failures++;
295 return SET_ERRNO(ERROR_NETWORK, "TCP connection failed after %u attempts", ctx->reconnect_attempt);
296 }
297
298 // Extract socket from TCP client for crypto handshake
300 if (sockfd == INVALID_SOCKET_VALUE) {
301 log_error("Failed to get socket from TCP client");
303 return SET_ERRNO(ERROR_NETWORK, "Invalid socket after TCP connection");
304 }
305
306 // Extract and set server IP for crypto context initialization
307 // TCP client already resolved and connected to the server IP, stored in tcp_client->server_ip
308 if (tcp_client->server_ip[0] != '\0') {
310 log_debug("Server IP extracted from TCP client: %s", tcp_client->server_ip);
311 } else {
312 log_warn("TCP client did not populate server_ip field");
313 }
314
315 // Initialize crypto context if encryption is enabled
316 // This must happen AFTER setting server IP, as crypto init reads server IP/port
317 if (!GET_OPTION(no_encrypt)) {
318 log_debug("Initializing crypto context...");
319 if (client_crypto_init() != 0) {
320 log_error("Failed to initialize crypto context");
322 return SET_ERRNO(ERROR_CRYPTO, "Crypto initialization failed");
323 }
324 log_debug("Crypto context initialized successfully");
325
326 // Perform crypto handshake with server
327 log_debug("Performing crypto handshake with server...");
328 if (client_crypto_handshake(sockfd) != 0) {
329 log_error("Crypto handshake failed");
331 return SET_ERRNO(ERROR_NETWORK, "Crypto handshake failed");
332 }
333 log_debug("Crypto handshake completed successfully");
334 }
335
336 // Get crypto context after handshake
338
339 // Create ACIP transport for protocol-agnostic packet sending/receiving
340 ctx->tcp_transport = acip_tcp_transport_create(sockfd, (crypto_context_t *)crypto_ctx);
341 if (!ctx->tcp_transport) {
342 log_error("Failed to create ACIP transport for Direct TCP");
344 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACIP transport");
345 }
346
347 log_info("Direct TCP connection established to %s:%u", server_address, server_port);
350
351 // Store tcp_client in context for proper lifecycle management
352 // It will be destroyed in connection_context_cleanup()
354 log_debug("TCP client instance stored in connection context");
355
356 return ASCIICHAT_OK;
357}
358
359/* ============================================================================
360 * WebRTC Transport Ready Callback
361 * ============================================================================ */
362
374static void on_webrtc_transport_ready(acip_transport_t *transport, const uint8_t participant_id[16], void *user_data) {
376
377 if (!ctx || !transport) {
378 log_error("on_webrtc_transport_ready: Invalid parameters");
379 return;
380 }
381
382 log_info("WebRTC transport ready (participant_id=%.*s)", 16, (const char *)participant_id);
383
384 // Store transport and signal waiting thread
386 ctx->webrtc_transport = transport;
387 ctx->webrtc_transport_received = true;
390}
391
392/* ============================================================================
393 * Stage 2: WebRTC + STUN Connection
394 * ============================================================================ */
395
405static asciichat_error_t attempt_webrtc_stun(connection_attempt_context_t *ctx, const char *server_address,
406 uint16_t server_port, const char *acds_server, uint16_t acds_port) {
407 if (!ctx || !server_address || !acds_server) {
408 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
409 }
410
411 // Skip if STUN explicitly disabled or we're disabling WebRTC only
412 if (ctx->webrtc_skip_stun || ctx->no_webrtc) {
413 log_debug("Skipping WebRTC+STUN (webrtc_skip_stun=%d, no_webrtc=%d)", ctx->webrtc_skip_stun, ctx->no_webrtc);
414 return SET_ERRNO(ERROR_NETWORK, "STUN stage disabled per CLI flags");
415 }
416
417 log_info("Stage 2/3: Attempting WebRTC + STUN connection via %s:%u (8s timeout)", acds_server, acds_port);
418
419 // Transition to attempting state
421 if (result != ASCIICHAT_OK) {
422 return result;
423 }
424
425 // Set stage timeout
426 ctx->stage_start_time = time(NULL);
428
429 // ─────────────────────────────────────────────────────────────
430 // Step 1: Connect to ACDS server
431 // ─────────────────────────────────────────────────────────────
432
433 acds_client_t acds_client = {0};
434 acds_client_config_t acds_config = {0};
435 SAFE_STRNCPY(acds_config.server_address, acds_server, sizeof(acds_config.server_address));
436 acds_config.server_port = acds_port;
437 acds_config.timeout_ms = 5000; // 5s timeout for ACDS connection
438
439 result = acds_client_connect(&acds_client, &acds_config);
440 if (result != ASCIICHAT_OK) {
441 log_warn("Failed to connect to ACDS server %s:%u: %d", acds_server, acds_port, result);
442 return SET_ERRNO(ERROR_NETWORK, "ACDS connection failed");
443 }
444
445 // ─────────────────────────────────────────────────────────────
446 // Step 2: Join ACDS session (use session context from discovery)
447 // ─────────────────────────────────────────────────────────────
448
449 // Check if we have session information from prior ACDS discovery
450 if (ctx->session_ctx.session_string[0] == '\0') {
451 log_warn("No session context available - ACDS discovery may have failed or not been performed");
452 acds_client_disconnect(&acds_client);
453 return SET_ERRNO(ERROR_NETWORK, "Session context not available (discovery required before WebRTC)");
454 }
455
456 acds_session_join_params_t join_params = {0};
457 join_params.session_string = ctx->session_ctx.session_string;
458
459 // Add password if configured (required for password-protected sessions)
460 const options_t *opts = options_get();
461 if (opts && opts->password[0] != '\0') {
462 join_params.has_password = true;
463 SAFE_STRNCPY(join_params.password, opts->password, sizeof(join_params.password));
464 }
465
466 acds_session_join_result_t join_result = {0};
467 result = acds_session_join(&acds_client, &join_params, &join_result);
468 if (result != ASCIICHAT_OK || !join_result.success) {
469 log_warn("Failed to join ACDS session: %d (%s)", join_result.error_code,
470 join_result.error_message[0] ? join_result.error_message : "unknown error");
471 acds_client_disconnect(&acds_client);
472 return SET_ERRNO(ERROR_NETWORK, "ACDS session join failed");
473 }
474
475 // Store session context for WebRTC signaling
476 memcpy(ctx->session_ctx.session_id, join_result.session_id, sizeof(join_result.session_id));
477 memcpy(ctx->session_ctx.participant_id, join_result.participant_id, sizeof(join_result.participant_id));
478 ctx->session_ctx.server_port = join_result.server_port;
480
481 log_debug("Joined ACDS session: session_id=%.*s, participant_id=%.*s", 16, (char *)join_result.session_id, 16,
482 (char *)join_result.participant_id);
483
484 // ─────────────────────────────────────────────────────────────
485 // Step 3: Create WebRTC peer manager with STUN servers
486 // ─────────────────────────────────────────────────────────────
487
488 // Configure STUN servers (ascii-chat primary, Google fallback)
489 stun_server_t stun_server1 = {.host_len = 28};
490 SAFE_STRNCPY(stun_server1.host, "stun:stun.ascii-chat.com:3478", sizeof(stun_server1.host));
491
492 stun_server_t stun_server2 = {.host_len = 23};
493 SAFE_STRNCPY(stun_server2.host, "stun:stun.l.google.com:19302", sizeof(stun_server2.host));
494
495 stun_server_t stun_servers[] = {stun_server1, stun_server2};
496
497 // Initialize synchronization primitives for transport_ready callback
498 ctx->webrtc_transport_received = false;
499
500 webrtc_peer_manager_config_t pm_config = {
501 .role = WEBRTC_ROLE_JOINER, // Client joins, server creates
502 .stun_servers = stun_servers,
503 .stun_count = 2,
504 .turn_servers = NULL,
505 .turn_count = 0,
506 .on_transport_ready = on_webrtc_transport_ready, // Callback when DataChannel ready
507 .user_data = (void *)ctx,
508 .crypto_ctx = NULL, // TODO: Get crypto context from client
509 };
510
511 // Get signaling callbacks (returns struct, not pointer)
513
514 // Create ACIP transport wrapper for ACDS signaling
515 // This transport is used to send SDP/ICE messages via ACDS relay
516 ctx->acds_transport = acip_tcp_transport_create(acds_client.socket, NULL);
517 if (!ctx->acds_transport) {
518 log_error("Failed to create ACDS transport wrapper");
519 acds_client_disconnect(&acds_client);
520 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACDS transport");
521 }
522
523 // Set ACDS transport for signaling (SDP/ICE will be sent via this)
525
526 // Set session context (session_id, participant_id) for signaling
528
529 result = webrtc_peer_manager_create(&pm_config, &signaling_callbacks, &ctx->peer_manager);
530 if (result != ASCIICHAT_OK) {
531 log_warn("Failed to create WebRTC peer manager: %d", result);
532 acds_client_disconnect(&acds_client);
533 return SET_ERRNO(ERROR_NETWORK, "WebRTC peer manager creation failed");
534 }
535
536 // ─────────────────────────────────────────────────────────────
537 // Step 4: Initiate WebRTC connection (send SDP offer)
538 // ─────────────────────────────────────────────────────────────
539
540 // Initiate peer connection as JOINER - this creates peer, data channel, and sends SDP offer
542 if (result != ASCIICHAT_OK) {
543 log_warn("Failed to initiate WebRTC connection: %d", result);
544 acds_client_disconnect(&acds_client);
545 return SET_ERRNO(ERROR_NETWORK, "WebRTC connection initiation failed");
546 }
547
548 // ─────────────────────────────────────────────────────────────
549 // Step 5-7: Exchange SDP/ICE and wait for connection
550 // ─────────────────────────────────────────────────────────────
551
553
554 // Get client callbacks for receiving SDP/ICE responses from server
556 if (!callbacks) {
557 log_error("Failed to get ACIP client callbacks for WebRTC signaling");
558 acds_client_disconnect(&acds_client);
559 return SET_ERRNO(ERROR_INVALID_STATE, "Missing ACIP callbacks");
560 }
561
562 // Receive-and-wait loop: Process incoming ACDS signaling packets while waiting for DataChannel
563 // The peer_manager automatically sends SDP offer via send_sdp callback after creation.
564 // We need to receive the SDP answer and ICE candidates from the server via ACDS.
565 time_t wait_start = time(NULL);
566 time_t timeout_seconds = CONN_TIMEOUT_WEBRTC_STUN;
567 bool connection_successful = false;
568
569 while ((time(NULL) - wait_start) < timeout_seconds) {
570 // Check if transport is ready (set by on_transport_ready callback)
572 if (ctx->webrtc_transport_received) {
573 connection_successful = true;
575 break;
576 }
578
579 // Receive one ACDS packet (SDP answer, ICE candidate, etc.)
580 // This dispatches to on_webrtc_sdp/on_webrtc_ice in client/protocol.c
581 // which forward to the peer_manager for processing
583
584 if (recv_result != ASCIICHAT_OK) {
585 if (recv_result == ERROR_NETWORK) {
586 log_warn("ACDS connection closed during WebRTC signaling");
587 break;
588 } else if (recv_result == ERROR_CRYPTO) {
589 log_error("ACDS crypto error during WebRTC signaling");
590 break;
591 }
592 // Other errors are non-fatal, continue waiting
593 log_debug("ACDS receive error (non-fatal): %s", asciichat_error_string(recv_result));
594 }
595 }
596
597 if (!connection_successful) {
598 log_warn("WebRTC+STUN connection timed out after %ld seconds", timeout_seconds);
600 ctx->stage_failures++;
601 acds_client_disconnect(&acds_client);
602 return SET_ERRNO(ERROR_NETWORK_TIMEOUT, "WebRTC+STUN connection timeout");
603 }
604
605 log_info("WebRTC+STUN connection established");
608
609 // Clean up ACDS client (signaling relay is separate from data transport)
610 acds_client_disconnect(&acds_client);
611
612 return ASCIICHAT_OK;
613}
614
615/* ============================================================================
616 * Stage 3: WebRTC + TURN Connection
617 * ============================================================================ */
618
630static asciichat_error_t attempt_webrtc_turn(connection_attempt_context_t *ctx, const char *server_address,
631 uint16_t server_port, const char *acds_server, uint16_t acds_port) {
632 if (!ctx || !server_address || !acds_server) {
633 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
634 }
635
636 // Skip if TURN explicitly disabled
637 if (ctx->webrtc_disable_turn) {
638 log_debug("Skipping WebRTC+TURN (webrtc_disable_turn=true)");
639 return SET_ERRNO(ERROR_NETWORK, "TURN stage disabled per CLI flags");
640 }
641
642 log_info("Stage 3/3: Attempting WebRTC + TURN connection via %s:%u (15s timeout)", acds_server, acds_port);
643
644 // Transition to attempting state
646 if (result != ASCIICHAT_OK) {
647 return result;
648 }
649
650 // Set stage timeout
651 ctx->stage_start_time = time(NULL);
653
654 // ─────────────────────────────────────────────────────────────
655 // Step 1: Connect to ACDS server
656 // ─────────────────────────────────────────────────────────────
657
658 acds_client_t acds_client = {0};
659 acds_client_config_t acds_config = {0};
660 SAFE_STRNCPY(acds_config.server_address, acds_server, sizeof(acds_config.server_address));
661 acds_config.server_port = acds_port;
662 acds_config.timeout_ms = 5000; // 5s timeout for ACDS connection
663
664 result = acds_client_connect(&acds_client, &acds_config);
665 if (result != ASCIICHAT_OK) {
666 log_warn("Failed to connect to ACDS server %s:%u for TURN: %d", acds_server, acds_port, result);
667 return SET_ERRNO(ERROR_NETWORK, "ACDS connection failed for TURN");
668 }
669
670 // ─────────────────────────────────────────────────────────────
671 // Step 2: Join ACDS session to get TURN credentials
672 // ─────────────────────────────────────────────────────────────
673
674 // Check if we have session information from prior ACDS discovery
675 if (ctx->session_ctx.session_string[0] == '\0') {
676 log_warn("No session context available for TURN stage (ACDS discovery required before WebRTC)");
677 acds_client_disconnect(&acds_client);
678 return SET_ERRNO(ERROR_NETWORK, "Session context not available");
679 }
680
681 // Re-join same session to get TURN credentials from server
682 // In production, ACDS would return TURN credentials on first join;
683 // we're re-joining here to ensure we have them
684 acds_session_join_params_t join_params = {0};
685 join_params.session_string = ctx->session_ctx.session_string;
686
687 // Add password if configured (required for password-protected sessions)
688 const options_t *opts_turn = options_get();
689 if (opts_turn && opts_turn->password[0] != '\0') {
690 join_params.has_password = true;
691 SAFE_STRNCPY(join_params.password, opts_turn->password, sizeof(join_params.password));
692 }
693
694 acds_session_join_result_t join_result = {0};
695 result = acds_session_join(&acds_client, &join_params, &join_result);
696 if (result != ASCIICHAT_OK || !join_result.success) {
697 log_warn("Failed to re-join ACDS session for TURN: %d (%s)", join_result.error_code,
698 join_result.error_message[0] ? join_result.error_message : "unknown error");
699 acds_client_disconnect(&acds_client);
700 return SET_ERRNO(ERROR_NETWORK, "ACDS session re-join failed for TURN");
701 }
702
703 // Store TURN server credentials from ACDS response
704 // Note: ACDS response should include TURN server, username, and password
705 // For now we use ascii-chat's TURN server - in production this comes from server
706 ctx->stun_turn_cfg.turn_port = 3478; // Standard TURN port
707 SAFE_STRNCPY(ctx->stun_turn_cfg.turn_server, "turn.ascii-chat.com", sizeof(ctx->stun_turn_cfg.turn_server));
709 SAFE_STRNCPY(ctx->stun_turn_cfg.turn_password, "ephemeral-credential", sizeof(ctx->stun_turn_cfg.turn_password));
710
711 log_debug("Retrieved TURN credentials: server=%s:%u, username=%s", ctx->stun_turn_cfg.turn_server,
713
714 // ─────────────────────────────────────────────────────────────
715 // Step 3: Create WebRTC peer manager with TURN relay
716 // ─────────────────────────────────────────────────────────────
717
718 // Configure STUN + TURN servers (ascii-chat primary, Google fallback)
719 stun_server_t stun_server1_turn = {.host_len = 28};
720 SAFE_STRNCPY(stun_server1_turn.host, "stun:stun.ascii-chat.com:3478", sizeof(stun_server1_turn.host));
721
722 stun_server_t stun_server2_turn = {.host_len = 23};
723 SAFE_STRNCPY(stun_server2_turn.host, "stun:stun.l.google.com:19302", sizeof(stun_server2_turn.host));
724
725 stun_server_t stun_servers[] = {stun_server1_turn, stun_server2_turn};
726
727 // Build TURN URL from configuration
728 char turn_url[128] = {0};
729 snprintf(turn_url, sizeof(turn_url), "turn:%s:%u", ctx->stun_turn_cfg.turn_server, ctx->stun_turn_cfg.turn_port);
730
731 turn_server_t turn_server = {0};
732 turn_server.url_len = strlen(turn_url);
733 SAFE_STRNCPY(turn_server.url, turn_url, sizeof(turn_server.url));
734 turn_server.username_len = strlen(ctx->stun_turn_cfg.turn_username);
735 SAFE_STRNCPY(turn_server.username, ctx->stun_turn_cfg.turn_username, sizeof(turn_server.username));
736 turn_server.credential_len = strlen(ctx->stun_turn_cfg.turn_password);
737 SAFE_STRNCPY(turn_server.credential, ctx->stun_turn_cfg.turn_password, sizeof(turn_server.credential));
738
739 turn_server_t turn_servers[] = {turn_server};
740
741 // Initialize synchronization primitives for transport_ready callback
742 ctx->webrtc_transport_received = false;
743
744 webrtc_peer_manager_config_t pm_config = {
745 .role = WEBRTC_ROLE_JOINER, // Client joins, server creates
746 .stun_servers = stun_servers,
747 .stun_count = 2,
748 .turn_servers = turn_servers,
749 .turn_count = 1,
750 .on_transport_ready = on_webrtc_transport_ready, // Callback when DataChannel ready
751 .user_data = (void *)ctx,
752 .crypto_ctx = NULL, // TODO: Get crypto context from client
753 };
754
755 // Get signaling callbacks (returns struct, not pointer)
757
758 // Create ACIP transport wrapper for ACDS signaling
759 // This transport is used to send SDP/ICE messages via ACDS relay
760 ctx->acds_transport = acip_tcp_transport_create(acds_client.socket, NULL);
761 if (!ctx->acds_transport) {
762 log_error("Failed to create ACDS transport wrapper for TURN");
763 acds_client_disconnect(&acds_client);
764 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACDS transport");
765 }
766
767 // Set ACDS transport for signaling (SDP/ICE will be sent via this)
769
770 // Set session context (session_id, participant_id) for signaling
772
773 result = webrtc_peer_manager_create(&pm_config, &signaling_callbacks_turn, &ctx->peer_manager);
774 if (result != ASCIICHAT_OK) {
775 log_warn("Failed to create WebRTC peer manager for TURN: %d", result);
776 acds_client_disconnect(&acds_client);
777 return SET_ERRNO(ERROR_NETWORK, "WebRTC peer manager creation failed");
778 }
779
780 // Initiate WebRTC connection with TURN
782 if (result != ASCIICHAT_OK) {
783 log_warn("Failed to initiate WebRTC+TURN connection: %d", result);
784 acds_client_disconnect(&acds_client);
785 return SET_ERRNO(ERROR_NETWORK, "WebRTC+TURN connection initiation failed");
786 }
787
788 // ─────────────────────────────────────────────────────────────
789 // Step 4-7: Exchange SDP/ICE and wait for connection
790 // ─────────────────────────────────────────────────────────────
791
793
794 // Get client callbacks for receiving SDP/ICE responses from server
796 if (!callbacks) {
797 log_error("Failed to get ACIP client callbacks for TURN signaling");
798 acds_client_disconnect(&acds_client);
799 return SET_ERRNO(ERROR_INVALID_STATE, "Missing ACIP callbacks");
800 }
801
802 // Receive-and-wait loop: Process incoming ACDS signaling packets while waiting for DataChannel
803 // (Must actively receive SDP answer + ICE candidates from server or connection will never establish)
804 time_t wait_start = time(NULL);
805 time_t timeout_seconds = CONN_TIMEOUT_WEBRTC_TURN;
806 bool connection_successful = false;
807
808 while ((time(NULL) - wait_start) < timeout_seconds) {
809 // Check if transport is ready (set by on_transport_ready callback)
811 if (ctx->webrtc_transport_received) {
812 connection_successful = true;
814 break;
815 }
817
818 // Receive one ACDS packet (SDP answer, ICE candidate, etc.)
819 // This dispatches to on_webrtc_sdp/on_webrtc_ice in client/protocol.c
821
822 if (recv_result != ASCIICHAT_OK) {
823 if (recv_result == ERROR_NETWORK) {
824 log_warn("ACDS connection closed during WebRTC TURN signaling");
825 break;
826 } else if (recv_result == ERROR_CRYPTO) {
827 log_error("ACDS crypto error during WebRTC TURN signaling");
828 break;
829 }
830 // Non-fatal errors (e.g., timeout on receive): log and continue
831 log_debug("ACDS receive error (non-fatal): %s", asciichat_error_string(recv_result));
832 }
833
834 // Yield CPU to avoid busy-wait (10ms sleep)
836 }
837
838 if (!connection_successful) {
839 log_warn("WebRTC+TURN connection timed out after %ld seconds", timeout_seconds);
841 ctx->stage_failures++;
842 acds_client_disconnect(&acds_client);
843 return SET_ERRNO(ERROR_NETWORK_TIMEOUT, "WebRTC+TURN connection timeout");
844 }
845
846 log_info("WebRTC+TURN connection established");
849
850 // Clean up ACDS client (signaling relay is separate from data transport)
851 acds_client_disconnect(&acds_client);
852
853 return ASCIICHAT_OK;
854}
855
856/* ============================================================================
857 * Main Orchestrator
858 * ============================================================================ */
859
883 uint16_t server_port, const char *acds_server, uint16_t acds_port) {
884 if (!ctx || !server_address || !acds_server) {
885 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
886 }
887
888 log_info("=== Connection attempt %u: %s:%u (fallback strategy: TCP → STUN → TURN) ===", ctx->reconnect_attempt,
889 server_address, server_port);
890
892
893 // ─────────────────────────────────────────────────────────────
894 // Stage 1: Direct TCP (3s timeout)
895 // ─────────────────────────────────────────────────────────────
896
897 if (!ctx->prefer_webrtc && !ctx->no_webrtc) {
898 // Normal path: try TCP first unless WebRTC preferred
899 result = attempt_direct_tcp(ctx, server_address, server_port);
900 if (result == ASCIICHAT_OK) {
901 log_info("Connection succeeded via Direct TCP");
903 return ASCIICHAT_OK;
904 }
905
906 // Check if timeout (fall back to next stage)
907 if (connection_check_timeout(ctx)) {
908 log_info("Stage 1 timeout, proceeding to Stage 2 (WebRTC+STUN)");
909 } else {
910 // Actual failure (not just timeout) - could be local error
911 log_warn("Stage 1 failed immediately, proceeding to Stage 2");
912 }
913 } else if (ctx->no_webrtc) {
914 // TCP-only mode - don't try WebRTC at all
915 result = attempt_direct_tcp(ctx, server_address, server_port);
916 if (result == ASCIICHAT_OK) {
917 log_info("Connection succeeded via Direct TCP (--no-webrtc)");
919 return ASCIICHAT_OK;
920 }
921 log_error("Direct TCP failed with --no-webrtc flag");
923 return result;
924 }
925
926 // ─────────────────────────────────────────────────────────────
927 // Stage 2: WebRTC + STUN (8s timeout)
928 // ─────────────────────────────────────────────────────────────
929
930 result = attempt_webrtc_stun(ctx, server_address, server_port, acds_server, acds_port);
931 if (result == ASCIICHAT_OK) {
932 log_info("Connection succeeded via WebRTC+STUN");
934 return ASCIICHAT_OK;
935 }
936
937 if (result == ERROR_NETWORK) {
938 log_debug("WebRTC+STUN stage skipped per CLI flags");
939 } else if (connection_check_timeout(ctx)) {
940 log_info("Stage 2 timeout, proceeding to Stage 3 (WebRTC+TURN)");
941 } else {
942 log_warn("Stage 2 failed immediately, proceeding to Stage 3");
943 }
944
945 // ─────────────────────────────────────────────────────────────
946 // Stage 3: WebRTC + TURN (15s timeout)
947 // ─────────────────────────────────────────────────────────────
948
949 result = attempt_webrtc_turn(ctx, server_address, server_port, acds_server, acds_port);
950 if (result == ASCIICHAT_OK) {
951 log_info("Connection succeeded via WebRTC+TURN");
953 return ASCIICHAT_OK;
954 }
955
956 if (result == ERROR_NETWORK) {
957 log_debug("WebRTC+TURN stage skipped per CLI flags");
958 } else if (connection_check_timeout(ctx)) {
959 log_error("Stage 3 timeout - all fallback stages exhausted");
960 } else {
961 log_error("Stage 3 failed - all fallback stages exhausted");
962 }
963
964 // ─────────────────────────────────────────────────────────────
965 // All stages failed
966 // ─────────────────────────────────────────────────────────────
967
969 return SET_ERRNO(ERROR_NETWORK, "All fallback stages exhausted (TCP: failed, STUN: %s, TURN: %s)",
970 ctx->webrtc_skip_stun ? "skipped" : "failed", ctx->webrtc_disable_turn ? "skipped" : "failed");
971}
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
Join an existing session.
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Connect to ACDS server.
Definition acds_client.c:53
void acds_client_disconnect(acds_client_t *client)
Disconnect from ACDS server.
asciichat_error_t connection_context_init(connection_attempt_context_t *ctx, bool prefer_webrtc, bool no_webrtc, bool webrtc_skip_stun, bool webrtc_disable_turn)
Initialize connection attempt context.
asciichat_error_t connection_attempt_with_fallback(connection_attempt_context_t *ctx, const char *server_address, uint16_t server_port, const char *acds_server, uint16_t acds_port)
Orchestrate connection attempt with automatic fallback.
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 current stage 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.
uint32_t connection_get_stage(connection_state_t state)
Get current stage number (1, 2, or 3) from state.
const char * connection_state_name(connection_state_t state)
Get human-readable state name for logging.
🎯 Connection state machine for Phase 3 WebRTC fallback integration
#define CONN_TIMEOUT_WEBRTC_TURN
Stage 3: WebRTC+TURN timeout.
#define CONN_TIMEOUT_DIRECT_TCP
Stage 1: Direct TCP timeout.
connection_state_t
13-state connection state machine
@ CONN_STATE_WEBRTC_STUN_SIGNALING
Exchanging SDP/ICE candidates via ACDS.
@ CONN_STATE_CONNECTED
Successfully connected (any transport)
@ CONN_STATE_WEBRTC_TURN_SIGNALING
Exchanging SDP/ICE candidates with TURN relay.
@ CONN_STATE_WEBRTC_TURN_CONNECTED
WebRTC + TURN connection established.
@ CONN_STATE_WEBRTC_STUN_CONNECTED
WebRTC + STUN connection established.
@ CONN_STATE_ATTEMPTING_WEBRTC_TURN
Initiating WebRTC + TURN connection.
@ CONN_STATE_IDLE
Not connected, no attempt in progress.
@ CONN_STATE_DIRECT_TCP_FAILED
Direct TCP failed, falling back to STUN.
@ CONN_STATE_DISCONNECTED
Clean disconnect (user initiated)
@ CONN_STATE_DIRECT_TCP_CONNECTED
Direct TCP connection established.
@ CONN_STATE_FAILED
All fallback stages exhausted.
@ CONN_STATE_ATTEMPTING_WEBRTC_STUN
Initiating WebRTC + STUN connection.
@ CONN_STATE_WEBRTC_STUN_FAILED
STUN failed, falling back to TURN.
@ CONN_STATE_ATTEMPTING_DIRECT_TCP
Attempting direct TCP connection.
@ CONN_STATE_WEBRTC_TURN_FAILED
All stages exhausted.
#define CONN_TIMEOUT_WEBRTC_STUN
Stage 2: WebRTC+STUN timeout.
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.
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.
const acip_client_callbacks_t * protocol_get_acip_callbacks()
Get ACIP client callbacks for packet dispatch.
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:69
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:72
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
int cond_signal(cond_t *cond)
Signal a condition variable (wake one waiting thread)
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
stun_server_t
Definition stun.h:75
turn_server_t
Definition turn.h:101
asciichat_error_t webrtc_peer_manager_create(const webrtc_peer_manager_config_t *config, const webrtc_signaling_callbacks_t *signaling_callbacks, webrtc_peer_manager_t **manager_out)
Create a WebRTC peer manager.
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
Destroy peer manager and close all connections.
asciichat_error_t webrtc_peer_manager_connect(webrtc_peer_manager_t *manager, const uint8_t session_id[16], const uint8_t participant_id[16])
Initiate connection to remote peer (joiner role only)
@ WEBRTC_ROLE_JOINER
Session joiner - generates offers, receives answers.
asciichat_error_t acip_client_receive_and_dispatch(acip_transport_t *transport, const acip_client_callbacks_t *callbacks)
Receive packet from server and dispatch to callbacks.
ACIP client-side protocol library.
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.
📝 Logging API with multiple log levels and terminal output control
ASCII-Chat Discovery Service (ACDS) Protocol Message Formats.
⚙️ Command-line options parsing and configuration management for ascii-chat
ascii-chat Client Server Connection Management Interface
void webrtc_set_session_context(const uint8_t session_id[16], const uint8_t participant_id[16])
Set session and participant IDs for signaling.
void webrtc_set_acds_transport(acip_transport_t *transport)
Set the ACDS transport for signaling callbacks.
webrtc_signaling_callbacks_t webrtc_get_signaling_callbacks(void)
Get signaling callbacks for WebRTC peer manager.
uint8_t participant_id[16]
Client-side WebRTC signaling callback implementations.
Server cryptographic operations and per-client handshake management.
Server packet processing and protocol implementation.
ACDS client connection configuration.
Definition acds_client.h:44
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
Definition acds_client.h:45
uint32_t timeout_ms
Connection timeout in milliseconds.
Definition acds_client.h:47
uint16_t server_port
ACDS server port (default: 27225)
Definition acds_client.h:46
ACDS client connection handle.
Definition acds_client.h:53
socket_t socket
TCP socket to ACDS server.
Definition acds_client.h:55
Session join parameters.
bool has_password
Password provided.
char password[128]
Password (if has_password)
const char * session_string
Session to join.
Session join result.
char error_message[129]
Error message (if !success, null-terminated)
uint8_t error_code
Error code (if !success)
uint8_t participant_id[16]
Participant UUID (if success)
bool success
Join succeeded.
char server_address[65]
Server IP/hostname (if success, null-terminated)
uint16_t server_port
Server port (if success)
uint8_t session_id[16]
Session UUID (if success)
Client-side packet handler callbacks.
Definition handlers.h:51
Transport instance structure.
Definition transport.h:169
Master context for connection attempt with fallback.
acip_transport_t * acds_transport
ACDS signaling transport (Stages 2/3) - may be NULL.
time_t stage_start_time
When current stage began.
connection_state_t current_state
Current connection state.
webrtc_peer_manager_t * peer_manager
WebRTC peer manager (Stages 2/3)
uint32_t current_stage_timeout_seconds
Timeout for current stage (3/8/15)
connection_state_t previous_state
Previous state (for debugging)
uint32_t stage_failures
How many stages have failed.
uint32_t reconnect_attempt
Reconnection attempt number (1st, 2nd, etc.)
bool prefer_webrtc
–prefer-webrtc flag
struct tcp_client * tcp_client_instance
TCP client instance (Direct TCP only) - owned by context.
mutex_t webrtc_mutex
Mutex for WebRTC callback synchronization.
acip_transport_t * active_transport
Currently active transport (whichever succeeded)
bool no_webrtc
–no-webrtc flag (disable WebRTC, TCP only)
bool webrtc_skip_stun
–webrtc-skip-stun flag (skip Stage 2 STUN)
bool webrtc_disable_turn
–webrtc-disable-turn flag (skip Stage 3 TURN)
connection_stun_turn_config_t stun_turn_cfg
STUN/TURN server config.
acip_transport_t * webrtc_transport
WebRTC transport (Stages 2/3) - may be NULL.
acip_transport_t * tcp_transport
TCP transport (Stage 1) - may be NULL.
cond_t webrtc_ready_cond
Condition variable for on_transport_ready.
connection_session_context_t session_ctx
Session context from ACDS.
uint32_t total_transitions
Total state transitions (for metrics)
bool webrtc_transport_received
Flag: transport_ready callback fired.
char session_string[128]
Session string (e.g., "mystic-stone-obelisk")
uint16_t server_port
Server port for connection.
uint8_t session_id[16]
Session UUID (binary)
char server_address[64]
Server IP/hostname for connection.
uint8_t participant_id[16]
Client's participant UUID (binary)
char turn_server[256]
TURN relay server address.
uint16_t turn_port
TURN relay port.
char turn_password[256]
TURN password (from ACDS or defaults)
char turn_username[128]
TURN username (from ACDS or defaults)
Cryptographic context structure.
Consolidated options structure.
Definition options.h:439
char password[256]
Password string.
Definition options.h:543
TCP client connection and state management.
Peer manager configuration.
webrtc_peer_role_t role
Session role (creator or joiner)
Signaling callbacks for sending SDP/ICE.
⏱️ High-precision timing utilities using sokol_time.h and uthash
acip_transport_t * acip_tcp_transport_create(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.