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

Go to the source code of this file.

Functions

discovery_session_tdiscovery_session_create (const discovery_config_t *config)
 Create a new discovery session.
 
void discovery_session_destroy (discovery_session_t *session)
 Destroy discovery session and free resources.
 
asciichat_error_t discovery_session_start (discovery_session_t *session)
 Start the discovery session.
 
asciichat_error_t discovery_session_process (discovery_session_t *session, int64_t timeout_ns)
 Process session events (call in main loop)
 
void discovery_session_stop (discovery_session_t *session)
 Stop the discovery session.
 
discovery_state_t discovery_session_get_state (const discovery_session_t *session)
 Get current session state.
 
bool discovery_session_is_active (const discovery_session_t *session)
 Check if session is active (call in progress)
 
const char * discovery_session_get_string (const discovery_session_t *session)
 Get session string.
 
bool discovery_session_is_host (const discovery_session_t *session)
 Check if we are the host.
 
session_host_tdiscovery_session_get_host (discovery_session_t *session)
 Get host context (if we are host)
 
session_participant_tdiscovery_session_get_participant (discovery_session_t *session)
 Get participant context (if we are participant)
 
asciichat_error_t discovery_session_init_ring (discovery_session_t *session)
 Initialize ring consensus state.
 
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)
 
asciichat_error_t discovery_session_check_host_alive (discovery_session_t *session)
 Detect host disconnect (check connection status)
 
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_become_host (discovery_session_t *session)
 Become the host (called when elected as 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)
 
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.
 
bool discovery_session_is_future_host (const discovery_session_t *session)
 Check if we are the future host.
 

Function Documentation

◆ discovery_session_become_host()

asciichat_error_t discovery_session_become_host ( discovery_session_t session)

Become the host (called when elected as future host)

Parameters
sessionSession context
Returns
ASCIICHAT_OK on success

Definition at line 2080 of file src/discovery/session.c.

2080 {
2081 if (!session) {
2082 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2083 return ERROR_INVALID_PARAM;
2084 }
2085
2086 log_info("Starting as new host after migration (participant ID: %02x%02x...)", session->participant_id[0],
2087 session->participant_id[1]);
2088
2089 // Get configured port (or use default)
2090 int host_port = GET_OPTION(port);
2091
2092 // Mark ourselves as host
2093 session->is_host = true;
2094
2095 // Create host context if needed with configured port
2096 if (!session->host_ctx) {
2097 session_host_config_t hconfig = {
2098 .port = host_port,
2099 .ipv4_address = "0.0.0.0",
2100 .max_clients = 32,
2101 .encryption_enabled = true,
2102 };
2103
2104 session->host_ctx = session_host_create(&hconfig);
2105 if (!session->host_ctx) {
2106 log_error("Failed to create host context for migration");
2107 return ERROR_MEMORY;
2108 }
2109
2110 // Start listening for new participant connections
2111 asciichat_error_t hstart = session_host_start(session->host_ctx);
2112 if (hstart != ASCIICHAT_OK) {
2113 log_error("Failed to start host after migration: %d", hstart);
2114 return hstart;
2115 }
2116
2117 log_info("Host restarted after migration, listening on port %d", host_port);
2118 }
2119
2120 // Send HOST_ANNOUNCEMENT to ACDS so other participants can reconnect
2121 acip_host_announcement_t announcement = {0};
2122 memcpy(announcement.session_id, session->session_id, 16);
2123 memcpy(announcement.host_id, session->participant_id, 16);
2124 SAFE_STRNCPY(announcement.host_address, "127.0.0.1", sizeof(announcement.host_address));
2125 // TODO: Use actual determined host address instead of 127.0.0.1
2126 announcement.host_port = host_port;
2127 announcement.connection_type = ACIP_CONNECTION_TYPE_DIRECT_PUBLIC;
2128 // TODO: Use actual connection type based on NAT quality
2129
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);
2134 }
2135
2136 // Mark migration complete
2138
2139 // Transition to ACTIVE state (host is ready)
2140 set_state(session, DISCOVERY_STATE_ACTIVE);
2141
2142 return ASCIICHAT_OK;
2143}
asciichat_error_t session_host_start(session_host_t *host)
Definition host.c:797
session_host_t * session_host_create(const session_host_config_t *config)
Definition host.c:166
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.
Definition packet.c:288
@ MIGRATION_STATE_COMPLETE
Failover complete, call resumed.
Definition session.h:89
@ DISCOVERY_STATE_ACTIVE
Session active (call in progress)
Definition session.h:125
session_host_t * host_ctx
Definition session.h:194
socket_t acds_socket
Definition session.h:152
uint8_t participant_id[16]
Definition session.h:141
migration_ctx_t migration
Definition session.h:176
uint8_t session_id[16]
Definition session.h:140
migration_state_t state
Current migration state.
Definition session.h:96

References discovery_session_t::acds_socket, DISCOVERY_STATE_ACTIVE, discovery_session_t::host_ctx, discovery_session_t::is_host, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, packet_send(), discovery_session_t::participant_id, session_host_create(), session_host_start(), discovery_session_t::session_id, and migration_ctx_t::state.

Referenced by discovery_session_handle_host_disconnect().

◆ discovery_session_check_host_alive()

asciichat_error_t discovery_session_check_host_alive ( discovery_session_t session)

Detect host disconnect (check connection status)

Parameters
sessionSession context
Returns
ASCIICHAT_OK if host alive, error if disconnected

Definition at line 1957 of file src/discovery/session.c.

1957 {
1958 if (!session || session->is_host) {
1959 // We are the host, so "host" is always alive
1960 return ASCIICHAT_OK;
1961 }
1962
1963 // For WebRTC sessions, check the WebRTC transport instead of participant_ctx
1964 if (session->session_type == SESSION_TYPE_WEBRTC) {
1965 // If we have a WebRTC transport and it's marked as ready, host is alive
1966 if (session->webrtc_transport_ready) {
1967 return ASCIICHAT_OK;
1968 }
1969 // WebRTC transport not ready yet - might still be connecting
1970 return ERROR_NETWORK;
1971 }
1972
1973 // For TCP sessions, check participant context
1974 // If we don't have a participant context yet, we're not connected
1975 if (!session->participant_ctx) {
1976 return ERROR_NETWORK;
1977 }
1978
1979 // Check migration state - if we're already migrating, host is definitely not alive
1980 if (session->migration.state != MIGRATION_STATE_NONE) {
1981 return ERROR_NETWORK; // Already detected disconnect
1982 }
1983
1984 uint64_t now_ms = session_get_current_time_ms();
1985
1986 // Check if we have a ping in flight that timed out
1987 if (session->liveness.ping_in_flight) {
1988 uint64_t ping_age_ms = now_ms - session->liveness.last_ping_sent_ms;
1989 if (ping_age_ms > session->liveness.timeout_ms) {
1990 // Ping timed out
1991 session->liveness.consecutive_failures++;
1992 session->liveness.ping_in_flight = false;
1993 log_warn("Host ping timeout (attempt %u/%u, age=%llu ms)", session->liveness.consecutive_failures,
1994 session->liveness.max_failures, (unsigned long long)ping_age_ms);
1995
1996 // Check if we've exceeded failure threshold
1997 if (session->liveness.consecutive_failures >= session->liveness.max_failures) {
1998 log_error("Host declared dead after %u consecutive ping failures", session->liveness.consecutive_failures);
1999 return ERROR_NETWORK;
2000 }
2001 }
2002 }
2003
2004 // Check if it's time to send a new ping
2005 uint64_t time_since_last_ping = now_ms - session->liveness.last_ping_sent_ms;
2006 if (!session->liveness.ping_in_flight && time_since_last_ping >= session->liveness.ping_interval_ms) {
2007 // Send ping to host via participant connection
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) {
2012 session->liveness.last_ping_sent_ms = now_ms;
2013 session->liveness.ping_in_flight = true;
2014 log_debug("Sent ping to host (attempt %u/%u)", session->liveness.consecutive_failures + 1,
2015 session->liveness.max_failures);
2016 } else {
2017 log_warn("Failed to send ping to host: %d", result);
2018 session->liveness.consecutive_failures++;
2019 if (session->liveness.consecutive_failures >= session->liveness.max_failures) {
2020 log_error("Host declared dead after %u consecutive send failures", session->liveness.consecutive_failures);
2021 return ERROR_NETWORK;
2022 }
2023 }
2024 }
2025 }
2026
2027 return ASCIICHAT_OK;
2028}
int socket_t
socket_t session_participant_get_socket(session_participant_t *p)
@ MIGRATION_STATE_NONE
No migration in progress.
Definition session.h:86
host_liveness_t liveness
Definition session.h:173
uint8_t session_type
0 = DIRECT_TCP, 1 = WEBRTC
Definition session.h:160
bool webrtc_transport_ready
True when DataChannel is open and transport created.
Definition session.h:180
session_participant_t * participant_ctx
Definition session.h:195
uint32_t consecutive_failures
Number of consecutive ping failures.
Definition session.h:71
uint32_t max_failures
Threshold for triggering migration (default: 3)
Definition session.h:72
uint64_t ping_interval_ms
Time between pings (default: 3000ms)
Definition session.h:73
uint64_t last_ping_sent_ms
Timestamp of last ping sent (monotonic)
Definition session.h:69
bool ping_in_flight
True if waiting for pong.
Definition session.h:75
uint64_t timeout_ms
Timeout for ping response (default: 10000ms)
Definition session.h:74

