9#include <ascii-chat/common.h>
10#include <ascii-chat/options/options.h>
11#include <ascii-chat/log/logging.h>
12#include <ascii-chat/buffer_pool.h>
13#include <ascii-chat/network/acip/acds.h>
14#include <ascii-chat/network/acip/acds_client.h>
15#include <ascii-chat/network/acip/send.h>
16#include <ascii-chat/network/packet.h>
17#include <ascii-chat/network/webrtc/stun.h>
18#include <ascii-chat/network/webrtc/peer_manager.h>
19#include <ascii-chat/discovery/identity.h>
20#include "ascii-chat/common/error_codes.h"
23#include <ascii-chat/platform/abstraction.h>
24#include <ascii-chat/platform/socket.h>
25#include <ascii-chat/util/time.h>
26#include <ascii-chat/util/endian.h>
27#include <ascii-chat/crypto/keys.h>
43static uint64_t session_get_current_time_ms(
void) {
45 return time_ns_to_ms(current_time_ns);
50 SET_ERRNO(ERROR_INVALID_PARAM,
"config is NULL");
56 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate discovery session");
81 log_info(
"discovery_session_create: After CALLOC - participant_ctx=%p, host_ctx=%p", session->
participant_ctx,
85 char identity_path[256];
87 if (id_result == ASCIICHAT_OK) {
89 if (id_result != ASCIICHAT_OK) {
91 log_info(
"Identity key not found, generating new key");
93 if (id_result == ASCIICHAT_OK) {
100 if (id_result != ASCIICHAT_OK) {
101 log_warn(
"Failed to load/generate identity key, using zero key");
105 char fingerprint[65];
107 log_info(
"Using identity key with fingerprint: %.16s...", fingerprint);
142 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
147 if (session->
acds_socket != INVALID_SOCKET_VALUE) {
188 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
193 session->
state = new_state;
195 log_debug(
"Discovery state: %d -> %d", old_state, new_state);
202static void set_error(
discovery_session_t *session, asciichat_error_t error,
const char *message) {
204 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
208 session->
error = error;
211 log_error(
"Discovery error: %s", message ? message :
"Unknown error");
226static asciichat_error_t gather_nat_quality(
nat_quality_t *quality) {
228 return SET_ERRNO(ERROR_INVALID_PARAM,
"quality is NULL");
237 const char *stun_servers_option = GET_OPTION(stun_servers);
238 const char *stun_server_for_probe = OPT_ENDPOINT_STUN_FALLBACK;
240 if (stun_servers_option && stun_servers_option[0] !=
'\0') {
244 stun_server_for_probe = OPT_ENDPOINT_STUN_FALLBACK;
248 const char *probe_host = stun_server_for_probe;
249 if (strncmp(probe_host,
"stun:", 5) == 0) {
250 probe_host = stun_server_for_probe + 5;
256 if (result != ASCIICHAT_OK) {
257 log_warn(
"NAT detection had issues, using partial data");
272 if (!session || session->
acds_socket == INVALID_SOCKET_VALUE) {
273 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid session or ACDS socket");
278 asciichat_error_t result = gather_nat_quality(&our_quality);
279 if (result != ASCIICHAT_OK) {
280 log_warn(
"Failed to gather NAT quality: %d", result);
285 acip_nat_quality_t wire_quality;
289 asciichat_error_t send_result =
292 if (send_result == ASCIICHAT_OK) {
293 log_debug(
"Sent NETWORK_QUALITY to ACDS (NAT tier: %d, upload: %u Kbps)",
nat_compute_tier(&our_quality),
296 log_error(
"Failed to send NETWORK_QUALITY: %d", send_result);
312 if (!session || session->
acds_socket == INVALID_SOCKET_VALUE) {
313 return SET_ERRNO(ERROR_INVALID_PARAM,
"invalid session or ACDS socket");
322 if (result != ASCIICHAT_OK) {
329 if (ptype != PACKET_TYPE_ACIP_NETWORK_QUALITY) {
330 log_debug(
"Received packet type %u (expected NETWORK_QUALITY)", ptype);
333 return ERROR_NETWORK_PROTOCOL;
337 if (len <
sizeof(acip_nat_quality_t)) {
338 log_error(
"NETWORK_QUALITY packet too small: %zu bytes", len);
341 return ERROR_NETWORK_SIZE;
344 acip_nat_quality_t *wire_quality = (acip_nat_quality_t *)data;
350 log_debug(
"Received NETWORK_QUALITY from peer (NAT tier: %d, upload: %u Kbps)",
360 return SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
367 struct addrinfo hints, *result = NULL;
368 memset(&hints, 0,
sizeof(hints));
369 hints.ai_family = AF_UNSPEC;
370 hints.ai_socktype = SOCK_STREAM;
371 hints.ai_protocol = IPPROTO_TCP;
376 int gai_err = getaddrinfo(session->
acds_address, port_str, &hints, &result);
378 set_error(session, ERROR_NETWORK_CONNECT,
"Failed to resolve ACDS address");
379 log_error(
"getaddrinfo failed: %s", gai_strerror(gai_err));
380 return ERROR_NETWORK_CONNECT;
384 session->
acds_socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
385 if (session->
acds_socket == INVALID_SOCKET_VALUE) {
386 set_error(session, ERROR_NETWORK,
"Failed to create socket");
387 freeaddrinfo(result);
388 return ERROR_NETWORK;
395 freeaddrinfo(result);
396 return ERROR_NETWORK_CONNECT;
400 if (socket_set_nonblocking(session->
acds_socket,
true) != 0) {
401 set_error(session, ERROR_NETWORK,
"Failed to set non-blocking mode");
404 freeaddrinfo(result);
405 return ERROR_NETWORK;
409 int connect_result = connect(session->
acds_socket, result->ai_addr, (socklen_t)result->ai_addrlen);
411 if (connect_result == 0) {
414 freeaddrinfo(result);
415 log_info(
"Connected to ACDS");
420 int last_error = socket_get_last_error();
421 if (!socket_is_in_progress_error(last_error)) {
422 set_error(session, ERROR_NETWORK_CONNECT,
"Failed to connect to ACDS");
425 freeaddrinfo(result);
426 return ERROR_NETWORK_CONNECT;
430 const uint64_t start_time_ms = session_get_current_time_ms();
431 const uint64_t max_connect_timeout_ms = 10000;
436 log_debug(
"ACDS connection interrupted by exit signal");
439 freeaddrinfo(result);
440 return ERROR_NETWORK_CONNECT;
444 uint64_t elapsed_ms = session_get_current_time_ms() - start_time_ms;
445 if (elapsed_ms >= max_connect_timeout_ms) {
446 log_debug(
"ACDS connection timeout after %lu ms", elapsed_ms);
447 set_error(session, ERROR_NETWORK_CONNECT,
"Connection to ACDS timed out");
450 freeaddrinfo(result);
451 return ERROR_NETWORK_CONNECT;
459 fd_set writefds, exceptfds;
460 socket_fd_zero(&writefds);
461 socket_fd_zero(&exceptfds);
465 int select_result = socket_select(session->
acds_socket + 1, NULL, &writefds, &exceptfds, &tv);
467 if (select_result < 0) {
468 set_error(session, ERROR_NETWORK,
"select() failed");
471 freeaddrinfo(result);
472 return ERROR_NETWORK;
475 if (select_result == 0) {
481 if (socket_fd_isset(session->
acds_socket, &exceptfds)) {
483 int socket_errno = socket_get_error(session->
acds_socket);
484 log_debug(
"ACDS connection failed with error: %d", socket_errno);
485 set_error(session, ERROR_NETWORK_CONNECT,
"Failed to connect to ACDS");
488 freeaddrinfo(result);
489 return ERROR_NETWORK_CONNECT;
492 if (socket_fd_isset(session->
acds_socket, &writefds)) {
494 int socket_errno = socket_get_error(session->
acds_socket);
495 if (socket_errno != 0) {
496 log_debug(
"ACDS connection failed with SO_ERROR: %d", socket_errno);
497 set_error(session, ERROR_NETWORK_CONNECT,
"Failed to connect to ACDS");
500 freeaddrinfo(result);
501 return ERROR_NETWORK_CONNECT;
506 freeaddrinfo(result);
507 log_info(
"Connected to ACDS");
515 return SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
519 log_info(
"Creating new discovery session...");
522 acip_session_create_t create_msg;
523 memset(&create_msg, 0,
sizeof(create_msg));
529 create_msg.timestamp = session_get_current_time_ms();
532 create_msg.capabilities = 0x03;
533 create_msg.max_participants = 8;
536 asciichat_error_t sig_result =
538 create_msg.max_participants, create_msg.signature);
539 if (sig_result != ASCIICHAT_OK) {
540 set_error(session, sig_result,
"Failed to sign SESSION_CREATE");
546 if (opts && opts->prefer_webrtc) {
547 log_info(
"DISCOVERY: WebRTC preferred, using SESSION_TYPE_WEBRTC");
548 create_msg.session_type = SESSION_TYPE_WEBRTC;
550 log_info(
"DISCOVERY: Using direct TCP (SESSION_TYPE_DIRECT_TCP)");
551 create_msg.session_type = SESSION_TYPE_DIRECT_TCP;
554 create_msg.has_password = 0;
555 create_msg.expose_ip_publicly = 0;
556 create_msg.reserved_string_len = 0;
559 SAFE_STRNCPY(create_msg.server_address,
"127.0.0.1",
sizeof(create_msg.server_address));
560 create_msg.server_port = ACIP_HOST_DEFAULT_PORT;
563 asciichat_error_t result =
565 if (result != ASCIICHAT_OK) {
566 set_error(session, result,
"Failed to send SESSION_CREATE");
576 if (result != ASCIICHAT_OK) {
577 set_error(session, result,
"Failed to receive SESSION_CREATED");
581 if (type == PACKET_TYPE_ACIP_ERROR) {
582 acip_error_t *error = (acip_error_t *)data;
583 log_error(
"ACDS error: %s", error->error_message);
584 set_error(session, ERROR_NETWORK_PROTOCOL, error->error_message);
585 POOL_FREE(data, len);
586 return ERROR_NETWORK_PROTOCOL;
589 if (type != PACKET_TYPE_ACIP_SESSION_CREATED || len <
sizeof(acip_session_created_t)) {
590 set_error(session, ERROR_NETWORK_PROTOCOL,
"Unexpected response to SESSION_CREATE");
591 POOL_FREE(data, len);
592 return ERROR_NETWORK_PROTOCOL;
595 acip_session_created_t *created = (acip_session_created_t *)data;
598 memcpy(session->
session_id, created->session_id, 16);
607 POOL_FREE(data, len);
627static asciichat_error_t discovery_send_sdp_via_acds(
const uint8_t
session_id[16],
const uint8_t recipient_id[16],
628 const char *sdp_type,
const char *sdp,
void *user_data) {
631 if (!session || session->
acds_socket == INVALID_SOCKET_VALUE) {
632 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid session or ACDS socket");
636 size_t sdp_len = strlen(sdp);
637 size_t total_len =
sizeof(acip_webrtc_sdp_t) + sdp_len;
639 uint8_t *packet_data = SAFE_MALLOC(total_len, uint8_t *);
641 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate SDP packet");
643 acip_webrtc_sdp_t *sdp_msg = (acip_webrtc_sdp_t *)packet_data;
647 memcpy(sdp_msg->recipient_id, recipient_id, 16);
648 sdp_msg->sdp_type = (strcmp(sdp_type,
"offer") == 0) ? 0 : 1;
649 sdp_msg->sdp_len = HOST_TO_NET_U16(sdp_len);
650 memcpy(packet_data +
sizeof(acip_webrtc_sdp_t), sdp, sdp_len);
653 asciichat_error_t result =
packet_send(session->
acds_socket, PACKET_TYPE_ACIP_WEBRTC_SDP, packet_data, total_len);
655 SAFE_FREE(packet_data);
657 if (result != ASCIICHAT_OK) {
658 return SET_ERRNO(ERROR_NETWORK,
"Failed to send SDP to ACDS");
661 log_info(
"Sent SDP %s to ACDS (len=%zu)", sdp_type, sdp_len);
668static asciichat_error_t discovery_send_ice_via_acds(
const uint8_t
session_id[16],
const uint8_t recipient_id[16],
669 const char *candidate,
const char *mid,
void *user_data) {
672 if (!session || session->
acds_socket == INVALID_SOCKET_VALUE) {
673 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid session or ACDS socket");
677 size_t candidate_len = strlen(candidate);
678 size_t mid_len = strlen(mid);
679 size_t payload_len = candidate_len + 1 + mid_len + 1;
682 size_t total_len =
sizeof(acip_webrtc_ice_t) + payload_len;
683 uint8_t *packet_data = SAFE_MALLOC(total_len, uint8_t *);
685 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate ICE packet");
689 acip_webrtc_ice_t *ice_msg = (acip_webrtc_ice_t *)packet_data;
692 memcpy(ice_msg->recipient_id, recipient_id, 16);
693 ice_msg->candidate_len = HOST_TO_NET_U16((uint16_t)candidate_len);
696 uint8_t *payload = packet_data +
sizeof(acip_webrtc_ice_t);
697 memcpy(payload, candidate, candidate_len);
698 payload[candidate_len] =
'\0';
699 memcpy(payload + candidate_len + 1, mid, mid_len);
700 payload[candidate_len + 1 + mid_len] =
'\0';
702 log_debug(
"Sending ICE candidate to ACDS (candidate_len=%zu, mid=%s)", candidate_len, mid);
705 asciichat_error_t result =
packet_send(session->
acds_socket, PACKET_TYPE_ACIP_WEBRTC_ICE, packet_data, total_len);
707 SAFE_FREE(packet_data);
709 if (result != ASCIICHAT_OK) {
710 return SET_ERRNO(ERROR_NETWORK,
"Failed to send ICE to ACDS");
713 log_debug(
"Sent ICE candidate to ACDS (candidate_len=%zu, mid=%s)", candidate_len, mid);
725static uint32_t calculate_backoff_delay_ms(
int attempt) {
727 uint32_t base_delay_ms = 1000U << attempt;
730 if (base_delay_ms > 30 * MS_PER_SEC_INT) {
731 base_delay_ms = 30 * MS_PER_SEC_INT;
738 return base_delay_ms + jitter_ms;
748static void discovery_on_gathering_timeout(
const uint8_t
participant_id[16], uint32_t timeout_ms, uint64_t elapsed_ms,
752 log_warn(
"Peer connection failed to gather ICE candidates");
753 log_debug(
" Participant: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
participant_id[0],
757 log_error(
" Timeout: %u ms", timeout_ms);
758 log_error(
" Elapsed: %llu ms", (
unsigned long long)elapsed_ms);
759 log_error(
"===========================================");
760 log_warn(
"Possible causes: STUN/TURN servers unreachable, firewall blocking UDP, network issues");
777static void discovery_on_transport_ready(acip_transport_t *transport,
const uint8_t
participant_id[16],
782 SET_ERRNO(ERROR_INVALID_STATE,
"discovery_on_transport_ready: session is NULL");
786 log_info(
"========== WebRTC DataChannel READY ==========");
787 log_info(
"WebRTC DataChannel successfully established and wrapped in ACIP transport");
790 log_info(
" Transport status: READY for media transmission");
791 log_info(
" Encryption: DTLS (automatic) + optional ACIP layer encryption");
797 log_warn(
"WebRTC transport ready but session type is not WebRTC!");
801 log_info(
"========== WebRTC Media Channel ACTIVE ==========");
802 log_info(
"Switching to WebRTC transport for media transmission...");
808 uint32_t client_id = 0;
813 if (result == ASCIICHAT_OK) {
814 log_info(
"✓ Host: Successfully switched client %u to WebRTC transport", client_id);
816 log_error(
"✗ Host: Failed to set WebRTC transport for client %u: %d", client_id, result);
821 if (result == ASCIICHAT_OK) {
822 log_info(
"✓ Participant: Successfully switched to WebRTC transport");
823 log_info(
" TCP connection remains open as control/signaling channel");
824 log_info(
" Media frames will now be transmitted over WebRTC DataChannel");
826 log_error(
"✗ Participant: Failed to set WebRTC transport: %d", result);
829 log_warn(
"transport_ready: No participant or host context available for transport switching");
841 return SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
846 if (init_result != ASCIICHAT_OK) {
847 return SET_ERRNO(ERROR_NETWORK,
"Failed to initialize WebRTC library");
851 if (GET_OPTION(webrtc_skip_stun)) {
852 log_info(
"Skipping STUN (--webrtc-skip-stun) - will use TURN relay only");
857 const int max_stun_servers = 4;
858 session->
stun_servers = SAFE_MALLOC(max_stun_servers *
sizeof(stun_server_t), stun_server_t *);
860 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate STUN server array");
868 log_warn(
"No STUN servers configured, WebRTC may fail with symmetric NAT");
872 log_info(
"Configured %d STUN server(s) for WebRTC", session->
stun_count);
877 if (GET_OPTION(webrtc_disable_turn)) {
878 log_info(
"TURN disabled (--webrtc-disable-turn) - will use direct P2P + STUN only");
883 const char *turn_servers_str = GET_OPTION(turn_servers);
884 if (!turn_servers_str || turn_servers_str[0] ==
'\0') {
885 turn_servers_str = OPT_ENDPOINT_TURN_SERVERS_DEFAULT;
889 const int max_turn_servers = 4;
890 session->
turn_servers = SAFE_MALLOC(max_turn_servers *
sizeof(turn_server_t), turn_server_t *);
895 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate TURN server array");
899 char turn_copy[OPTIONS_BUFF_SIZE];
900 SAFE_STRNCPY(turn_copy, turn_servers_str,
sizeof(turn_copy));
903 char *saveptr = NULL;
904 char *token = platform_strtok_r(turn_copy,
",", &saveptr);
905 while (token && session->
turn_count < max_turn_servers) {
907 while (*token ==
' ' || *token ==
'\t')
909 size_t len = strlen(token);
910 while (len > 0 && (token[len - 1] ==
' ' || token[len - 1] ==
'\t')) {
914 if (len > 0 && len <
sizeof(session->
turn_servers[0].url)) {
930 }
else if (len > 0) {
931 log_warn(
"TURN server URL too long (max 63 chars): %s", token);
934 token = platform_strtok_r(NULL,
",", &saveptr);
938 log_info(
"Configured %d TURN server(s) with ACDS credentials for symmetric NAT relay", session->
turn_count);
940 log_warn(
"No valid TURN servers configured");
945 log_info(
"No TURN credentials from ACDS - will rely on direct P2P + STUN (sufficient for most NAT scenarios)");
952 webrtc_peer_role_t role = WEBRTC_ROLE_JOINER;
954 log_info(
"Initializing WebRTC peer manager (role=%s, stun_count=%zu, turn_count=%zu)",
955 role == WEBRTC_ROLE_JOINER ?
"JOINER" :
"CREATOR", session->stun_count, session->turn_count);
958 webrtc_peer_manager_config_t pm_config = {
964 .on_transport_ready = discovery_on_transport_ready,
965 .on_gathering_timeout = discovery_on_gathering_timeout,
966 .user_data = session,
971 webrtc_signaling_callbacks_t signaling = {
972 .send_sdp = discovery_send_sdp_via_acds, .send_ice = discovery_send_ice_via_acds, .user_data = session};
976 if (result != ASCIICHAT_OK) {
994static void handle_discovery_webrtc_sdp(
discovery_session_t *session,
void *data,
size_t len) {
995 if (len <
sizeof(acip_webrtc_sdp_t)) {
996 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid SDP packet size: %zu", len);
1000 const acip_webrtc_sdp_t *sdp_msg = (
const acip_webrtc_sdp_t *)data;
1001 uint16_t sdp_len = NET_TO_HOST_U16(sdp_msg->sdp_len);
1003 if (len !=
sizeof(acip_webrtc_sdp_t) + sdp_len) {
1004 log_error(
"SDP packet size mismatch: expected %zu, got %zu",
sizeof(acip_webrtc_sdp_t) + sdp_len, len);
1009 char *sdp_str = SAFE_MALLOC(sdp_len + 1,
char *);
1011 log_error(
"Failed to allocate SDP string");
1014 memcpy(sdp_str, (
const uint8_t *)data +
sizeof(acip_webrtc_sdp_t), sdp_len);
1015 sdp_str[sdp_len] =
'\0';
1017 const char *sdp_type = (sdp_msg->sdp_type == 0) ?
"offer" :
"answer";
1018 log_info(
"Received SDP %s from ACDS (len=%u)", sdp_type, sdp_len);
1023 if (result != ASCIICHAT_OK) {
1024 log_error(
"Failed to handle SDP: %d", result);
1027 log_error(
"Received SDP but peer manager not initialized");
1036static void handle_discovery_webrtc_ice(
discovery_session_t *session,
void *data,
size_t len) {
1037 if (len <
sizeof(acip_webrtc_ice_t)) {
1038 log_error(
"Invalid ICE packet size: %zu", len);
1042 const acip_webrtc_ice_t *ice_msg = (
const acip_webrtc_ice_t *)data;
1043 uint16_t candidate_len = NET_TO_HOST_U16(ice_msg->candidate_len);
1045 log_debug(
"★ ICE VALIDATION: len=%zu, sizeof(header)=%zu, candidate_len=%u, expected=%zu", len,
1046 sizeof(acip_webrtc_ice_t), candidate_len,
sizeof(acip_webrtc_ice_t) + candidate_len);
1050 if (len <
sizeof(acip_webrtc_ice_t) + candidate_len) {
1051 log_error(
"ICE packet too small: expected at least %zu, got %zu",
sizeof(acip_webrtc_ice_t) + candidate_len, len);
1055 log_debug(
"Received ICE candidate from ACDS (len=%u)", candidate_len);
1060 if (result != ASCIICHAT_OK) {
1061 log_error(
"Failed to handle ICE candidate: %d", result);
1064 log_error(
"Received ICE but peer manager not initialized");
1071static void handle_acds_webrtc_packet(
discovery_session_t *session, packet_type_t type,
void *data,
size_t len) {
1073 case PACKET_TYPE_ACIP_WEBRTC_SDP:
1074 handle_discovery_webrtc_sdp(session, data, len);
1076 case PACKET_TYPE_ACIP_WEBRTC_ICE:
1077 handle_discovery_webrtc_ice(session, data, len);
1080 log_debug(
"Unhandled WebRTC packet type: 0x%04x", type);
1088 log_info(
"join_session: START - participant_ctx=%p", session->
participant_ctx);
1091 return ERROR_INVALID_PARAM;
1097 acip_session_join_t join_msg;
1098 memset(&join_msg, 0,
sizeof(join_msg));
1100 join_msg.session_string_len = (uint8_t)strlen(session->
session_string);
1101 SAFE_STRNCPY(join_msg.session_string, session->
session_string,
sizeof(join_msg.session_string));
1107 join_msg.timestamp = session_get_current_time_ms();
1110 asciichat_error_t sig_result =
1112 if (sig_result != ASCIICHAT_OK) {
1113 set_error(session, sig_result,
"Failed to sign SESSION_JOIN");
1117 join_msg.has_password = 0;
1120 asciichat_error_t result =
1122 if (result != ASCIICHAT_OK) {
1123 set_error(session, result,
"Failed to send SESSION_JOIN");
1133 if (result != ASCIICHAT_OK) {
1134 set_error(session, result,
"Failed to receive SESSION_JOINED");
1138 if (type == PACKET_TYPE_ACIP_ERROR) {
1139 acip_error_t *error = (acip_error_t *)data;
1140 log_error(
"ACDS error: %s", error->error_message);
1141 set_error(session, ERROR_NETWORK_PROTOCOL, error->error_message);
1142 POOL_FREE(data, len);
1143 return ERROR_NETWORK_PROTOCOL;
1146 if (type != PACKET_TYPE_ACIP_SESSION_JOINED || len <
sizeof(acip_session_joined_t)) {
1147 set_error(session, ERROR_NETWORK_PROTOCOL,
"Unexpected response to SESSION_JOIN");
1148 POOL_FREE(data, len);
1149 return ERROR_NETWORK_PROTOCOL;
1152 acip_session_joined_t *joined = (acip_session_joined_t *)data;
1154 if (!joined->success) {
1155 log_error(
"Failed to join session: %s", joined->error_message);
1156 set_error(session, ERROR_NETWORK_PROTOCOL, joined->error_message);
1157 POOL_FREE(data, len);
1158 return ERROR_NETWORK_PROTOCOL;
1162 memcpy(session->
session_id, joined->session_id, 16);
1167 if (joined->session_type == 1) {
1170 log_info(
"WebRTC session detected - TURN username: %s", session->
turn_username);
1173 asciichat_error_t webrtc_result = initialize_webrtc_peer_manager(session);
1174 if (webrtc_result != ASCIICHAT_OK) {
1175 log_error(
"Failed to initialize WebRTC peer manager: %d", webrtc_result);
1176 POOL_FREE(data, len);
1177 return webrtc_result;
1179 log_info(
"WebRTC peer manager initialized successfully");
1183 if (joined->server_address[0] && joined->server_port > 0) {
1186 session->
host_port = joined->server_port;
1189 log_info(
"Host already established: %s:%u (session_type=%u, participant_ctx=%p before transition)",
1191 POOL_FREE(data, len);
1193 log_info(
"join_session: About to transition to CONNECTING_HOST - participant_ctx=%p", session->
participant_ctx);
1195 log_info(
"join_session: Transitioned to CONNECTING_HOST - participant_ctx=%p (returning ASCIICHAT_OK)",
1197 return ASCIICHAT_OK;
1201 log_info(
"No host established (server_address='%s' port=%u), starting negotiation...", joined->server_address,
1202 joined->server_port);
1203 POOL_FREE(data, len);
1206 return ASCIICHAT_OK;
1210 log_info(
"discovery_session_start: ENTRY - session=%p", session);
1213 return SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
1216 log_info(
"discovery_session_start: is_initiator=%d, session_string='%s'", session->
is_initiator,
1221 log_info(
"discovery_session_start: Exiting early due to should_exit_callback (before ACDS connect)");
1222 return ASCIICHAT_OK;
1225 log_info(
"discovery_session_start: Calling connect_to_acds...");
1227 asciichat_error_t result = connect_to_acds(session);
1228 if (result != ASCIICHAT_OK) {
1229 log_error(
"discovery_session_start: connect_to_acds FAILED - result=%d", result);
1232 log_info(
"discovery_session_start: connect_to_acds succeeded");
1236 log_info(
"discovery_session_start: Exiting early due to should_exit_callback (after ACDS connect)");
1237 return ASCIICHAT_OK;
1242 log_info(
"discovery_session_start: Calling create_session - participant_ctx before=%p", session->
participant_ctx);
1243 asciichat_error_t result = create_session(session);
1244 log_info(
"discovery_session_start: create_session returned - participant_ctx after=%p, result=%d",
1248 log_info(
"discovery_session_start: Calling join_session - participant_ctx before=%p", session->
participant_ctx);
1249 asciichat_error_t result = join_session(session);
1250 log_info(
"discovery_session_start: join_session returned - participant_ctx after=%p, state=%d, result=%d",
1258 return SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
1262 switch (session->
state) {
1265 if (session->
acds_socket != INVALID_SOCKET_VALUE && timeout_ns > 0) {
1274 if (recv_result == ASCIICHAT_OK) {
1275 if (type == PACKET_TYPE_ACIP_PARTICIPANT_JOINED) {
1276 log_info(
"Peer joined session, transitioning to negotiation");
1278 POOL_FREE(data, len);
1281 log_debug(
"Received packet type %d while waiting for peer", type);
1282 POOL_FREE(data, len);
1284 }
else if (recv_result == ERROR_NETWORK_TIMEOUT) {
1287 log_debug(
"Packet receive error while waiting for peer: %d", recv_result);
1302 log_info(
"Starting NAT negotiation (initiator=%d)", session->
is_initiator);
1305 asciichat_error_t send_result = send_network_quality_to_acds(session);
1306 if (send_result != ASCIICHAT_OK) {
1307 log_warn(
"Failed to send initial NETWORK_QUALITY: %d", send_result);
1314 asciichat_error_t recv_result = receive_network_quality_from_acds(session);
1315 if (recv_result == ASCIICHAT_OK) {
1317 log_info(
"Received peer NETWORK_QUALITY, proceeding to host election");
1318 }
else if (recv_result != ERROR_NETWORK_TIMEOUT) {
1320 log_debug(
"Error receiving NETWORK_QUALITY: %d (this might be normal if peer not ready)", recv_result);
1327 log_info(
"Both NAT qualities received, determining host...");
1329 if (result == ASCIICHAT_OK) {
1335 log_info(
"🏠 NAT negotiation complete: WE ARE HOST (tier:%d vs tier:%d)",
1344 log_info(
"👤 NAT negotiation complete: THEY ARE HOST at %s:%u (tier:%d vs tier:%d)",
1353 log_error(
"Failed to determine negotiation result: %d", result);
1354 set_error(session, result,
"Host election failed");
1367 int host_port = GET_OPTION(port);
1369 session_host_config_t hconfig = {
1371 .ipv4_address =
"0.0.0.0",
1373 .encryption_enabled =
true,
1378 log_error(
"Failed to create host context");
1379 set_error(session, ERROR_MEMORY,
"Failed to create host context");
1385 if (hstart != ASCIICHAT_OK) {
1386 log_error(
"Failed to start host: %d", hstart);
1387 set_error(session, hstart,
"Failed to start host");
1391 log_info(
"Host started, listening for connections");
1410 log_info(
"WebRTC DataChannel established, transitioning to ACTIVE state");
1417 log_info(
"Initiating WebRTC connection to host (attempt %d/%d)...", session->
webrtc_retry_attempt + 1,
1418 GET_OPTION(webrtc_reconnect_attempts) + 1);
1421 asciichat_error_t conn_result =
1424 if (conn_result != ASCIICHAT_OK) {
1425 log_error(
"Failed to initiate WebRTC connection: %d", conn_result);
1426 set_error(session, conn_result,
"Failed to initiate WebRTC connection");
1430 log_info(
"WebRTC connection initiated, waiting for DataChannel...");
1437 int timeout_ms = GET_OPTION(webrtc_ice_timeout_ms);
1440 if (timed_out_count > 0) {
1441 log_error(
"ICE gathering timeout: %d peer(s) failed to gather candidates within %dms", timed_out_count,
1445 int max_attempts = GET_OPTION(webrtc_reconnect_attempts);
1450 log_warn(
"WebRTC connection attempt %d/%d failed, retrying in %ums...", session->
webrtc_retry_attempt + 1,
1451 max_attempts + 1, backoff_ms);
1461 asciichat_error_t reinit_result = initialize_webrtc_peer_manager(session);
1462 if (reinit_result != ASCIICHAT_OK) {
1463 log_error(
"Failed to re-initialize WebRTC for retry: %d", reinit_result);
1464 set_error(session, reinit_result,
"Failed to re-initialize WebRTC for retry");
1467 if (GET_OPTION(prefer_webrtc)) {
1468 log_fatal(
"WebRTC connection failed and --prefer-webrtc is set - exiting");
1470 return ERROR_NETWORK_TIMEOUT;
1480 log_info(
"WebRTC peer manager re-initialized for attempt %d/%d", session->
webrtc_retry_attempt + 1,
1484 log_error(
"WebRTC connection failed after %d attempts (timeout: %dms)", max_attempts + 1, timeout_ms);
1487 char error_msg[256];
1488 snprintf(error_msg,
sizeof(error_msg),
"WebRTC connection failed after %d attempts (%dms timeout)",
1489 max_attempts + 1, timeout_ms);
1490 set_error(session, ERROR_NETWORK_TIMEOUT, error_msg);
1493 if (GET_OPTION(prefer_webrtc)) {
1494 log_fatal(
"WebRTC connection failed and --prefer-webrtc is set - exiting");
1496 return ERROR_NETWORK_TIMEOUT;
1506 if (session->
acds_socket != INVALID_SOCKET_VALUE) {
1508 struct timeval tv_timeout;
1509 tv_timeout.tv_sec = timeout_ns / NS_PER_SEC_INT;
1510 tv_timeout.tv_usec = (timeout_ns % NS_PER_SEC_INT) / 1000;
1516 int select_result = select(session->
acds_socket + 1, &readfds, NULL, NULL, &tv_timeout);
1517 if (select_result > 0 && FD_ISSET(session->
acds_socket, &readfds)) {
1525 if (recv_result == ASCIICHAT_OK) {
1527 handle_acds_webrtc_packet(session, type, data, len);
1528 POOL_FREE(data, len);
1530 log_debug(
"Failed to receive ACDS packet: %d", recv_result);
1542 session_participant_config_t pconfig = {
1545 .enable_audio =
true,
1546 .enable_video =
true,
1547 .encryption_enabled =
true,
1552 log_error(
"Failed to create participant context");
1553 set_error(session, ERROR_MEMORY,
"Failed to create participant context");
1559 if (pconn != ASCIICHAT_OK) {
1560 log_error(
"Failed to connect as participant: %d", pconn);
1565 log_info(
"Connected to host, performing crypto handshake...");
1570 if (participant_socket == INVALID_SOCKET_VALUE) {
1571 log_error(
"Failed to get socket from participant context");
1572 set_error(session, ERROR_NETWORK,
"Failed to get participant socket");
1578 log_debug(
"Sending protocol version to server...");
1579 protocol_version_packet_t client_version = {0};
1580 client_version.protocol_version = HOST_TO_NET_U16(1);
1581 client_version.protocol_revision = HOST_TO_NET_U16(0);
1582 client_version.supports_encryption = 1;
1583 client_version.compression_algorithms = 0;
1584 client_version.compression_threshold = 0;
1585 client_version.feature_flags = 0;
1589 log_error(
"Failed to send protocol version to server");
1590 set_error(session, ERROR_NETWORK,
"Failed to send protocol version");
1596 log_debug(
"Receiving server protocol version...");
1597 packet_type_t packet_type;
1598 void *payload = NULL;
1599 size_t payload_len = 0;
1601 int recv_result =
receive_packet(participant_socket, &packet_type, &payload, &payload_len);
1602 if (recv_result != ASCIICHAT_OK || packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
1603 log_error(
"Failed to receive server protocol version (got type 0x%x)", packet_type);
1607 set_error(session, ERROR_NETWORK,
"Failed to receive protocol version from server");
1612 if (payload_len !=
sizeof(protocol_version_packet_t)) {
1613 log_error(
"Invalid protocol version packet size: %zu", payload_len);
1615 set_error(session, ERROR_NETWORK,
"Invalid protocol version packet");
1620 protocol_version_packet_t server_version;
1621 memcpy(&server_version, payload,
sizeof(protocol_version_packet_t));
1624 if (!server_version.supports_encryption) {
1625 log_error(
"Server does not support encryption");
1626 set_error(session, ERROR_NETWORK,
"Server does not support encryption");
1631 log_info(
"Server protocol version: %u.%u (encryption: yes)", NET_TO_HOST_U16(server_version.protocol_version),
1632 NET_TO_HOST_U16(server_version.protocol_revision));
1635 log_debug(
"Sending crypto capabilities...");
1636 crypto_capabilities_packet_t client_caps = {0};
1637 client_caps.supported_kex_algorithms = HOST_TO_NET_U16(0x0001);
1638 client_caps.supported_auth_algorithms = HOST_TO_NET_U16(0x0003);
1639 client_caps.supported_cipher_algorithms = HOST_TO_NET_U16(0x0001);
1640 client_caps.requires_verification = 0;
1641 client_caps.preferred_kex = 0x0001;
1642 client_caps.preferred_auth = 0x0001;
1643 client_caps.preferred_cipher = 0x0001;
1647 log_error(
"Failed to send crypto capabilities");
1648 set_error(session, ERROR_NETWORK,
"Failed to send crypto capabilities");
1653 log_info(
"Crypto handshake initiated successfully");
1654 log_warn(
"*** Connected to host as participant - transitioning to ACTIVE ***");
1660 log_debug(
"discovery_session_process: CONNECTING_HOST - participant_ctx=%p, ctx_valid=%d, is_connected=%d",
1663 if (ctx_valid && !is_connected) {
1665 log_debug(
"discovery_session_process: CONNECTING_HOST - context exists but not connected yet");
1669 log_info(
"Participant connection confirmed, transitioning to ACTIVE state");
1672 log_debug(
"discovery_session_process: CONNECTING_HOST - awaiting connection establishment (ctx_valid=%d, "
1674 ctx_valid, is_connected);
1683 struct pollfd pfd = {.fd = session->
acds_socket, .events = POLLIN, .revents = 0};
1685 int poll_result = socket_poll(&pfd, 1, 0);
1687 if (poll_result > 0 && (pfd.revents & POLLIN)) {
1695 if (recv_result == ASCIICHAT_OK) {
1697 handle_acds_webrtc_packet(session, type, data, len);
1698 POOL_FREE(data, len);
1707 if (host_check != ASCIICHAT_OK) {
1709 log_warn(
"Host disconnect detected during ACTIVE state");
1713 if (opts && opts->prefer_webrtc) {
1714 log_fatal(
"WebRTC connection failed and --prefer-webrtc is set - exiting");
1716 session->
error = ERROR_NETWORK;
1717 return ERROR_NETWORK;
1734 log_info(
"Migration complete, session resumed");
1737 log_error(
"Host migration timeout - session cannot recover");
1739 session->
error = ERROR_NETWORK;
1745 SET_ERRNO(ERROR_INVALID_STATE,
"Invalid session state");
1749 return ASCIICHAT_OK;
1754 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
1758 log_info(
"Stopping discovery session...");
1762 acip_session_leave_t leave_msg;
1763 memset(&leave_msg, 0,
sizeof(leave_msg));
1764 memcpy(leave_msg.session_id, session->
session_id, 16);
1775 SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
1778 return session->
state;
1783 SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
1791 SET_ERRNO(ERROR_INVALID_PARAM,
"null session or session string is empty");
1799 SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
1807 SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
1815 SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
1841 if (!session || !session->
is_host) {
1842 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid session or not a host");
1845 log_debug(
"Running host election with collected NETWORK_QUALITY");
1863 const char *stun_server = opts ? opts->stun_servers : NULL;
1864 if (stun_server && stun_server[0] !=
'\0') {
1865 log_debug(
"Re-measuring host NAT quality for election");
1869 if (session->
acds_socket != INVALID_SOCKET_VALUE) {
1881 uint8_t future_host_id[16];
1884 log_info(
"Election result: Future host elected (round %llu)",
1896 acip_future_host_elected_t msg = {0};
1897 memcpy(msg.session_id, session->
session_id, 16);
1898 memcpy(msg.future_host_id, future_host_id, 16);
1906 log_info(
"Broadcasted FUTURE_HOST_ELECTED: participant_id=%.*s, round=%llu", 16, (
char *)future_host_id,
1907 (
unsigned long long)msg.elected_at_round);
1909 return ASCIICHAT_OK;
1914 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
1915 return ERROR_INVALID_PARAM;
1924 log_info(
"Ring consensus initialized (host-mediated model)");
1925 return ASCIICHAT_OK;
1930 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
1931 return ERROR_INVALID_PARAM;
1937 log_info(
"Starting 5-minute proactive election round (collecting NETWORK_QUALITY from participants)");
1944 asciichat_error_t result = discovery_session_run_election(session);
1945 if (result != ASCIICHAT_OK) {
1946 log_error(
"Host election failed: %d", result);
1950 return ASCIICHAT_OK;
1952 log_debug(
"Ring round timer triggered (waiting for host to broadcast FUTURE_HOST_ELECTED)");
1953 return ASCIICHAT_OK;
1958 if (!session || session->
is_host) {
1960 return ASCIICHAT_OK;
1967 return ASCIICHAT_OK;
1970 return ERROR_NETWORK;
1976 return ERROR_NETWORK;
1981 return ERROR_NETWORK;
1984 uint64_t now_ms = session_get_current_time_ms();
1999 return ERROR_NETWORK;
2009 if (host_socket != INVALID_SOCKET_VALUE) {
2010 asciichat_error_t result =
packet_send(host_socket, PACKET_TYPE_PING, NULL, 0);
2011 if (result == ASCIICHAT_OK) {
2017 log_warn(
"Failed to send ping to host: %d", result);
2021 return ERROR_NETWORK;
2027 return ASCIICHAT_OK;
2032 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
2033 return ERROR_INVALID_PARAM;
2036 log_warn(
"Host disconnect detected: reason=%u", disconnect_reason);
2045 acip_host_lost_t host_lost = {0};
2046 memcpy(host_lost.session_id, session->
session_id, 16);
2048 memcpy(host_lost.last_host_id, session->
host_id, 16);
2049 host_lost.disconnect_reason = disconnect_reason;
2058 log_error(
"No future host pre-elected! Session cannot recover from host disconnect.");
2060 return ERROR_NETWORK;
2070 log_info(
"Becoming host (I'm the pre-elected future host)");
2082 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
2083 return ERROR_INVALID_PARAM;
2086 log_info(
"Starting as new host after migration (participant ID: %02x%02x...)", session->
participant_id[0],
2090 int host_port = GET_OPTION(port);
2097 session_host_config_t hconfig = {
2099 .ipv4_address =
"0.0.0.0",
2101 .encryption_enabled =
true,
2106 log_error(
"Failed to create host context for migration");
2107 return ERROR_MEMORY;
2112 if (hstart != ASCIICHAT_OK) {
2113 log_error(
"Failed to start host after migration: %d", hstart);
2117 log_info(
"Host restarted after migration, listening on port %d", host_port);
2121 acip_host_announcement_t announcement = {0};
2122 memcpy(announcement.session_id, session->
session_id, 16);
2124 SAFE_STRNCPY(announcement.host_address,
"127.0.0.1",
sizeof(announcement.host_address));
2126 announcement.host_port = host_port;
2127 announcement.connection_type = ACIP_CONNECTION_TYPE_DIRECT_PUBLIC;
2130 if (session->
acds_socket != INVALID_SOCKET_VALUE) {
2131 packet_send(session->
acds_socket, PACKET_TYPE_ACIP_HOST_ANNOUNCEMENT, &announcement,
sizeof(announcement));
2132 log_info(
"Sent HOST_ANNOUNCEMENT to ACDS for new host %02x%02x... at %s:%u", announcement.host_id[0],
2133 announcement.host_id[1], announcement.host_address, announcement.host_port);
2142 return ASCIICHAT_OK;
2147 SET_ERRNO(ERROR_INVALID_PARAM,
"session is NULL");
2148 return ERROR_INVALID_PARAM;
2161 session_participant_config_t pconfig = {
2164 .enable_audio =
true,
2165 .enable_video =
true,
2166 .encryption_enabled =
true,
2171 log_error(
"Failed to create participant context for future host");
2172 return ERROR_MEMORY;
2177 if (pconn != ASCIICHAT_OK) {
2178 log_error(
"Failed to connect to future host: %d", pconn);
2182 log_info(
"Connected to future host after migration");
2189 log_info(
"Updated host info to: %s:%u (id: %02x%02x...)", session->
host_address, session->
host_port,
2198 return ASCIICHAT_OK;
2202 char out_address[64], uint16_t *out_port,
2203 uint8_t *out_connection_type) {
2204 if (!session || !out_id || !out_address || !out_port || !out_connection_type) {
2205 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid output parameters");
2206 return ERROR_INVALID_PARAM;
2210 SET_ERRNO(ERROR_NOT_FOUND,
"Future host not elected yet");
2211 return ERROR_NOT_FOUND;
2219 return ASCIICHAT_OK;
2224 SET_ERRNO(ERROR_INVALID_PARAM,
"null session");
asciichat_error_t acds_sign_session_join(const uint8_t identity_seckey[64], uint64_t timestamp, const char *session_string, uint8_t signature_out[64])
asciichat_error_t acds_sign_session_create(const uint8_t identity_seckey[64], uint64_t timestamp, uint8_t capabilities, uint8_t max_participants, uint8_t signature_out[64])
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
asciichat_error_t session_host_set_client_transport(session_host_t *host, uint32_t client_id, acip_transport_t *transport)
asciichat_error_t session_host_start(session_host_t *host)
void session_host_destroy(session_host_t *host)
session_host_t * session_host_create(const session_host_config_t *config)
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
asciichat_error_t acds_identity_default_path(char *path_out, size_t path_size)
asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64])
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
asciichat_error_t webrtc_init(void)
void nat_quality_to_acip(const nat_quality_t *quality, const uint8_t session_id[16], const uint8_t participant_id[16], acip_nat_quality_t *out)
Convert nat_quality_t to acip_nat_quality_t for network transmission.
asciichat_error_t nat_measure_bandwidth(nat_quality_t *quality, socket_t acds_socket)
Measure upload bandwidth to ACDS server.
void nat_quality_init(nat_quality_t *quality)
Initialize NAT quality structure with defaults.
asciichat_error_t nat_detect_quality(nat_quality_t *quality, const char *stun_server, uint16_t local_port)
Detect NAT quality using all available methods.
int nat_compute_tier(const nat_quality_t *quality)
Compute NAT tier for host selection (0=best, 4=worst)
void nat_quality_from_acip(const acip_nat_quality_t *acip, nat_quality_t *out)
Convert acip_nat_quality_t to nat_quality_t.
NAT quality detection for discovery mode host selection.
void negotiate_init(negotiate_ctx_t *ctx, const uint8_t session_id[16], const uint8_t participant_id[16], bool is_initiator)
Initialize negotiation context.
asciichat_error_t negotiate_determine_result(negotiate_ctx_t *ctx)
Determine negotiation result.
Host negotiation logic for discovery mode.
@ NEGOTIATE_STATE_INIT
Initial state.
@ NEGOTIATE_STATE_COMPARING
Comparing qualities.
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
asciichat_error_t packet_receive(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a packet with proper header validation and CRC32 checking.
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
asciichat_error_t packet_send(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a packet with proper header and CRC32.
int send_crypto_capabilities_packet(socket_t sockfd, const crypto_capabilities_packet_t *caps)
Send crypto capabilities packet.
asciichat_error_t session_participant_connect(session_participant_t *p)
void session_participant_destroy(session_participant_t *p)
socket_t session_participant_get_socket(session_participant_t *p)
session_participant_t * session_participant_create(const session_participant_config_t *config)
bool session_participant_is_connected(session_participant_t *p)
asciichat_error_t session_participant_set_transport(session_participant_t *p, acip_transport_t *transport)
void session_participant_disconnect(session_participant_t *p)
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)
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
asciichat_error_t webrtc_peer_manager_connect(webrtc_peer_manager_t *manager, const uint8_t session_id[16], const uint8_t participant_id[16])
int webrtc_peer_manager_check_gathering_timeouts(webrtc_peer_manager_t *manager, uint32_t timeout_ms)
const options_t * options_get(void)
Discovery session flow management.
@ MIGRATION_STATE_DETECTED
Host disconnect detected.
@ MIGRATION_STATE_COMPLETE
Failover complete, call resumed.
@ MIGRATION_STATE_NONE
No migration in progress.
@ MIGRATION_STATE_FAILOVER
Failing over to pre-elected future host.
discovery_state_t
Discovery session state.
@ DISCOVERY_STATE_ENDED
Session ended.
@ DISCOVERY_STATE_JOINING_SESSION
Joining existing session.
@ DISCOVERY_STATE_INIT
Initial state.
@ DISCOVERY_STATE_ACTIVE
Session active (call in progress)
@ DISCOVERY_STATE_MIGRATING
Host migration in progress.
@ DISCOVERY_STATE_NEGOTIATING
NAT negotiation in progress.
@ DISCOVERY_STATE_CONNECTING_HOST
Connecting to host as participant.
@ DISCOVERY_STATE_CREATING_SESSION
Creating new session.
@ DISCOVERY_STATE_CONNECTING_ACDS
Connecting to ACDS.
@ DISCOVERY_STATE_WAITING_PEER
Waiting for peer to join (initiator)
@ DISCOVERY_STATE_FAILED
Session failed.
@ DISCOVERY_STATE_STARTING_HOST
Starting as host.
int socket_set_timeout(socket_t sock, uint64_t timeout_ns)
Set socket receive and send timeouts.
uint8_t participant_id[16]
bool discovery_session_is_active(const discovery_session_t *session)
Check if session is active (call in progress)
bool discovery_session_is_future_host(const discovery_session_t *session)
Check if we are the future host.
void discovery_session_stop(discovery_session_t *session)
Stop the discovery session.
bool discovery_session_is_host(const discovery_session_t *session)
Check if we are the host.
discovery_session_t * discovery_session_create(const discovery_config_t *config)
Create a new discovery session.
asciichat_error_t discovery_session_start(discovery_session_t *session)
Start the discovery session.
asciichat_error_t discovery_session_init_ring(discovery_session_t *session)
Initialize ring consensus state.
discovery_state_t discovery_session_get_state(const discovery_session_t *session)
Get current session state.
asciichat_error_t discovery_session_check_host_alive(discovery_session_t *session)
Detect host disconnect (check connection status)
session_host_t * discovery_session_get_host(discovery_session_t *session)
Get host context (if we are host)
asciichat_error_t discovery_session_get_future_host(const discovery_session_t *session, uint8_t out_id[16], char out_address[64], uint16_t *out_port, uint8_t *out_connection_type)
Get future host information.
asciichat_error_t discovery_session_process(discovery_session_t *session, int64_t timeout_ns)
Process session events (call in main loop)
asciichat_error_t discovery_session_start_ring_round(discovery_session_t *session)
Start a new ring consensus round (every 5 minutes or on new joiner)
const char * discovery_session_get_string(const discovery_session_t *session)
Get session string.
asciichat_error_t discovery_session_handle_host_disconnect(discovery_session_t *session, uint32_t disconnect_reason)
Handle host disconnect with automatic failover to future host.
asciichat_error_t discovery_session_connect_to_future_host(discovery_session_t *session)
Connect to pre-elected future host (called when NOT future host)
void discovery_session_destroy(discovery_session_t *session)
Destroy discovery session and free resources.
session_participant_t * discovery_session_get_participant(discovery_session_t *session)
Get participant context (if we are participant)
asciichat_error_t discovery_session_become_host(discovery_session_t *session)
Become the host (called when elected as future host)
Configuration for discovery session.
void * callback_user_data
const char * acds_address
ACDS address (default: "127.0.0.1")
void(* on_error)(asciichat_error_t error, const char *message, void *user_data)
uint16_t acds_port
ACDS port (default: 27225)
discovery_should_exit_fn should_exit_callback
void * exit_callback_data
void(* on_state_change)(discovery_state_t new_state, void *user_data)
const char * session_string
Session string to join (or NULL to create)
void(* on_session_ready)(const char *session_string, void *user_data)
Discovery session context.
void * callback_user_data
uint64_t webrtc_last_attempt_time_ms
Timestamp of last connection attempt (monotonic time)
uint8_t session_type
0 = DIRECT_TCP, 1 = WEBRTC
session_host_t * host_ctx
uint8_t participant_id[16]
int webrtc_retry_attempt
Current retry attempt number (0 = initial, 1+ = retries)
void * exit_callback_data
uint8_t identity_pubkey[32]
Ed25519 public key for this participant.
bool webrtc_transport_ready
True when DataChannel is open and transport created.
turn_server_t * turn_servers
TURN servers from ACDS.
size_t stun_count
Number of STUN servers.
void(* on_error)(asciichat_error_t error, const char *message, void *user_data)
uint8_t identity_seckey[64]
Ed25519 secret key for signing.
migration_ctx_t migration
stun_server_t * stun_servers
STUN servers from ACDS.
void(* on_session_ready)(const char *session_string, void *user_data)
char session_string[SESSION_STRING_BUFFER_SIZE]
discovery_should_exit_fn should_exit_callback
size_t turn_count
Number of TURN servers.
bool webrtc_connection_initiated
True when we've called webrtc_peer_manager_connect()
session_participant_t * participant_ctx
void(* on_state_change)(discovery_state_t new_state, void *user_data)
struct webrtc_peer_manager * peer_manager
WebRTC peer connection manager (NULL for TCP sessions)
negotiate_ctx_t negotiate
uint32_t consecutive_failures
Number of consecutive ping failures.
uint32_t max_failures
Threshold for triggering migration (default: 3)
uint64_t ping_interval_ms
Time between pings (default: 3000ms)
uint64_t last_pong_received_ms
Timestamp of last pong received (monotonic)
uint64_t last_ping_sent_ms
Timestamp of last ping sent (monotonic)
bool ping_in_flight
True if waiting for pong.
uint64_t timeout_ms
Timeout for ping response (default: 10000ms)
uint64_t detection_time_ms
When host disconnect detected (Unix ms)
migration_state_t state
Current migration state.
uint32_t disconnect_reason
Reason for disconnect (from HOST_LOST packet)
uint8_t last_host_id[16]
The host that died.
NAT quality assessment result.
bool detection_complete
All probes finished.
uint32_t upload_kbps
Upload bandwidth in Kbps.
bool has_public_ip
STUN reflexive == local IP.
bool we_are_host
True if we should become host.
char host_address[64]
Host's address (ours if we_are_host)
nat_quality_t our_quality
Our NAT quality.
bool peer_quality_received
Have we received peer's quality?
nat_quality_t peer_quality
Peer's NAT quality (when received)
uint16_t host_port
Host's port.
uint8_t future_host_id[16]
Who will host if current host dies.
uint8_t future_host_connection_type
acip_connection_type_t (DIRECT, UPNP, STUN, TURN)
uint16_t future_host_port
Port number.
uint64_t future_host_elected_round
Which 5-minute round this was elected in.
uint64_t last_ring_round_ms
When host last ran election (for 5-min timer)
char future_host_address[64]
Where to connect.
bool am_future_host
Am I the elected future host?
Internal session host structure.
Internal session participant structure.
char address[BUFFER_SIZE_SMALL]
Server address.
int stun_servers_parse(const char *csv_servers, const char *default_csv, stun_server_t *out_servers, int max_count)
Parse comma-separated STUN server URLs into stun_server_t array.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
uint64_t time_get_ns(void)