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

Discovery session flow management. More...

Go to the source code of this file.

Data Structures

struct  ring_consensus_t
 Ring consensus state (Host-Mediated Proactive Election) More...
 
struct  host_liveness_t
 Host liveness detection state. More...
 
struct  migration_ctx_t
 Host migration context for tracking failover state. More...
 
struct  discovery_session_t
 Discovery session context. More...
 
struct  discovery_config_t
 Configuration for discovery session. More...
 

Macros

#define MAX_PARTICIPANTS   16
 

Typedefs

typedef bool(* discovery_should_exit_fn) (void *user_data)
 Callback to check if session should exit.
 

Enumerations

enum  migration_state_t { MIGRATION_STATE_NONE , MIGRATION_STATE_DETECTED , MIGRATION_STATE_FAILOVER , MIGRATION_STATE_COMPLETE }
 Migration detection and failover state. More...
 
enum  discovery_state_t {
  DISCOVERY_STATE_INIT , DISCOVERY_STATE_CONNECTING_ACDS , DISCOVERY_STATE_CREATING_SESSION , DISCOVERY_STATE_JOINING_SESSION ,
  DISCOVERY_STATE_WAITING_PEER , DISCOVERY_STATE_NEGOTIATING , DISCOVERY_STATE_STARTING_HOST , DISCOVERY_STATE_CONNECTING_HOST ,
  DISCOVERY_STATE_ACTIVE , DISCOVERY_STATE_MIGRATING , DISCOVERY_STATE_FAILED , DISCOVERY_STATE_ENDED
}
 Discovery session state. More...
 

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.
 

Detailed Description

Discovery session flow management.

Manages the complete discovery session lifecycle:

  1. Connect to ACDS
  2. Create or join session
  3. NAT negotiation (if needed)
  4. Transition to host or participant role
  5. Handle host migration

Definition in file session.h.

Macro Definition Documentation

◆ MAX_PARTICIPANTS

#define MAX_PARTICIPANTS   16

Definition at line 33 of file session.h.

Typedef Documentation

◆ discovery_should_exit_fn

typedef bool(* discovery_should_exit_fn) (void *user_data)

Callback to check if session should exit.

Called periodically during blocking operations (e.g., ACDS connect, join). Should return true to gracefully cancel the current operation.

Parameters
user_dataOpaque pointer provided by caller
Returns
true to cancel operation, false to continue

Definition at line 111 of file session.h.

Enumeration Type Documentation

◆ discovery_state_t

Discovery session state.

Enumerator
DISCOVERY_STATE_INIT 

Initial state.

DISCOVERY_STATE_CONNECTING_ACDS 

Connecting to ACDS.

DISCOVERY_STATE_CREATING_SESSION 

Creating new session.

DISCOVERY_STATE_JOINING_SESSION 

Joining existing session.

DISCOVERY_STATE_WAITING_PEER 

Waiting for peer to join (initiator)

DISCOVERY_STATE_NEGOTIATING 

NAT negotiation in progress.

DISCOVERY_STATE_STARTING_HOST 

Starting as host.

DISCOVERY_STATE_CONNECTING_HOST 

Connecting to host as participant.

DISCOVERY_STATE_ACTIVE 

Session active (call in progress)

DISCOVERY_STATE_MIGRATING 

Host migration in progress.

DISCOVERY_STATE_FAILED 

Session failed.

DISCOVERY_STATE_ENDED 

Session ended.

Definition at line 116 of file session.h.

116 {
discovery_state_t
Discovery session state.
Definition session.h:116
@ DISCOVERY_STATE_ENDED
Session ended.
Definition session.h:128
@ DISCOVERY_STATE_JOINING_SESSION
Joining existing session.
Definition session.h:120
@ DISCOVERY_STATE_INIT
Initial state.
Definition session.h:117
@ DISCOVERY_STATE_ACTIVE
Session active (call in progress)
Definition session.h:125
@ 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_CREATING_SESSION
Creating new session.
Definition session.h:119
@ DISCOVERY_STATE_CONNECTING_ACDS
Connecting to ACDS.
Definition session.h:118
@ DISCOVERY_STATE_WAITING_PEER
Waiting for peer to join (initiator)
Definition session.h:121
@ DISCOVERY_STATE_FAILED
Session failed.
Definition session.h:127
@ DISCOVERY_STATE_STARTING_HOST
Starting as host.
Definition session.h:123

◆ migration_state_t

Migration detection and failover state.

SIMPLIFIED DESIGN: No re-election during migration! Future host was pre-elected 5 minutes ago. When current host dies, we already know who takes over and where to find them.

Enumerator
MIGRATION_STATE_NONE 

No migration in progress.

MIGRATION_STATE_DETECTED 

Host disconnect detected.

MIGRATION_STATE_FAILOVER 

Failing over to pre-elected future host.

MIGRATION_STATE_COMPLETE 

Failover complete, call resumed.

Definition at line 85 of file session.h.

85 {
migration_state_t
Migration detection and failover state.
Definition session.h:85
@ MIGRATION_STATE_DETECTED
Host disconnect detected.
Definition session.h:87
@ MIGRATION_STATE_COMPLETE
Failover complete, call resumed.
Definition session.h:89
@ MIGRATION_STATE_NONE
No migration in progress.
Definition session.h:86
@ MIGRATION_STATE_FAILOVER
Failing over to pre-elected future host.
Definition session.h:88

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
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)
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
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}

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}
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
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}

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().