References host_liveness_t::consecutive_failures, discovery_session_t::is_host, host_liveness_t::last_ping_sent_ms, discovery_session_t::liveness, host_liveness_t::max_failures, discovery_session_t::migration, MIGRATION_STATE_NONE, packet_send(), discovery_session_t::participant_ctx, host_liveness_t::ping_in_flight, host_liveness_t::ping_interval_ms, session_participant_get_socket(), discovery_session_t::session_type, migration_ctx_t::state, host_liveness_t::timeout_ms, and discovery_session_t::webrtc_transport_ready.

Referenced by discovery_session_process().

◆ discovery_session_connect_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)

Parameters
sessionSession context
Returns
ASCIICHAT_OK on success

Definition at line 2145 of file src/discovery/session.c.

2145 {
2146 if (!session) {
2147 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2148 return ERROR_INVALID_PARAM;
2149 }
2150
2151 log_info("Reconnecting to future host: %s:%u (connection type: %u)", session->ring.future_host_address,
2153
2154 // Destroy old participant context if present
2155 if (session->participant_ctx) {
2157 session->participant_ctx = NULL;
2158 }
2159
2160 // Create new participant context for the future host
2161 session_participant_config_t pconfig = {
2162 .address = session->ring.future_host_address,
2163 .port = session->ring.future_host_port,
2164 .enable_audio = true,
2165 .enable_video = true,
2166 .encryption_enabled = true,
2167 };
2168
2169 session->participant_ctx = session_participant_create(&pconfig);
2170 if (!session->participant_ctx) {
2171 log_error("Failed to create participant context for future host");
2172 return ERROR_MEMORY;
2173 }
2174
2175 // Attempt connection to future host
2176 asciichat_error_t pconn = session_participant_connect(session->participant_ctx);
2177 if (pconn != ASCIICHAT_OK) {
2178 log_error("Failed to connect to future host: %d", pconn);
2179 return pconn;
2180 }
2181
2182 log_info("Connected to future host after migration");
2183
2184 // Update our host info with the new future host details
2185 memcpy(session->host_id, session->ring.future_host_id, 16);
2186 SAFE_STRNCPY(session->host_address, session->ring.future_host_address, sizeof(session->host_address));
2187 session->host_port = session->ring.future_host_port;
2188
2189 log_info("Updated host info to: %s:%u (id: %02x%02x...)", session->host_address, session->host_port,
2190 session->host_id[0], session->host_id[1]);
2191
2192 // Mark migration complete (in real implementation, this would be done after successful connection)
2194
2195 // Transition to ACTIVE state (participant is ready)
2196 set_state(session, DISCOVERY_STATE_ACTIVE);
2197
2198 return ASCIICHAT_OK;
2199}
asciichat_error_t session_participant_connect(session_participant_t *p)
void session_participant_destroy(session_participant_t *p)
session_participant_t * session_participant_create(const session_participant_config_t *config)
ring_consensus_t ring
Definition session.h:170
uint16_t host_port
Definition session.h:159
uint8_t host_id[16]
Definition session.h:157
char host_address[64]
Definition session.h:158
uint8_t future_host_id[16]
Who will host if current host dies.
Definition session.h:51
uint8_t future_host_connection_type
acip_connection_type_t (DIRECT, UPNP, STUN, TURN)
Definition session.h:54
uint16_t future_host_port
Port number.
Definition session.h:53
char future_host_address[64]
Where to connect.
Definition session.h:52
char address[BUFFER_SIZE_SMALL]
Server address.
Definition participant.c:45

References session_participant::address, DISCOVERY_STATE_ACTIVE, ring_consensus_t::future_host_address, ring_consensus_t::future_host_connection_type, ring_consensus_t::future_host_id, ring_consensus_t::future_host_port, discovery_session_t::host_address, discovery_session_t::host_id, discovery_session_t::host_port, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, discovery_session_t::participant_ctx, discovery_session_t::ring, session_participant_connect(), session_participant_create(), session_participant_destroy(), and migration_ctx_t::state.

Referenced by discovery_session_handle_host_disconnect().

◆ discovery_session_create()

discovery_session_t * discovery_session_create ( const discovery_config_t config)

Create a new discovery session.

Parameters
configSession configuration
Returns
New session or NULL on error

Definition at line 48 of file src/discovery/session.c.

48 {
49 if (!config) {
50 SET_ERRNO(ERROR_INVALID_PARAM, "config is NULL");
51 return NULL;
52 }
53
54 discovery_session_t *session = SAFE_CALLOC(1, sizeof(discovery_session_t), discovery_session_t *);
55 if (!session) {
56 SET_ERRNO(ERROR_MEMORY, "Failed to allocate discovery session");
57 return NULL;
58 }
59
60 session->state = DISCOVERY_STATE_INIT;
61 session->acds_socket = INVALID_SOCKET_VALUE;
62 session->peer_manager = NULL;
63 session->webrtc_transport_ready = false;
64 session->webrtc_connection_initiated = false;
65 session->webrtc_retry_attempt = 0;
66 session->webrtc_last_attempt_time_ms = 0;
67 session->stun_servers = NULL;
68 session->stun_count = 0;
69 session->turn_servers = NULL;
70 session->turn_count = 0;
71
72 // Initialize host liveness detection
73 session->liveness.last_ping_sent_ms = 0;
75 session->liveness.consecutive_failures = 0;
76 session->liveness.max_failures = 3;
77 session->liveness.ping_interval_ms = 3 * MS_PER_SEC_INT; // Ping every 3 seconds
78 session->liveness.timeout_ms = 10 * MS_PER_SEC_INT; // 10 second timeout
79 session->liveness.ping_in_flight = false;
80
81 log_info("discovery_session_create: After CALLOC - participant_ctx=%p, host_ctx=%p", session->participant_ctx,
82 session->host_ctx);
83
84 // Load or generate identity key
85 char identity_path[256];
86 asciichat_error_t id_result = acds_identity_default_path(identity_path, sizeof(identity_path));
87 if (id_result == ASCIICHAT_OK) {
88 id_result = acds_identity_load(identity_path, session->identity_pubkey, session->identity_seckey);
89 if (id_result != ASCIICHAT_OK) {
90 // File doesn't exist or is corrupted - generate new key
91 log_info("Identity key not found, generating new key");
92 id_result = acds_identity_generate(session->identity_pubkey, session->identity_seckey);
93 if (id_result == ASCIICHAT_OK) {
94 // Save for future use
95 acds_identity_save(identity_path, session->identity_pubkey, session->identity_seckey);
96 }
97 }
98 }
99
100 if (id_result != ASCIICHAT_OK) {
101 log_warn("Failed to load/generate identity key, using zero key");
102 memset(session->identity_pubkey, 0, sizeof(session->identity_pubkey));
103 memset(session->identity_seckey, 0, sizeof(session->identity_seckey));
104 } else {
105 char fingerprint[65];
106 acds_identity_fingerprint(session->identity_pubkey, fingerprint);
107 log_info("Using identity key with fingerprint: %.16s...", fingerprint);
108 }
109
110 // ACDS connection info
111 if (config->acds_address && config->acds_address[0]) {
112 SAFE_STRNCPY(session->acds_address, config->acds_address, sizeof(session->acds_address));
113 } else {
114 SAFE_STRNCPY(session->acds_address, "127.0.0.1", sizeof(session->acds_address));
115 }
116 session->acds_port = config->acds_port > 0 ? config->acds_port : ACIP_DISCOVERY_DEFAULT_PORT;
117
118 // Session string (if joining)
119 if (config->session_string && config->session_string[0]) {
120 SAFE_STRNCPY(session->session_string, config->session_string, sizeof(session->session_string));
121 session->is_initiator = false;
122 } else {
123 session->is_initiator = true;
124 }
125
126 // Callbacks
127 session->on_state_change = config->on_state_change;
128 session->on_session_ready = config->on_session_ready;
129 session->on_error = config->on_error;
130 session->callback_user_data = config->callback_user_data;
132 session->exit_callback_data = config->exit_callback_data;
133
134 log_debug("Discovery session created (initiator=%d, acds=%s:%u)", session->is_initiator, session->acds_address,
135 session->acds_port);
136
137 return session;
138}
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
Definition identity.c:32
asciichat_error_t acds_identity_default_path(char *path_out, size_t path_size)
Definition identity.c:121
asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64])
Definition identity.c:61
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
Definition identity.c:104
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
Definition identity.c:18
@ DISCOVERY_STATE_INIT
Initial state.
Definition session.h:117
void * callback_user_data
Definition session.h:226
const char * acds_address
ACDS address (default: "127.0.0.1")
Definition session.h:213
void(* on_error)(asciichat_error_t error, const char *message, void *user_data)
Definition session.h:225
uint16_t acds_port
ACDS port (default: 27225)
Definition session.h:214
discovery_should_exit_fn should_exit_callback
Definition session.h:229
void * exit_callback_data
Definition session.h:230
void(* on_state_change)(discovery_state_t new_state, void *user_data)
Definition session.h:223
const char * session_string
Session string to join (or NULL to create)
Definition session.h:217
void(* on_session_ready)(const char *session_string, void *user_data)
Definition session.h:224
Discovery session context.
Definition session.h:134
void * callback_user_data
Definition session.h:201
uint64_t webrtc_last_attempt_time_ms
Timestamp of last connection attempt (monotonic time)
Definition session.h:183
uint16_t acds_port
Definition session.h:154
char acds_address[64]
Definition session.h:153
int webrtc_retry_attempt
Current retry attempt number (0 = initial, 1+ = retries)
Definition session.h:182
discovery_state_t state
Definition session.h:136
void * exit_callback_data
Definition session.h:205
uint8_t identity_pubkey[32]
Ed25519 public key for this participant.
Definition session.h:148
turn_server_t * turn_servers
TURN servers from ACDS.
Definition session.h:190
size_t stun_count
Number of STUN servers.
Definition session.h:189
void(* on_error)(asciichat_error_t error, const char *message, void *user_data)
Definition session.h:200
uint8_t identity_seckey[64]
Ed25519 secret key for signing.
Definition session.h:149
stun_server_t * stun_servers
STUN servers from ACDS.
Definition session.h:188
void(* on_session_ready)(const char *session_string, void *user_data)
Definition session.h:199
char session_string[SESSION_STRING_BUFFER_SIZE]
Definition session.h:143
discovery_should_exit_fn should_exit_callback
Definition session.h:204
size_t turn_count
Number of TURN servers.
Definition session.h:191
bool webrtc_connection_initiated
True when we've called webrtc_peer_manager_connect()
Definition session.h:181
void(* on_state_change)(discovery_state_t new_state, void *user_data)
Definition session.h:198
struct webrtc_peer_manager * peer_manager
WebRTC peer connection manager (NULL for TCP sessions)
Definition session.h:179
uint64_t last_pong_received_ms
Timestamp of last pong received (monotonic)
Definition session.h:70

References discovery_session_t::acds_address, discovery_config_t::acds_address, acds_identity_default_path(), acds_identity_fingerprint(), acds_identity_generate(), acds_identity_load(), acds_identity_save(), discovery_session_t::acds_port, discovery_config_t::acds_port, discovery_session_t::acds_socket, discovery_session_t::callback_user_data, discovery_config_t::callback_user_data, host_liveness_t::consecutive_failures, DISCOVERY_STATE_INIT, discovery_session_t::exit_callback_data, discovery_config_t::exit_callback_data, discovery_session_t::host_ctx, discovery_session_t::identity_pubkey, discovery_session_t::identity_seckey, discovery_session_t::is_initiator, host_liveness_t::last_ping_sent_ms, host_liveness_t::last_pong_received_ms, discovery_session_t::liveness, host_liveness_t::max_failures, discovery_session_t::on_error, discovery_config_t::on_error, discovery_session_t::on_session_ready, discovery_config_t::on_session_ready, discovery_session_t::on_state_change, discovery_config_t::on_state_change, discovery_session_t::participant_ctx, discovery_session_t::peer_manager, host_liveness_t::ping_in_flight, host_liveness_t::ping_interval_ms, discovery_session_t::session_string, discovery_config_t::session_string, discovery_session_t::should_exit_callback, discovery_config_t::should_exit_callback, discovery_session_t::state, discovery_session_t::stun_count, discovery_session_t::stun_servers, host_liveness_t::timeout_ms, discovery_session_t::turn_count, discovery_session_t::turn_servers, discovery_session_t::webrtc_connection_initiated, discovery_session_t::webrtc_last_attempt_time_ms, discovery_session_t::webrtc_retry_attempt, and discovery_session_t::webrtc_transport_ready.

Referenced by discovery_main().

◆ discovery_session_destroy()

void discovery_session_destroy ( discovery_session_t session)

Destroy discovery session and free resources.

Parameters
sessionSession to destroy

Definition at line 140 of file src/discovery/session.c.

140 {
141 if (!session) {
142 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
143 return;
144 }
145
146 // Close ACDS connection
147 if (session->acds_socket != INVALID_SOCKET_VALUE) {
148 socket_close(session->acds_socket);
149 session->acds_socket = INVALID_SOCKET_VALUE;
150 }
151
152 // Destroy host context if active
153 if (session->host_ctx) {
155 session->host_ctx = NULL;
156 }
157
158 // Destroy participant context if active
159 if (session->participant_ctx) {
161 session->participant_ctx = NULL;
162 }
163
164 // Clean up WebRTC peer manager (NEW)
165 if (session->peer_manager) {
167 session->peer_manager = NULL;
168 }
169
170 // Clean up STUN/TURN server arrays (NEW)
171 if (session->stun_servers) {
172 SAFE_FREE(session->stun_servers);
173 session->stun_servers = NULL;
174 session->stun_count = 0;
175 }
176
177 if (session->turn_servers) {
178 SAFE_FREE(session->turn_servers);
179 session->turn_servers = NULL;
180 session->turn_count = 0;
181 }
182
183 SAFE_FREE(session);
184}
void session_host_destroy(session_host_t *host)
Definition host.c:222
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)

References discovery_session_t::acds_socket, discovery_session_t::host_ctx, discovery_session_t::participant_ctx, discovery_session_t::peer_manager, session_host_destroy(), session_participant_destroy(), discovery_session_t::stun_count, discovery_session_t::stun_servers, discovery_session_t::turn_count, discovery_session_t::turn_servers, and webrtc_peer_manager_destroy().

Referenced by discovery_main().

◆ discovery_session_get_future_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.

Parameters
sessionSession context
out_idOutput: Future host participant ID
out_addressOutput: Future host address
out_portOutput: Future host port
out_connection_typeOutput: Connection type
Returns
ASCIICHAT_OK if future host known, error otherwise

Definition at line 2201 of file src/discovery/session.c.

2203 {
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;
2207 }
2208
2209 if (session->ring.future_host_id[0] == 0) {
2210 SET_ERRNO(ERROR_NOT_FOUND, "Future host not elected yet");
2211 return ERROR_NOT_FOUND;
2212 }
2213
2214 memcpy(out_id, session->ring.future_host_id, 16);
2215 SAFE_STRNCPY(out_address, session->ring.future_host_address, 64);
2216 *out_port = session->ring.future_host_port;
2217 *out_connection_type = session->ring.future_host_connection_type;
2218
2219 return ASCIICHAT_OK;
2220}

References ring_consensus_t::future_host_address, ring_consensus_t::future_host_connection_type, ring_consensus_t::future_host_id, ring_consensus_t::future_host_port, and discovery_session_t::ring.

◆ discovery_session_get_host()

session_host_t * discovery_session_get_host ( discovery_session_t session)

Get host context (if we are host)

Parameters
sessionSession context
Returns
Host context or NULL

Definition at line 1805 of file src/discovery/session.c.

1805 {
1806 if (!session) {
1807 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
1808 return NULL;
1809 }
1810 return session->host_ctx;
1811}

References discovery_session_t::host_ctx.

◆ discovery_session_get_participant()

session_participant_t * discovery_session_get_participant ( discovery_session_t session)

Get participant context (if we are participant)

Parameters
sessionSession context
Returns
Participant context or NULL

Definition at line 1813 of file src/discovery/session.c.

1813 {
1814 if (!session) {
1815 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
1816 return NULL;
1817 }
1818 return session->participant_ctx;
1819}

References discovery_session_t::participant_ctx.

◆ discovery_session_get_state()

discovery_state_t discovery_session_get_state ( const discovery_session_t session)

Get current session state.

Parameters
sessionSession context
Returns
Current state

Definition at line 1773 of file src/discovery/session.c.

1773 {
1774 if (!session) {
1775 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
1777 }
1778 return session->state;
1779}
@ DISCOVERY_STATE_FAILED
Session failed.
Definition session.h:127

References DISCOVERY_STATE_FAILED, and discovery_session_t::state.

◆ discovery_session_get_string()

const char * discovery_session_get_string ( const discovery_session_t session)

Get session string.

Parameters
sessionSession context
Returns
Session string or NULL if not yet assigned

Definition at line 1789 of file src/discovery/session.c.

1789 {
1790 if (!session || session->session_string[0] == '\0') {
1791 SET_ERRNO(ERROR_INVALID_PARAM, "null session or session string is empty");
1792 return NULL;
1793 }
1794 return session->session_string;
1795}

References discovery_session_t::session_string.

◆ discovery_session_handle_host_disconnect()

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.

Parameters
sessionSession context
disconnect_reasonReason code for disconnect
Returns
ASCIICHAT_OK on successful failover

Definition at line 2030 of file src/discovery/session.c.

2030 {
2031 if (!session) {
2032 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2033 return ERROR_INVALID_PARAM;
2034 }
2035
2036 log_warn("Host disconnect detected: reason=%u", disconnect_reason);
2037
2038 // Initialize migration context
2040 session->migration.detection_time_ms = session_get_current_time_ms();
2041 memcpy(session->migration.last_host_id, session->host_id, 16);
2042 session->migration.disconnect_reason = disconnect_reason;
2043
2044 // Notify ACDS of host loss (lightweight notification, no NAT data exchanged!)
2045 acip_host_lost_t host_lost = {0};
2046 memcpy(host_lost.session_id, session->session_id, 16);
2047 memcpy(host_lost.participant_id, session->participant_id, 16);
2048 memcpy(host_lost.last_host_id, session->host_id, 16);
2049 host_lost.disconnect_reason = disconnect_reason;
2050 host_lost.disconnect_time_ms = session->migration.detection_time_ms;
2051
2052 packet_send(session->acds_socket, PACKET_TYPE_ACIP_HOST_LOST, &host_lost, sizeof(host_lost));
2053
2054 // Check if we have a pre-elected future host (from last 5-minute ring round)
2055 if (session->ring.future_host_id[0] == 0) {
2056 // No future host known! This shouldn't happen if we just received FUTURE_HOST_ELECTED
2057 // within the last 5 minutes. Fall back to treating this as fatal.
2058 log_error("No future host pre-elected! Session cannot recover from host disconnect.");
2060 return ERROR_NETWORK; // Session should end
2061 }
2062
2063 // **SIMPLIFIED**: No metrics exchange needed! Future host was already elected 5 minutes ago.
2064 // Just failover to the pre-elected host.
2066
2067 // Check if I am the future host
2068 if (session->ring.am_future_host) {
2069 // I become the new host immediately! (no election delay!)
2070 log_info("Becoming host (I'm the pre-elected future host)");
2071 return discovery_session_become_host(session);
2072 } else {
2073 // Connect to pre-elected future host (address already stored!)
2074 log_info("Connecting to pre-elected future host: %s:%u", session->ring.future_host_address,
2075 session->ring.future_host_port);
2077 }
2078}
@ MIGRATION_STATE_DETECTED
Host disconnect detected.
Definition session.h:87
@ MIGRATION_STATE_FAILOVER
Failing over to pre-elected future host.
Definition session.h:88
asciichat_error_t discovery_session_connect_to_future_host(discovery_session_t *session)
Connect to pre-elected future host (called when NOT future host)
asciichat_error_t discovery_session_become_host(discovery_session_t *session)
Become the host (called when elected as future host)
uint64_t detection_time_ms
When host disconnect detected (Unix ms)
Definition session.h:97
uint32_t disconnect_reason
Reason for disconnect (from HOST_LOST packet)
Definition session.h:99
uint8_t last_host_id[16]
The host that died.
Definition session.h:98
bool am_future_host
Am I the elected future host?
Definition session.h:55

References discovery_session_t::acds_socket, ring_consensus_t::am_future_host, migration_ctx_t::detection_time_ms, migration_ctx_t::disconnect_reason, discovery_session_become_host(), discovery_session_connect_to_future_host(), ring_consensus_t::future_host_address, ring_consensus_t::future_host_id, ring_consensus_t::future_host_port, discovery_session_t::host_id, migration_ctx_t::last_host_id, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, MIGRATION_STATE_DETECTED, MIGRATION_STATE_FAILOVER, packet_send(), discovery_session_t::participant_id, discovery_session_t::ring, discovery_session_t::session_id, and migration_ctx_t::state.

Referenced by discovery_session_process().

◆ discovery_session_init_ring()

asciichat_error_t discovery_session_init_ring ( discovery_session_t session)

Initialize ring consensus state.

Simplified for host-mediated architecture - just initializes timing, no participant list needed (host runs the election).

Parameters
sessionSession context
Returns
ASCIICHAT_OK on success

Definition at line 1912 of file src/discovery/session.c.

1912 {
1913 if (!session) {
1914 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1915 return ERROR_INVALID_PARAM;
1916 }
1917
1918 // Simplified for new architecture: just initialize timing
1919 // No participant list needed - host runs the election
1920 session->ring.last_ring_round_ms = session_get_current_time_ms();
1921 session->ring.am_future_host = false;
1922 memset(session->ring.future_host_id, 0, 16);
1923
1924 log_info("Ring consensus initialized (host-mediated model)");
1925 return ASCIICHAT_OK;
1926}
uint64_t last_ring_round_ms
When host last ran election (for 5-min timer)
Definition session.h:59

References ring_consensus_t::am_future_host, ring_consensus_t::future_host_id, ring_consensus_t::last_ring_round_ms, and discovery_session_t::ring.

◆ discovery_session_is_active()

bool discovery_session_is_active ( const discovery_session_t session)

Check if session is active (call in progress)

Parameters
sessionSession context
Returns
True if active

Definition at line 1781 of file src/discovery/session.c.

1781 {
1782 if (!session) {
1783 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
1784 return false;
1785 }
1786 return session->state == DISCOVERY_STATE_ACTIVE;
1787}

References DISCOVERY_STATE_ACTIVE, and discovery_session_t::state.

◆ discovery_session_is_future_host()

bool discovery_session_is_future_host ( const discovery_session_t session)

Check if we are the future host.

Parameters
sessionSession context
Returns
True if we will be host if current host dies

Definition at line 2222 of file src/discovery/session.c.

2222 {
2223 if (!session) {
2224 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2225 return false;
2226 }
2227 return session->ring.am_future_host;
2228}

References ring_consensus_t::am_future_host, and discovery_session_t::ring.

◆ discovery_session_is_host()

bool discovery_session_is_host ( const discovery_session_t session)

Check if we are the host.

Parameters
sessionSession context
Returns
True if we are hosting

Definition at line 1797 of file src/discovery/session.c.

1797 {
1798 if (!session) {
1799 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
1800 return false;
1801 }
1802 return session->is_host;
1803}

References discovery_session_t::is_host.

◆ discovery_session_process()

asciichat_error_t discovery_session_process ( discovery_session_t session,
int64_t  timeout_ns 
)

Process session events (call in main loop)

Handles incoming ACDS messages, negotiation, and state transitions.

Parameters
sessionSession context
timeout_nsMax time to wait for events in nanoseconds (0 = non-blocking)
Returns
ASCIICHAT_OK on success, error on failure

Definition at line 1256 of file src/discovery/session.c.

1256 {
1257 if (!session) {
1258 return SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1259 }
1260
1261 // Handle state-specific processing
1262 switch (session->state) {
1264 // Wait for PARTICIPANT_JOINED notification from ACDS
1265 if (session->acds_socket != INVALID_SOCKET_VALUE && timeout_ns > 0) {
1266 // Set socket timeout
1267 if (socket_set_timeout(session->acds_socket, timeout_ns) == 0) {
1268 packet_type_t type;
1269 void *data = NULL;
1270 size_t len = 0;
1271
1272 // Try to receive a packet (non-blocking with timeout)
1273 asciichat_error_t recv_result = packet_receive(session->acds_socket, &type, &data, &len);
1274 if (recv_result == ASCIICHAT_OK) {
1275 if (type == PACKET_TYPE_ACIP_PARTICIPANT_JOINED) {
1276 log_info("Peer joined session, transitioning to negotiation");
1277 set_state(session, DISCOVERY_STATE_NEGOTIATING);
1278 POOL_FREE(data, len);
1279 } else {
1280 // Got some other packet, handle it or ignore
1281 log_debug("Received packet type %d while waiting for peer", type);
1282 POOL_FREE(data, len);
1283 }
1284 } else if (recv_result == ERROR_NETWORK_TIMEOUT) {
1285 // Timeout is normal, just return
1286 } else {
1287 log_debug("Packet receive error while waiting for peer: %d", recv_result);
1288 }
1289 }
1290 } else {
1291 // Fallback to sleep if no timeout or invalid socket
1292 platform_sleep_us(timeout_ns / 1000);
1293 }
1294 break;
1295 }
1296
1298 // Handle NAT negotiation
1299 if (session->negotiate.state == NEGOTIATE_STATE_INIT) {
1300 // Initialize negotiation
1301 negotiate_init(&session->negotiate, session->session_id, session->participant_id, session->is_initiator);
1302 log_info("Starting NAT negotiation (initiator=%d)", session->is_initiator);
1303
1304 // Send our NETWORK_QUALITY to ACDS immediately (let peer receive it)
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);
1308 // Don't fail - we'll try again next iteration
1309 }
1310 }
1311
1312 // Try to receive peer's NETWORK_QUALITY from ACDS (non-blocking with short timeout)
1313 if (!session->negotiate.peer_quality_received) {
1314 asciichat_error_t recv_result = receive_network_quality_from_acds(session);
1315 if (recv_result == ASCIICHAT_OK) {
1316 // Got peer quality - proceed to election
1317 log_info("Received peer NETWORK_QUALITY, proceeding to host election");
1318 } else if (recv_result != ERROR_NETWORK_TIMEOUT) {
1319 // Real error (not just timeout)
1320 log_debug("Error receiving NETWORK_QUALITY: %d (this might be normal if peer not ready)", recv_result);
1321 }
1322 }
1323
1324 // Check if we have both qualities and can determine host
1326 // Both sides have exchanged NAT quality - determine the winner
1327 log_info("Both NAT qualities received, determining host...");
1328 asciichat_error_t result = negotiate_determine_result(&session->negotiate);
1329 if (result == ASCIICHAT_OK) {
1330 // Store result
1331 session->is_host = session->negotiate.we_are_host;
1332
1333 if (session->is_host) {
1334 // We won the negotiation - become the host
1335 log_info("🏠 NAT negotiation complete: WE ARE HOST (tier:%d vs tier:%d)",
1338 memcpy(session->host_id, session->participant_id, 16);
1339 SAFE_STRNCPY(session->host_address, session->negotiate.host_address, sizeof(session->host_address));
1340 session->host_port = session->negotiate.host_port;
1341 set_state(session, DISCOVERY_STATE_STARTING_HOST);
1342 } else {
1343 // They won - we'll be a participant
1344 log_info("👤 NAT negotiation complete: THEY ARE HOST at %s:%u (tier:%d vs tier:%d)",
1345 session->negotiate.host_address, session->negotiate.host_port,
1348 SAFE_STRNCPY(session->host_address, session->negotiate.host_address, sizeof(session->host_address));
1349 session->host_port = session->negotiate.host_port;
1350 set_state(session, DISCOVERY_STATE_CONNECTING_HOST);
1351 }
1352 } else {
1353 log_error("Failed to determine negotiation result: %d", result);
1354 set_error(session, result, "Host election failed");
1355 }
1356 } else {
1357 // Still waiting for peer quality - sleep briefly to avoid busy loop
1358 platform_sleep_us(timeout_ns / 1000);
1359 }
1360 break;
1361 }
1362
1364 // Start hosting
1365 if (!session->host_ctx) {
1366 // Create host context with configured port
1367 int host_port = GET_OPTION(port);
1368
1369 session_host_config_t hconfig = {
1370 .port = host_port,
1371 .ipv4_address = "0.0.0.0",
1372 .max_clients = 32,
1373 .encryption_enabled = true,
1374 };
1375
1376 session->host_ctx = session_host_create(&hconfig);
1377 if (!session->host_ctx) {
1378 log_error("Failed to create host context");
1379 set_error(session, ERROR_MEMORY, "Failed to create host context");
1380 break;
1381 }
1382
1383 // Start listening
1384 asciichat_error_t hstart = session_host_start(session->host_ctx);
1385 if (hstart != ASCIICHAT_OK) {
1386 log_error("Failed to start host: %d", hstart);
1387 set_error(session, hstart, "Failed to start host");
1388 break;
1389 }
1390
1391 log_info("Host started, listening for connections");
1392 }
1393
1394 // Notify listeners that we're ready
1395 if (session->on_session_ready) {
1396 session->on_session_ready(session->session_string, session->callback_user_data);
1397 }
1398
1399 set_state(session, DISCOVERY_STATE_ACTIVE);
1400 break;
1401 }
1402
1404 // Connect to host as participant
1405
1406 // For WebRTC sessions, wait for DataChannel to open
1407 if (session->session_type == SESSION_TYPE_WEBRTC) {
1408 // Check if transport is ready (DataChannel opened)
1409 if (session->webrtc_transport_ready) {
1410 log_info("WebRTC DataChannel established, transitioning to ACTIVE state");
1411 set_state(session, DISCOVERY_STATE_ACTIVE);
1412 break;
1413 }
1414
1415 // If not ready and we haven't initiated connection yet, initiate it
1416 if (session->peer_manager && !session->webrtc_connection_initiated) {
1417 log_info("Initiating WebRTC connection to host (attempt %d/%d)...", session->webrtc_retry_attempt + 1,
1418 GET_OPTION(webrtc_reconnect_attempts) + 1);
1419
1420 // Initiate connection (generates offer, triggers SDP exchange)
1421 asciichat_error_t conn_result =
1422 webrtc_peer_manager_connect(session->peer_manager, session->session_id, session->host_id);
1423
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");
1427 break;
1428 }
1429
1430 log_info("WebRTC connection initiated, waiting for DataChannel...");
1431 session->webrtc_connection_initiated = true;
1433 }
1434
1435 // Check for ICE gathering timeout on all peers
1436 if (session->webrtc_connection_initiated && session->peer_manager) {
1437 int timeout_ms = GET_OPTION(webrtc_ice_timeout_ms);
1438 int timed_out_count = webrtc_peer_manager_check_gathering_timeouts(session->peer_manager, timeout_ms);
1439
1440 if (timed_out_count > 0) {
1441 log_error("ICE gathering timeout: %d peer(s) failed to gather candidates within %dms", timed_out_count,
1442 timeout_ms);
1443
1444 // Check if we have retries remaining
1445 int max_attempts = GET_OPTION(webrtc_reconnect_attempts);
1446 if (session->webrtc_retry_attempt < max_attempts) {
1447 // Calculate exponential backoff delay
1448 uint32_t backoff_ms = calculate_backoff_delay_ms(session->webrtc_retry_attempt);
1449
1450 log_warn("WebRTC connection attempt %d/%d failed, retrying in %ums...", session->webrtc_retry_attempt + 1,
1451 max_attempts + 1, backoff_ms);
1452
1453 // Wait for backoff period
1454 platform_sleep_ms(backoff_ms);
1455
1456 // Destroy old peer manager
1458 session->peer_manager = NULL;
1459
1460 // Re-initialize peer manager for retry
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");
1465
1466 // If --prefer-webrtc is set, this is a fatal error
1467 if (GET_OPTION(prefer_webrtc)) {
1468 log_fatal("WebRTC connection failed and --prefer-webrtc is set - exiting");
1469 set_state(session, DISCOVERY_STATE_FAILED);
1470 return ERROR_NETWORK_TIMEOUT;
1471 }
1472
1473 break; // Exit this case to allow fallback to TCP
1474 }
1475
1476 // Reset connection state for retry
1477 session->webrtc_connection_initiated = false;
1478 session->webrtc_retry_attempt++;
1479
1480 log_info("WebRTC peer manager re-initialized for attempt %d/%d", session->webrtc_retry_attempt + 1,
1481 max_attempts + 1);
1482 } else {
1483 // No more retries - connection failed
1484 log_error("WebRTC connection failed after %d attempts (timeout: %dms)", max_attempts + 1, timeout_ms);
1485
1486 // Set error state - WebRTC connection failed
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);
1491
1492 // If --prefer-webrtc is set, this is a fatal error (no TCP fallback)
1493 if (GET_OPTION(prefer_webrtc)) {
1494 log_fatal("WebRTC connection failed and --prefer-webrtc is set - exiting");
1495 set_state(session, DISCOVERY_STATE_FAILED);
1496 return ERROR_NETWORK_TIMEOUT;
1497 }
1498
1499 break; // Exit this case to allow fallback to TCP
1500 }
1501 }
1502 }
1503
1504 // Poll ACDS for WebRTC signaling messages (SDP answer, ICE candidates)
1505 // while waiting for DataChannel to open
1506 if (session->acds_socket != INVALID_SOCKET_VALUE) {
1507 // Check if data available (non-blocking with timeout_ns)
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;
1511
1512 fd_set readfds;
1513 FD_ZERO(&readfds);
1514 FD_SET(session->acds_socket, &readfds);
1515
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)) {
1518 // Data available to read
1519 packet_type_t type;
1520 void *data = NULL;
1521 size_t len = 0;
1522
1523 asciichat_error_t recv_result = packet_receive(session->acds_socket, &type, &data, &len);
1524
1525 if (recv_result == ASCIICHAT_OK) {
1526 // Dispatch to appropriate handler
1527 handle_acds_webrtc_packet(session, type, data, len);
1528 POOL_FREE(data, len);
1529 } else {
1530 log_debug("Failed to receive ACDS packet: %d", recv_result);
1531 }
1532 }
1533 }
1534
1535 // Stay in CONNECTING_HOST until DataChannel opens (webrtc_transport_ready becomes true)
1536 // The discovery_on_transport_ready callback will set webrtc_transport_ready=true
1537 break;
1538 }
1539
1540 if (!session->participant_ctx) {
1541 // Create participant context for this session
1542 session_participant_config_t pconfig = {
1543 .address = session->host_address,
1544 .port = session->host_port,
1545 .enable_audio = true,
1546 .enable_video = true,
1547 .encryption_enabled = true,
1548 };
1549
1550 session->participant_ctx = session_participant_create(&pconfig);
1551 if (!session->participant_ctx) {
1552 log_error("Failed to create participant context");
1553 set_error(session, ERROR_MEMORY, "Failed to create participant context");
1554 break;
1555 }
1556
1557 // Attempt connection
1558 asciichat_error_t pconn = session_participant_connect(session->participant_ctx);
1559 if (pconn != ASCIICHAT_OK) {
1560 log_error("Failed to connect as participant: %d", pconn);
1561 // Stay in this state, will retry on next process() call
1562 break;
1563 }
1564
1565 log_info("Connected to host, performing crypto handshake...");
1566
1567 // Perform crypto handshake with server
1568 // Get the socket from participant context
1569 socket_t participant_socket = session_participant_get_socket(session->participant_ctx);
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");
1574 break;
1575 }
1576
1577 // Step 1: Send client protocol version
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); // Protocol version 1
1581 client_version.protocol_revision = HOST_TO_NET_U16(0); // Revision 0
1582 client_version.supports_encryption = 1; // We support encryption
1583 client_version.compression_algorithms = 0;
1584 client_version.compression_threshold = 0;
1585 client_version.feature_flags = 0;
1586
1587 int result = send_protocol_version_packet(participant_socket, &client_version);
1588 if (result != 0) {
1589 log_error("Failed to send protocol version to server");
1590 set_error(session, ERROR_NETWORK, "Failed to send protocol version");
1592 break;
1593 }
1594
1595 // Step 2: Receive server 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;
1600
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);
1604 if (payload) {
1605 buffer_pool_free(NULL, payload, payload_len);
1606 }
1607 set_error(session, ERROR_NETWORK, "Failed to receive protocol version from server");
1609 break;
1610 }
1611
1612 if (payload_len != sizeof(protocol_version_packet_t)) {
1613 log_error("Invalid protocol version packet size: %zu", payload_len);
1614 buffer_pool_free(NULL, payload, payload_len);
1615 set_error(session, ERROR_NETWORK, "Invalid protocol version packet");
1617 break;
1618 }
1619
1620 protocol_version_packet_t server_version;
1621 memcpy(&server_version, payload, sizeof(protocol_version_packet_t));
1622 buffer_pool_free(NULL, payload, payload_len);
1623
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");
1628 break;
1629 }
1630
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));
1633
1634 // Step 3: Send crypto capabilities
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); // KEX_ALGO_X25519
1638 client_caps.supported_auth_algorithms = HOST_TO_NET_U16(0x0003); // AUTH_ALGO_ED25519 | AUTH_ALGO_NONE
1639 client_caps.supported_cipher_algorithms = HOST_TO_NET_U16(0x0001); // CIPHER_ALGO_XSALSA20_POLY1305
1640 client_caps.requires_verification = 0;
1641 client_caps.preferred_kex = 0x0001; // KEX_ALGO_X25519
1642 client_caps.preferred_auth = 0x0001; // AUTH_ALGO_ED25519
1643 client_caps.preferred_cipher = 0x0001; // CIPHER_ALGO_XSALSA20_POLY1305
1644
1645 result = send_crypto_capabilities_packet(participant_socket, &client_caps);
1646 if (result != 0) {
1647 log_error("Failed to send crypto capabilities");
1648 set_error(session, ERROR_NETWORK, "Failed to send crypto capabilities");
1650 break;
1651 }
1652
1653 log_info("Crypto handshake initiated successfully");
1654 log_warn("*** Connected to host as participant - transitioning to ACTIVE ***");
1655 }
1656
1657 // Check if connection is established
1658 bool ctx_valid = session->participant_ctx != NULL;
1659 bool is_connected = ctx_valid ? session_participant_is_connected(session->participant_ctx) : false;
1660 log_debug("discovery_session_process: CONNECTING_HOST - participant_ctx=%p, ctx_valid=%d, is_connected=%d",
1661 session->participant_ctx, ctx_valid, is_connected);
1662
1663 if (ctx_valid && !is_connected) {
1664 // Log more details about why it's not connected
1665 log_debug("discovery_session_process: CONNECTING_HOST - context exists but not connected yet");
1666 }
1667
1668 if (is_connected) {
1669 log_info("Participant connection confirmed, transitioning to ACTIVE state");
1670 set_state(session, DISCOVERY_STATE_ACTIVE);
1671 } else {
1672 log_debug("discovery_session_process: CONNECTING_HOST - awaiting connection establishment (ctx_valid=%d, "
1673 "is_connected=%d)",
1674 ctx_valid, is_connected);
1675 }
1676 break;
1677 }
1678
1680 // Receive and handle ACDS packets for WebRTC signaling (NEW)
1681 if (session->session_type == SESSION_TYPE_WEBRTC && session->acds_socket != INVALID_SOCKET_VALUE) {
1682 // Use non-blocking polling to check for incoming ACDS packets
1683 struct pollfd pfd = {.fd = session->acds_socket, .events = POLLIN, .revents = 0};
1684
1685 int poll_result = socket_poll(&pfd, 1, 0); // Non-blocking poll (timeout=0)
1686
1687 if (poll_result > 0 && (pfd.revents & POLLIN)) {
1688 // Data available to read
1689 packet_type_t type;
1690 void *data = NULL;
1691 size_t len = 0;
1692
1693 asciichat_error_t recv_result = packet_receive(session->acds_socket, &type, &data, &len);
1694
1695 if (recv_result == ASCIICHAT_OK) {
1696 // Dispatch to appropriate handler
1697 handle_acds_webrtc_packet(session, type, data, len);
1698 POOL_FREE(data, len);
1699 }
1700 }
1701 }
1702
1703 // Session is active - check for host disconnect
1704 if (!session->is_host) {
1705 // We are a participant - check if host is still alive
1706 asciichat_error_t host_check = discovery_session_check_host_alive(session);
1707 if (host_check != ASCIICHAT_OK) {
1708 // Host is down! Trigger automatic failover to pre-elected future host
1709 log_warn("Host disconnect detected during ACTIVE state");
1710
1711 // If --prefer-webrtc is set, WebRTC failure is fatal (no TCP fallback)
1712 const options_t *opts = options_get();
1713 if (opts && opts->prefer_webrtc) {
1714 log_fatal("WebRTC connection failed and --prefer-webrtc is set - exiting");
1715 set_state(session, DISCOVERY_STATE_FAILED);
1716 session->error = ERROR_NETWORK;
1717 return ERROR_NETWORK;
1718 }
1719
1720 set_state(session, DISCOVERY_STATE_MIGRATING);
1721 // Reason code: 1 = timeout/disconnect detected during ACTIVE session
1723 }
1724 }
1725 platform_sleep_us(timeout_ns / 1000);
1726 break;
1727
1729 // Host migration in progress
1730 // Check if migration has timed out (if it takes >30 seconds, give up)
1731 if (session->migration.state == MIGRATION_STATE_COMPLETE) {
1732 // Migration completed successfully
1733 set_state(session, DISCOVERY_STATE_ACTIVE);
1734 log_info("Migration complete, session resumed");
1735 } else if (session_get_current_time_ms() - session->migration.detection_time_ms > 30 * MS_PER_SEC_INT) {
1736 // Migration timed out after 30 seconds
1737 log_error("Host migration timeout - session cannot recover");
1738 set_state(session, DISCOVERY_STATE_FAILED);
1739 session->error = ERROR_NETWORK;
1740 }
1741 platform_sleep_us(timeout_ns / 1000);
1742 break;
1743
1744 default:
1745 SET_ERRNO(ERROR_INVALID_STATE, "Invalid session state");
1746 break;
1747 }
1748
1749 return ASCIICHAT_OK;
1750}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
int nat_compute_tier(const nat_quality_t *quality)
Compute NAT tier for host selection (0=best, 4=worst)
Definition nat.c:37
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.
Definition negotiate.c:17
asciichat_error_t negotiate_determine_result(negotiate_ctx_t *ctx)
Determine negotiation result.
Definition negotiate.c:111
@ NEGOTIATE_STATE_INIT
Initial state.
Definition negotiate.h:27
@ NEGOTIATE_STATE_COMPARING
Comparing qualities.
Definition negotiate.h:30
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
Definition packet.c:1013
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.
Definition packet.c:348
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:766
int send_crypto_capabilities_packet(socket_t sockfd, const crypto_capabilities_packet_t *caps)
Send crypto capabilities packet.
Definition packet.c:1027
bool session_participant_is_connected(session_participant_t *p)
void session_participant_disconnect(session_participant_t *p)
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)
void platform_sleep_us(unsigned int us)
uint64_t platform_get_monotonic_time_us(void)
void platform_sleep_ms(unsigned int ms)
const options_t * options_get(void)
Definition rcu.c:347
@ DISCOVERY_STATE_MIGRATING
Host migration in progress.
Definition session.h:126
@ DISCOVERY_STATE_NEGOTIATING
NAT negotiation in progress.
Definition session.h:122
@ DISCOVERY_STATE_CONNECTING_HOST
Connecting to host as participant.
Definition session.h:124
@ DISCOVERY_STATE_WAITING_PEER
Waiting for peer to join (initiator)
Definition session.h:121
@ DISCOVERY_STATE_STARTING_HOST
Starting as host.
Definition session.h:123
int socket_set_timeout(socket_t sock, uint64_t timeout_ns)
Set socket receive and send timeouts.
Definition socket.c:82
asciichat_error_t discovery_session_check_host_alive(discovery_session_t *session)
Detect host disconnect (check connection status)
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.
#define false
Definition stdbool.h:24
asciichat_error_t error
Definition session.h:137
negotiate_ctx_t negotiate
Definition session.h:167
bool we_are_host
True if we should become host.
Definition negotiate.h:56
char host_address[64]
Host's address (ours if we_are_host)
Definition negotiate.h:57
nat_quality_t our_quality
Our NAT quality.
Definition negotiate.h:47
bool peer_quality_received
Have we received peer's quality?
Definition negotiate.h:49
nat_quality_t peer_quality
Peer's NAT quality (when received)
Definition negotiate.h:48
negotiate_state_t state
Definition negotiate.h:52
uint16_t host_port
Host's port.
Definition negotiate.h:58

References discovery_session_t::acds_socket, buffer_pool_free(), discovery_session_t::callback_user_data, migration_ctx_t::detection_time_ms, discovery_session_check_host_alive(), discovery_session_handle_host_disconnect(), DISCOVERY_STATE_ACTIVE, DISCOVERY_STATE_CONNECTING_HOST, DISCOVERY_STATE_FAILED, DISCOVERY_STATE_MIGRATING, DISCOVERY_STATE_NEGOTIATING, DISCOVERY_STATE_STARTING_HOST, DISCOVERY_STATE_WAITING_PEER, discovery_session_t::error, negotiate_ctx_t::host_address, discovery_session_t::host_address, discovery_session_t::host_ctx, discovery_session_t::host_id, negotiate_ctx_t::host_port, discovery_session_t::host_port, discovery_session_t::is_host, discovery_session_t::is_initiator, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, nat_compute_tier(), discovery_session_t::negotiate, negotiate_determine_result(), negotiate_init(), NEGOTIATE_STATE_COMPARING, NEGOTIATE_STATE_INIT, discovery_session_t::on_session_ready, options_get(), negotiate_ctx_t::our_quality, packet_receive(), discovery_session_t::participant_ctx, discovery_session_t::participant_id, discovery_session_t::peer_manager, negotiate_ctx_t::peer_quality, negotiate_ctx_t::peer_quality_received, platform_get_monotonic_time_us(), platform_sleep_ms(), platform_sleep_us(), receive_packet(), send_crypto_capabilities_packet(), send_protocol_version_packet(), session_host_create(), session_host_start(), discovery_session_t::session_id, session_participant_connect(), session_participant_create(), session_participant_disconnect(), session_participant_get_socket(), session_participant_is_connected(), discovery_session_t::session_string, discovery_session_t::session_type, socket_set_timeout(), negotiate_ctx_t::state, migration_ctx_t::state, discovery_session_t::state, negotiate_ctx_t::we_are_host, discovery_session_t::webrtc_connection_initiated, discovery_session_t::webrtc_last_attempt_time_ms, webrtc_peer_manager_check_gathering_timeouts(), webrtc_peer_manager_connect(), webrtc_peer_manager_destroy(), discovery_session_t::webrtc_retry_attempt, and discovery_session_t::webrtc_transport_ready.

◆ discovery_session_start()

asciichat_error_t discovery_session_start ( discovery_session_t session)

Start the discovery session.

Connects to ACDS and either creates or joins a session.

Parameters
sessionSession context
Returns
ASCIICHAT_OK on success

Definition at line 1209 of file src/discovery/session.c.

1209 {
1210 log_info("discovery_session_start: ENTRY - session=%p", session);
1211
1212 if (!session) {
1213 return SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1214 }
1215
1216 log_info("discovery_session_start: is_initiator=%d, session_string='%s'", session->is_initiator,
1217 session->session_string);
1218
1219 // Check if we should exit before starting blocking operations
1220 if (session->should_exit_callback && session->should_exit_callback(session->exit_callback_data)) {
1221 log_info("discovery_session_start: Exiting early due to should_exit_callback (before ACDS connect)");
1222 return ASCIICHAT_OK; // Return success to allow clean shutdown
1223 }
1224
1225 log_info("discovery_session_start: Calling connect_to_acds...");
1226 // 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);
1230 return result;
1231 }
1232 log_info("discovery_session_start: connect_to_acds succeeded");
1233
1234 // Check again after connection
1235 if (session->should_exit_callback && session->should_exit_callback(session->exit_callback_data)) {
1236 log_info("discovery_session_start: Exiting early due to should_exit_callback (after ACDS connect)");
1237 return ASCIICHAT_OK; // Return success to allow clean shutdown
1238 }
1239
1240 // Create or join session
1241 if (session->is_initiator) {
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",
1245 session->participant_ctx, result);
1246 return result;
1247 } else {
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",
1251 session->participant_ctx, session->state, result);
1252 return result;
1253 }
1254}

References discovery_session_t::exit_callback_data, discovery_session_t::is_initiator, discovery_session_t::participant_ctx, discovery_session_t::session_string, discovery_session_t::should_exit_callback, and discovery_session_t::state.

Referenced by discovery_main().

◆ discovery_session_start_ring_round()

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)

Parameters
sessionSession context
Returns
ASCIICHAT_OK on success

Definition at line 1928 of file src/discovery/session.c.

1928 {
1929 if (!session) {
1930 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1931 return ERROR_INVALID_PARAM;
1932 }
1933
1934 session->ring.last_ring_round_ms = session_get_current_time_ms();
1935
1936 if (session->is_host) {
1937 log_info("Starting 5-minute proactive election round (collecting NETWORK_QUALITY from participants)");
1938
1939 // HOST: Run proactive host election
1940 // 1. Collect NETWORK_QUALITY from all connected participants (TODO: request fresh data)
1941 // 2. Measure the host's own NAT quality
1942 // 3. Run deterministic election to determine future host
1943 // 4. Broadcast FUTURE_HOST_ELECTED to all participants via ACDS
1944 asciichat_error_t result = discovery_session_run_election(session);
1945 if (result != ASCIICHAT_OK) {
1946 log_error("Host election failed: %d", result);
1947 return result;
1948 }
1949
1950 return ASCIICHAT_OK;
1951 } else {
1952 log_debug("Ring round timer triggered (waiting for host to broadcast FUTURE_HOST_ELECTED)");
1953 return ASCIICHAT_OK;
1954 }
1955}

References discovery_session_t::is_host, ring_consensus_t::last_ring_round_ms, and discovery_session_t::ring.

◆ discovery_session_stop()

void discovery_session_stop ( discovery_session_t session)

Stop the discovery session.

Parameters
sessionSession context

Definition at line 1752 of file src/discovery/session.c.

1752 {
1753 if (!session) {
1754 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1755 return;
1756 }
1757
1758 log_info("Stopping discovery session...");
1759
1760 // Send SESSION_LEAVE if connected
1761 if (session->acds_socket != INVALID_SOCKET_VALUE && session->session_id[0] != 0) {
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);
1765 memcpy(leave_msg.participant_id, session->participant_id, 16);
1766
1767 packet_send(session->acds_socket, PACKET_TYPE_ACIP_SESSION_LEAVE, &leave_msg, sizeof(leave_msg));
1768 }
1769
1770 set_state(session, DISCOVERY_STATE_ENDED);
1771}
@ DISCOVERY_STATE_ENDED
Session ended.
Definition session.h:128

References discovery_session_t::acds_socket, DISCOVERY_STATE_ENDED, packet_send(), discovery_session_t::participant_id, and discovery_session_t::session_id.

Referenced by discovery_main().