ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
session.h File Reference

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_t * discovery_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_t * discovery_session_get_host (discovery_session_t *session)
 Get host context (if we are host)
 
session_participant_t * discovery_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.
 

Macro Definition Documentation

◆ MAX_PARTICIPANTS

#define MAX_PARTICIPANTS   16

Definition at line 36 of file src/discovery/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 114 of file src/discovery/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 119 of file src/discovery/session.h.

119 {
discovery_state_t
Discovery session state.
@ DISCOVERY_STATE_ENDED
Session ended.
@ DISCOVERY_STATE_JOINING_SESSION
Joining existing session.
@ DISCOVERY_STATE_INIT
Initial state.
@ DISCOVERY_STATE_ACTIVE
Session active (call in progress)
@ DISCOVERY_STATE_MIGRATING
Host migration in progress.
@ DISCOVERY_STATE_NEGOTIATING
NAT negotiation in progress.
@ DISCOVERY_STATE_CONNECTING_HOST
Connecting to host as participant.
@ DISCOVERY_STATE_CREATING_SESSION
Creating new session.
@ DISCOVERY_STATE_CONNECTING_ACDS
Connecting to ACDS.
@ DISCOVERY_STATE_WAITING_PEER
Waiting for peer to join (initiator)
@ DISCOVERY_STATE_FAILED
Session failed.
@ DISCOVERY_STATE_STARTING_HOST
Starting as host.

◆ 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 88 of file src/discovery/session.h.

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

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 2455 of file src/discovery/session.c.

2455 {
2456 if (!session) {
2457 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2458 return ERROR_INVALID_PARAM;
2459 }
2460
2461 log_info("Starting as new host after migration (participant ID: %02x%02x...)", session->participant_id[0],
2462 session->participant_id[1]);
2463
2464 // Get configured port (or use default)
2465 int host_port = GET_OPTION(port);
2466
2467 // Mark ourselves as host
2468 session->is_host = true;
2469
2470 // Create host context if needed with configured port
2471 if (!session->host_ctx) {
2472 session_host_config_t hconfig = {
2473 .port = host_port,
2474 .ipv4_address = "0.0.0.0",
2475 .max_clients = 32,
2476 .encryption_enabled = true,
2477 };
2478
2479 session->host_ctx = session_host_create(&hconfig);
2480 if (!session->host_ctx) {
2481 log_error("Failed to create host context for migration");
2482 return ERROR_MEMORY;
2483 }
2484
2485 // Start listening for new participant connections
2487 if (hstart != ASCIICHAT_OK) {
2488 log_error("Failed to start host after migration: %d", hstart);
2489 return hstart;
2490 }
2491
2492 log_info("Host restarted after migration, listening on port %d", host_port);
2493 }
2494
2495 // Send HOST_ANNOUNCEMENT to ACDS so other participants can reconnect
2496 acip_host_announcement_t announcement = {0};
2497 memcpy(announcement.session_id, session->session_id, 16);
2498 memcpy(announcement.host_id, session->participant_id, 16);
2499 SAFE_STRNCPY(announcement.host_address, session->host_address, sizeof(announcement.host_address));
2500 announcement.host_port = host_port;
2501 // Set connection type based on NAT quality (for now use DIRECT_PUBLIC for localhost dev environments)
2502 announcement.connection_type = ACIP_CONNECTION_TYPE_DIRECT_PUBLIC;
2503
2504 if (session->acds_transport) {
2506 sizeof(announcement), 0);
2507 log_info("Sent HOST_ANNOUNCEMENT to ACDS for new host %02x%02x... at %s:%u", announcement.host_id[0],
2508 announcement.host_id[1], announcement.host_address, announcement.host_port);
2509 }
2510
2511 // Mark migration complete
2513
2514 // Transition to ACTIVE state (host is ready)
2515 set_state(session, DISCOVERY_STATE_ACTIVE);
2516
2517 return ASCIICHAT_OK;
2518}
acip_host_announcement_t
Definition acds.h:910
@ ACIP_CONNECTION_TYPE_DIRECT_PUBLIC
Direct public IP connection.
Definition acds.h:838
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
@ PACKET_TYPE_ACIP_HOST_ANNOUNCEMENT
Host announcement (Participant -> ACDS, "I won negotiation")
Definition packet.h:457
asciichat_error_t session_host_start(session_host_t *host)
Start accepting client connections.
Definition host.c:1026
session_host_t * session_host_create(const session_host_config_t *config)
Create a new session host.
Definition host.c:193
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Send packet via transport with proper header (exported for generic wrappers)
Definition send.c:41
acip_transport_t * acds_transport
Persistent transport for ACDS communication.
migration_state_t state
Current migration state.
Configuration for session host.
Definition host.h:154
int port
Port to listen on (default: 27224)
Definition host.h:156

References discovery_session_t::acds_transport, ACIP_CONNECTION_TYPE_DIRECT_PUBLIC, acip_host_announcement_t, ASCIICHAT_OK, DISCOVERY_STATE_ACTIVE, ERROR_INVALID_PARAM, ERROR_MEMORY, GET_OPTION, discovery_session_t::host_address, discovery_session_t::host_ctx, discovery_session_t::is_host, log_error, log_info, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, packet_send_via_transport(), PACKET_TYPE_ACIP_HOST_ANNOUNCEMENT, discovery_session_t::participant_id, session_host_config_t::port, SAFE_STRNCPY, session_host_create(), session_host_start(), discovery_session_t::session_id, SET_ERRNO, 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 2332 of file src/discovery/session.c.

2332 {
2333 if (!session || session->is_host) {
2334 // We are the host, so "host" is always alive
2335 return ASCIICHAT_OK;
2336 }
2337
2338 // For WebRTC sessions, check the WebRTC transport instead of participant_ctx
2339 if (session->session_type == SESSION_TYPE_WEBRTC) {
2340 // If we have a WebRTC transport and it's marked as ready, host is alive
2341 if (session->webrtc_transport_ready) {
2342 return ASCIICHAT_OK;
2343 }
2344 // WebRTC transport not ready yet - might still be connecting
2345 return ERROR_NETWORK;
2346 }
2347
2348 // For TCP sessions, check participant context
2349 // If we don't have a participant context yet, we're not connected
2350 if (!session->participant_ctx) {
2351 return ERROR_NETWORK;
2352 }
2353
2354 // Check migration state - if we're already migrating, host is definitely not alive
2355 if (session->migration.state != MIGRATION_STATE_NONE) {
2356 return ERROR_NETWORK; // Already detected disconnect
2357 }
2358
2359 uint64_t now_ms = session_get_current_time_ms();
2360
2361 // Check if we have a ping in flight that timed out
2362 if (session->liveness.ping_in_flight) {
2363 uint64_t ping_age_ms = now_ms - session->liveness.last_ping_sent_ms;
2364 if (ping_age_ms > session->liveness.timeout_ms) {
2365 // Ping timed out
2366 session->liveness.consecutive_failures++;
2367 session->liveness.ping_in_flight = false;
2368 log_warn("Host ping timeout (attempt %u/%u, age=%llu ms)", session->liveness.consecutive_failures,
2369 session->liveness.max_failures, (unsigned long long)ping_age_ms);
2370
2371 // Check if we've exceeded failure threshold
2372 if (session->liveness.consecutive_failures >= session->liveness.max_failures) {
2373 log_error("Host declared dead after %u consecutive ping failures", session->liveness.consecutive_failures);
2374 return ERROR_NETWORK;
2375 }
2376 }
2377 }
2378
2379 // Check if it's time to send a new ping
2380 uint64_t time_since_last_ping = now_ms - session->liveness.last_ping_sent_ms;
2381 if (!session->liveness.ping_in_flight && time_since_last_ping >= session->liveness.ping_interval_ms) {
2382 // Send ping to host via participant connection
2384 if (host_socket != INVALID_SOCKET_VALUE) {
2385 asciichat_error_t result = packet_send(host_socket, PACKET_TYPE_PING, NULL, 0);
2386 if (result == ASCIICHAT_OK) {
2387 session->liveness.last_ping_sent_ms = now_ms;
2388 session->liveness.ping_in_flight = true;
2389 log_debug("Sent ping to host (attempt %u/%u)", session->liveness.consecutive_failures + 1,
2390 session->liveness.max_failures);
2391 } else {
2392 log_warn("Failed to send ping to host: %d", result);
2393 session->liveness.consecutive_failures++;
2394 if (session->liveness.consecutive_failures >= session->liveness.max_failures) {
2395 log_error("Host declared dead after %u consecutive send failures", session->liveness.consecutive_failures);
2396 return ERROR_NETWORK;
2397 }
2398 }
2399 }
2400 }
2401
2402 return ASCIICHAT_OK;
2403}
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
Definition acds.h:146
unsigned long long uint64_t
Definition common.h:59
@ ERROR_NETWORK
Definition error_codes.h:77
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
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:292
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:383
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
socket_t session_participant_get_socket(session_participant_t *p)
Get the socket from a participant context.
int socket_t
uint8_t session_type
0 = DIRECT_TCP, 1 = WEBRTC
bool webrtc_transport_ready
True when DataChannel is open and transport created.
session_participant_t * participant_ctx
uint32_t consecutive_failures
Number of consecutive ping failures.
uint32_t max_failures
Threshold for triggering migration (default: 3)
uint64_t ping_interval_ms
Time between pings (default: 3000ms)
uint64_t last_ping_sent_ms
Timestamp of last ping sent (monotonic)
bool ping_in_flight
True if waiting for pong.
uint64_t timeout_ms
Timeout for ping response (default: 10000ms)

References ASCIICHAT_OK, host_liveness_t::consecutive_failures, ERROR_NETWORK, INVALID_SOCKET_VALUE, discovery_session_t::is_host, host_liveness_t::last_ping_sent_ms, discovery_session_t::liveness, log_debug, log_error, log_warn, host_liveness_t::max_failures, discovery_session_t::migration, MIGRATION_STATE_NONE, packet_send(), PACKET_TYPE_PING, 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, SESSION_TYPE_WEBRTC, 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 2520 of file src/discovery/session.c.

2520 {
2521 if (!session) {
2522 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2523 return ERROR_INVALID_PARAM;
2524 }
2525
2526 log_info("Reconnecting to future host: %s:%u (connection type: %u)", session->ring.future_host_address,
2528
2529 // Destroy old participant context if present
2530 if (session->participant_ctx) {
2532 session->participant_ctx = NULL;
2533 }
2534
2535 // Create new participant context for the future host
2537 .address = session->ring.future_host_address,
2538 .port = session->ring.future_host_port,
2539 .enable_audio = true,
2540 .enable_video = true,
2541 .encryption_enabled = true,
2542 };
2543
2544 session->participant_ctx = session_participant_create(&pconfig);
2545 if (!session->participant_ctx) {
2546 log_error("Failed to create participant context for future host");
2547 return ERROR_MEMORY;
2548 }
2549
2550 // Attempt connection to future host
2552 if (pconn != ASCIICHAT_OK) {
2553 log_error("Failed to connect to future host: %d", pconn);
2554 return pconn;
2555 }
2556
2557 log_info("Connected to future host after migration");
2558
2559 // Update our host info with the new future host details
2560 memcpy(session->host_id, session->ring.future_host_id, 16);
2561 SAFE_STRNCPY(session->host_address, session->ring.future_host_address, sizeof(session->host_address));
2562 session->host_port = session->ring.future_host_port;
2563
2564 log_info("Updated host info to: %s:%u (id: %02x%02x...)", session->host_address, session->host_port,
2565 session->host_id[0], session->host_id[1]);
2566
2567 // Mark migration complete (in real implementation, this would be done after successful connection)
2569
2570 // Transition to ACTIVE state (participant is ready)
2571 set_state(session, DISCOVERY_STATE_ACTIVE);
2572
2573 return ASCIICHAT_OK;
2574}
asciichat_error_t session_participant_connect(session_participant_t *p)
Connect to session server.
void session_participant_destroy(session_participant_t *p)
Destroy session participant and free resources.
session_participant_t * session_participant_create(const session_participant_config_t *config)
Create a new session participant.
uint8_t future_host_id[16]
Who will host if current host dies.
uint8_t future_host_connection_type
acip_connection_type_t (DIRECT, UPNP, STUN, TURN)
uint16_t future_host_port
Port number.
char future_host_address[64]
Where to connect.
Configuration for session participant.
const char * address
Server address to connect to.

References session_participant_config_t::address, ASCIICHAT_OK, DISCOVERY_STATE_ACTIVE, ERROR_INVALID_PARAM, ERROR_MEMORY, 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, log_error, log_info, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, discovery_session_t::participant_ctx, discovery_session_t::ring, SAFE_STRNCPY, session_participant_connect(), session_participant_create(), session_participant_destroy(), SET_ERRNO, 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 51 of file src/discovery/session.c.

51 {
52 if (!config) {
53 SET_ERRNO(ERROR_INVALID_PARAM, "config is NULL");
54 return NULL;
55 }
56
58 if (!session) {
59 SET_ERRNO(ERROR_MEMORY, "Failed to allocate discovery session");
60 return NULL;
61 }
62
63 session->state = DISCOVERY_STATE_INIT;
64 session->acds_transport = NULL;
65 session->peer_manager = NULL;
66 session->webrtc_transport_ready = false;
67 session->webrtc_connection_initiated = false;
68 session->webrtc_retry_attempt = 0;
69 session->webrtc_last_attempt_time_ms = 0;
71 session->stun_servers = NULL;
72 session->stun_count = 0;
73 session->turn_servers = NULL;
74 session->turn_count = 0;
75
76 // Initialize host liveness detection
77 session->liveness.last_ping_sent_ms = 0;
79 session->liveness.consecutive_failures = 0;
80 session->liveness.max_failures = 3;
81 session->liveness.ping_interval_ms = 3 * MS_PER_SEC_INT; // Ping every 3 seconds
82 session->liveness.timeout_ms = 10 * MS_PER_SEC_INT; // 10 second timeout
83 session->liveness.ping_in_flight = false;
84
85 log_info("discovery_session_create: After CALLOC - participant_ctx=%p, host_ctx=%p", session->participant_ctx,
86 session->host_ctx);
87
88 // Load or generate identity key
89 char identity_path[256];
90 asciichat_error_t id_result = acds_identity_default_path(identity_path, sizeof(identity_path));
91 if (id_result == ASCIICHAT_OK) {
92 id_result = acds_identity_load(identity_path, session->identity_pubkey, session->identity_seckey);
93 if (id_result != ASCIICHAT_OK) {
94 // File doesn't exist or is corrupted - generate new key
95 log_info("Identity key not found, generating new key");
96 id_result = acds_identity_generate(session->identity_pubkey, session->identity_seckey);
97 if (id_result == ASCIICHAT_OK) {
98 // Save for future use
99 acds_identity_save(identity_path, session->identity_pubkey, session->identity_seckey);
100 }
101 }
102 }
103
104 if (id_result != ASCIICHAT_OK) {
105 log_warn("Failed to load/generate identity key, using zero key");
106 memset(session->identity_pubkey, 0, sizeof(session->identity_pubkey));
107 memset(session->identity_seckey, 0, sizeof(session->identity_seckey));
108 } else {
109 char fingerprint[65];
110 acds_identity_fingerprint(session->identity_pubkey, fingerprint);
111 log_info("Using identity key with fingerprint: %.16s...", fingerprint);
112 }
113
114 // Load multiple identity keys from options (multi-key protocol support)
115 const options_t *opts = options_get();
116 if (opts && opts->num_identity_keys > 0) {
117 // Load all identity keys from options
118 for (size_t i = 0; i < opts->num_identity_keys && i < MAX_IDENTITY_KEYS; i++) {
119 uint8_t pubkey[32] = {0};
120 uint8_t seckey[64] = {0};
121 asciichat_error_t key_result = acds_identity_load(opts->identity_keys[i], pubkey, seckey);
122 if (key_result == ASCIICHAT_OK) {
123 memcpy(session->identity_pubkeys[i], pubkey, 32);
124 char fingerprint[65];
125 acds_identity_fingerprint(pubkey, fingerprint);
126 log_info("Loaded identity key #%zu with fingerprint: %.16s...", i + 1, fingerprint);
127 } else {
128 log_warn("Failed to load identity key #%zu from %s", i + 1, opts->identity_keys[i]);
129 }
130 }
131 session->num_identity_keys = opts->num_identity_keys;
132 log_info("Loaded %zu identity key(s) for multi-key protocol", session->num_identity_keys);
133 } else {
134 // Single-key mode: use only the primary identity key
135 memcpy(session->identity_pubkeys[0], session->identity_pubkey, 32);
136 session->num_identity_keys = 1;
137 }
138
139 // ACDS connection info
140 if (config->acds_url && config->acds_url[0]) {
141 SAFE_STRNCPY(session->acds_url, config->acds_url, sizeof(session->acds_url));
142 }
143 if (config->acds_address && config->acds_address[0]) {
144 SAFE_STRNCPY(session->acds_address, config->acds_address, sizeof(session->acds_address));
145 } else {
146 SAFE_STRNCPY(session->acds_address, "127.0.0.1", sizeof(session->acds_address));
147 }
148 session->acds_port = config->acds_port > 0 ? config->acds_port : ACIP_DISCOVERY_DEFAULT_PORT;
149
150 // Session string (if joining)
151 if (config->session_string && config->session_string[0]) {
152 SAFE_STRNCPY(session->session_string, config->session_string, sizeof(session->session_string));
153 session->is_initiator = false;
154 } else {
155 session->is_initiator = true;
156 }
157 if (config->password && config->password[0]) {
158 SAFE_STRNCPY(session->password, config->password, sizeof(session->password));
159 }
160
161 // Callbacks
162 session->on_state_change = config->on_state_change;
163 session->on_session_ready = config->on_session_ready;
164 session->on_error = config->on_error;
165 session->callback_user_data = config->callback_user_data;
167 session->exit_callback_data = config->exit_callback_data;
168
169 // Initialize mutex for NAT quality protection
170 if (mutex_init(&session->nat_quality_mutex, "discovery_nat_quality") != 0) {
171 log_warn("Failed to initialize NAT quality mutex");
172 }
173
174 log_debug("Discovery session created (initiator=%d, acds=%s:%u)", session->is_initiator, session->acds_address,
175 session->acds_port);
176
177 return session;
178}
#define ACIP_DISCOVERY_DEFAULT_PORT
Discovery server default port (use OPT_ACDS_PORT_INT_DEFAULT from options.h)
Definition acds.h:1094
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned char uint8_t
Definition common.h:56
#define MS_PER_SEC_INT
Definition time.h:164
#define MAX_IDENTITY_KEYS
Maximum number of identity keys that can be loaded (for multi-key support)
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
Load identity from file.
Definition identity.c:31
asciichat_error_t acds_identity_default_path(char *path_out, size_t path_size)
Get default identity file path for current platform.
Definition identity.c:120
asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64])
Save identity to file.
Definition identity.c:60
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
Compute SHA256 fingerprint of public key.
Definition identity.c:103
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
Generate new Ed25519 keypair.
Definition identity.c:17
const char * acds_address
ACDS address (default: "127.0.0.1")
void(* on_error)(asciichat_error_t error, const char *message, void *user_data)
uint16_t acds_port
ACDS port (default: 27225)
const char * password
Session password (optional)
discovery_should_exit_fn should_exit_callback
void(* on_state_change)(discovery_state_t new_state, void *user_data)
const char * session_string
Session string to join (or NULL to create)
const char * acds_url
ACDS WebSocket URL (ws:// or wss://, mutually exclusive with acds_port)
void(* on_session_ready)(const char *session_string, void *user_data)
Discovery session context.
char acds_url[512]
ACDS WebSocket URL (ws:// or wss://, if set uses WebSocket instead of TCP)
uint64_t webrtc_last_attempt_time_ms
Timestamp of last connection attempt (monotonic time)
uint64_t webrtc_disconnected_since_ms
Start of a transient peer disconnection.
int webrtc_retry_attempt
Current retry attempt number (0 = initial, 1+ = retries)
char password[128]
Session password used when joining password-protected sessions.
uint8_t identity_pubkey[32]
Ed25519 public key for this participant (primary key)
turn_server_t * turn_servers
TURN servers from ACDS.
size_t stun_count
Number of STUN servers.
void(* on_error)(asciichat_error_t error, const char *message, void *user_data)
uint8_t identity_seckey[64]
Ed25519 secret key for signing (primary key)
size_t num_identity_keys
Number of identity keys loaded.
stun_server_t * stun_servers
STUN servers from ACDS.
void(* on_session_ready)(const char *session_string, void *user_data)
char session_string[SESSION_STRING_BUFFER_SIZE]
discovery_should_exit_fn should_exit_callback
size_t turn_count
Number of TURN servers.
bool webrtc_connection_initiated
True when we've called webrtc_peer_manager_connect()
uint8_t identity_pubkeys[MAX_IDENTITY_KEYS][32]
All public keys for multi-key protocol.
void(* on_state_change)(discovery_state_t new_state, void *user_data)
struct webrtc_peer_manager * peer_manager
WebRTC peer connection manager (NULL for TCP sessions)
uint64_t last_pong_received_ms
Timestamp of last pong received (monotonic)
Consolidated options structure.
size_t num_identity_keys
Number of identity keys loaded (0 means single-key mode using encrypt_key)
char identity_keys[32][256]
All identity keys (populated when –key is used multiple times)

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_transport, discovery_session_t::acds_url, discovery_config_t::acds_url, ACIP_DISCOVERY_DEFAULT_PORT, ASCIICHAT_OK, discovery_session_t::callback_user_data, discovery_config_t::callback_user_data, host_liveness_t::consecutive_failures, DISCOVERY_STATE_INIT, ERROR_INVALID_PARAM, ERROR_MEMORY, discovery_session_t::exit_callback_data, discovery_config_t::exit_callback_data, discovery_session_t::host_ctx, options_state::identity_keys, discovery_session_t::identity_pubkey, discovery_session_t::identity_pubkeys, 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, log_debug, log_info, log_warn, host_liveness_t::max_failures, MAX_IDENTITY_KEYS, MS_PER_SEC_INT, mutex_init(), discovery_session_t::nat_quality_mutex, options_state::num_identity_keys, discovery_session_t::num_identity_keys, 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, options_get(), discovery_session_t::participant_ctx, discovery_session_t::password, discovery_config_t::password, discovery_session_t::peer_manager, host_liveness_t::ping_in_flight, host_liveness_t::ping_interval_ms, SAFE_CALLOC, SAFE_STRNCPY, discovery_session_t::session_string, discovery_config_t::session_string, SET_ERRNO, 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_disconnected_since_ms, 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 180 of file src/discovery/session.c.

180 {
181 if (!session) {
182 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
183 return;
184 }
185
186 // Close ACDS connection
187 if (session->acds_transport) {
189 session->acds_transport = NULL;
190 }
191
192 // Destroy host context if active
193 if (session->host_ctx) {
195 session->host_ctx = NULL;
196 }
197
198 // Destroy participant context if active
199 if (session->participant_ctx) {
201 session->participant_ctx = NULL;
202 }
203
204 // Clean up WebRTC peer manager (NEW)
205 if (session->peer_manager) {
207 session->peer_manager = NULL;
208 }
209
210 // Clean up STUN/TURN server arrays (NEW)
211 if (session->stun_servers) {
212 SAFE_FREE(session->stun_servers);
213 session->stun_servers = NULL;
214 session->stun_count = 0;
215 }
216
217 if (session->turn_servers) {
218 SAFE_FREE(session->turn_servers);
219 session->turn_servers = NULL;
220 session->turn_count = 0;
221 }
222
223 // Wait for NAT detection thread to complete if still running (with 500ms timeout)
224 if (session->nat_detection_thread_started) {
225 log_debug("Waiting for NAT detection thread to complete (500ms timeout)...");
226 int join_result = asciichat_thread_join_timeout(&session->nat_detection_thread, NULL, 500 * 1000 * 1000); // 500ms
227 if (join_result != 0) {
228 log_warn("NAT detection thread did not exit within 500ms, continuing cleanup anyway");
229 }
230 session->nat_detection_thread_started = false;
231 }
232
233 // Destroy NAT quality mutex
235
236 SAFE_FREE(session);
237}
#define SAFE_FREE(ptr)
Definition common.h:376
int asciichat_thread_join_timeout(asciichat_thread_t *thread, void **retval, uint64_t timeout_ns)
Wait for a thread to complete with timeout.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
void session_host_destroy(session_host_t *host)
Destroy session host and free resources.
Definition host.c:249
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
Destroy peer manager and close all connections.
asciichat_thread_t nat_detection_thread
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.

References discovery_session_t::acds_transport, acip_transport_destroy(), asciichat_thread_join_timeout(), ERROR_INVALID_PARAM, discovery_session_t::host_ctx, log_debug, log_warn, mutex_destroy(), discovery_session_t::nat_detection_thread, discovery_session_t::nat_detection_thread_started, discovery_session_t::nat_quality_mutex, discovery_session_t::participant_ctx, discovery_session_t::peer_manager, SAFE_FREE, session_host_destroy(), session_participant_destroy(), SET_ERRNO, 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 2576 of file src/discovery/session.c.

2578 {
2579 if (!session || !out_id || !out_address || !out_port || !out_connection_type) {
2580 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid output parameters");
2581 return ERROR_INVALID_PARAM;
2582 }
2583
2584 if (session->ring.future_host_id[0] == 0) {
2585 SET_ERRNO(ERROR_NOT_FOUND, "Future host not elected yet");
2586 return ERROR_NOT_FOUND;
2587 }
2588
2589 memcpy(out_id, session->ring.future_host_id, 16);
2590 SAFE_STRNCPY(out_address, session->ring.future_host_address, 64);
2591 *out_port = session->ring.future_host_port;
2592 *out_connection_type = session->ring.future_host_connection_type;
2593
2594 return ASCIICHAT_OK;
2595}
@ ERROR_NOT_FOUND

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, 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::ring, SAFE_STRNCPY, and SET_ERRNO.

◆ 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 2177 of file src/discovery/session.c.

2177 {
2178 if (!session) {
2179 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2180 return NULL;
2181 }
2182 return session->host_ctx;
2183}

References ERROR_INVALID_PARAM, discovery_session_t::host_ctx, and SET_ERRNO.

◆ 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 2185 of file src/discovery/session.c.

2185 {
2186 if (!session) {
2187 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2188 return NULL;
2189 }
2190 return session->participant_ctx;
2191}

References ERROR_INVALID_PARAM, discovery_session_t::participant_ctx, and SET_ERRNO.

◆ 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 2145 of file src/discovery/session.c.

2145 {
2146 if (!session) {
2147 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2149 }
2150 return session->state;
2151}

References DISCOVERY_STATE_FAILED, ERROR_INVALID_PARAM, SET_ERRNO, 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 2161 of file src/discovery/session.c.

2161 {
2162 if (!session || session->session_string[0] == '\0') {
2163 SET_ERRNO(ERROR_INVALID_PARAM, "null session or session string is empty");
2164 return NULL;
2165 }
2166 return session->session_string;
2167}

References ERROR_INVALID_PARAM, discovery_session_t::session_string, and SET_ERRNO.

◆ 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 2405 of file src/discovery/session.c.

2405 {
2406 if (!session) {
2407 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2408 return ERROR_INVALID_PARAM;
2409 }
2410
2411 log_warn("Host disconnect detected: reason=%u", disconnect_reason);
2412
2413 // Initialize migration context
2415 session->migration.detection_time_ms = session_get_current_time_ms();
2416 memcpy(session->migration.last_host_id, session->host_id, 16);
2417 session->migration.disconnect_reason = disconnect_reason;
2418
2419 // Notify ACDS of host loss (lightweight notification, no NAT data exchanged!)
2420 acip_host_lost_t host_lost = {0};
2421 memcpy(host_lost.session_id, session->session_id, 16);
2422 memcpy(host_lost.participant_id, session->participant_id, 16);
2423 memcpy(host_lost.last_host_id, session->host_id, 16);
2424 host_lost.disconnect_reason = disconnect_reason;
2425 host_lost.disconnect_time_ms = session->migration.detection_time_ms;
2426
2427 packet_send_via_transport(session->acds_transport, PACKET_TYPE_ACIP_HOST_LOST, &host_lost, sizeof(host_lost), 0);
2428
2429 // Check if we have a pre-elected future host (from last 5-minute ring round)
2430 if (session->ring.future_host_id[0] == 0) {
2431 // No future host known! This shouldn't happen if we just received FUTURE_HOST_ELECTED
2432 // within the last 5 minutes. Fall back to treating this as fatal.
2433 log_error("No future host pre-elected! Session cannot recover from host disconnect.");
2435 return ERROR_NETWORK; // Session should end
2436 }
2437
2438 // **SIMPLIFIED**: No metrics exchange needed! Future host was already elected 5 minutes ago.
2439 // Just failover to the pre-elected host.
2441
2442 // Check if I am the future host
2443 if (session->ring.am_future_host) {
2444 // I become the new host immediately! (no election delay!)
2445 log_info("Becoming host (I'm the pre-elected future host)");
2446 return discovery_session_become_host(session);
2447 } else {
2448 // Connect to pre-elected future host (address already stored!)
2449 log_info("Connecting to pre-elected future host: %s:%u", session->ring.future_host_address,
2450 session->ring.future_host_port);
2452 }
2453}
acip_host_lost_t
Definition acds.h:952
@ PACKET_TYPE_ACIP_HOST_LOST
Host lost notification - lightweight (Participant -> ACDS)
Definition packet.h:465
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)
uint32_t disconnect_reason
Reason for disconnect (from HOST_LOST packet)
uint8_t last_host_id[16]
The host that died.
bool am_future_host
Am I the elected future host?

References discovery_session_t::acds_transport, acip_host_lost_t, 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(), ERROR_INVALID_PARAM, ERROR_NETWORK, 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, log_error, log_info, log_warn, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, MIGRATION_STATE_DETECTED, MIGRATION_STATE_FAILOVER, packet_send_via_transport(), PACKET_TYPE_ACIP_HOST_LOST, discovery_session_t::participant_id, discovery_session_t::ring, discovery_session_t::session_id, SET_ERRNO, 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 2287 of file src/discovery/session.c.

2287 {
2288 if (!session) {
2289 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2290 return ERROR_INVALID_PARAM;
2291 }
2292
2293 // Simplified for new architecture: just initialize timing
2294 // No participant list needed - host runs the election
2295 session->ring.last_ring_round_ms = session_get_current_time_ms();
2296 session->ring.am_future_host = false;
2297 memset(session->ring.future_host_id, 0, 16);
2298
2299 log_info("Ring consensus initialized (host-mediated model)");
2300 return ASCIICHAT_OK;
2301}
uint64_t last_ring_round_ms
When host last ran election (for 5-min timer)

References ring_consensus_t::am_future_host, ASCIICHAT_OK, ERROR_INVALID_PARAM, ring_consensus_t::future_host_id, ring_consensus_t::last_ring_round_ms, log_info, discovery_session_t::ring, and SET_ERRNO.

◆ 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 2153 of file src/discovery/session.c.

2153 {
2154 if (!session) {
2155 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2156 return false;
2157 }
2158 return session->state == DISCOVERY_STATE_ACTIVE;
2159}

References DISCOVERY_STATE_ACTIVE, ERROR_INVALID_PARAM, SET_ERRNO, 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 2597 of file src/discovery/session.c.

2597 {
2598 if (!session) {
2599 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2600 return false;
2601 }
2602 return session->ring.am_future_host;
2603}

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

◆ 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 2169 of file src/discovery/session.c.

2169 {
2170 if (!session) {
2171 SET_ERRNO(ERROR_INVALID_PARAM, "null session");
2172 return false;
2173 }
2174 return session->is_host;
2175}

References ERROR_INVALID_PARAM, discovery_session_t::is_host, and SET_ERRNO.

◆ 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 1516 of file src/discovery/session.c.

1516 {
1517 if (!session) {
1518 return SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1519 }
1520
1521 // Media traffic uses a separate transport; keep the signaling connection alive.
1522 if (session->acds_transport && acip_transport_is_connected(session->acds_transport)) {
1523 uint64_t now_ns = time_get_ns();
1524 if (now_ns - session->last_acds_keepalive_ns >= 5 * NS_PER_SEC_INT) {
1525 asciichat_error_t ping_result = acip_send_ping(session->acds_transport);
1526 if (ping_result != ASCIICHAT_OK)
1527 return ping_result;
1528 session->last_acds_keepalive_ns = now_ns;
1529 }
1530 }
1531 // Handle state-specific processing
1532 switch (session->state) {
1534 // Wait for PARTICIPANT_JOINED notification from ACDS
1535 // Note: don't use a hard timeout here - let the outer process timeout (snapshot delay) control max wait
1536 // The discovery_run() loop will keep calling this until session becomes active or process exits
1537 uint64_t now_ns = time_get_ns();
1538 uint64_t wait_duration = now_ns - session->waiting_peer_start_ns;
1539
1540 log_debug("★ WAITING_PEER: duration=%.3fs (no timeout, waiting for peer...)", wait_duration / 1e9);
1541
1542 if (session->acds_transport && timeout_ns > 0) {
1543 // Do not set socket timeout - let receive_packet_secure use its own timeout
1544 // Setting a short socket timeout conflicts with its hardcoded 1-second timeout
1545 packet_type_t type;
1546 void *data = NULL;
1547 size_t len = 0;
1548 void *alloc_buffer = NULL;
1549
1550 // Try to receive a packet
1551 asciichat_error_t recv_result =
1552 packet_receive_via_transport(session->acds_transport, &type, &data, &len, &alloc_buffer);
1553 if (recv_result == ASCIICHAT_OK) {
1555 log_info("Peer joined session, transitioning to negotiation");
1556 set_state(session, DISCOVERY_STATE_NEGOTIATING);
1557 buffer_pool_free(NULL, alloc_buffer, 0);
1558 } else {
1559 // Got some other packet, handle it or ignore
1560 log_debug("Received packet type %d while waiting for peer", type);
1561 buffer_pool_free(NULL, alloc_buffer, 0);
1562 }
1563 } else if (recv_result == ERROR_NETWORK_TIMEOUT) {
1564 // Timeout is normal, just return
1565 } else {
1566 log_debug("Packet receive error while waiting for peer: %d", recv_result);
1567 }
1568 } else {
1569 // Fallback to sleep if no timeout or invalid transport
1570 platform_sleep_us(timeout_ns / 1000);
1571 }
1572 break;
1573 }
1574
1576 // Handle NAT negotiation
1577 if (session->negotiate.state == NEGOTIATE_STATE_INIT) {
1578 // Initialize negotiation
1579 negotiate_init(&session->negotiate, session->session_id, session->participant_id, session->is_initiator);
1580 log_info("Starting NAT negotiation (initiator=%d)", session->is_initiator);
1581
1582 // Send our NETWORK_QUALITY to ACDS so peers can evaluate us
1583 asciichat_error_t send_result = send_network_quality_to_acds(session);
1584 if (send_result != ASCIICHAT_OK) {
1585 log_warn("Failed to send initial NETWORK_QUALITY: %d", send_result);
1586 }
1587
1588 // ACDS relays NETWORK_QUALITY only to OTHER participants, not back to sender
1589 // For now in discovery mode, assume we're the only one and become host immediately
1590 // A real implementation would wait for peer to join via PARTICIPANT_JOINED, then connect directly
1591 log_info("Proceeding as host (ACDS cannot relay peer quality in solo mode)");
1593 session->negotiate.peer_quality_received = true;
1594 memset(&session->negotiate.peer_quality, 0, sizeof(session->negotiate.peer_quality));
1595 // Mark peer quality as detected but with worst NAT type so we win host election
1598 }
1599
1600 // Check for HOST_ANNOUNCEMENT from ACDS (in case we're waiting for host discovery)
1601 if (session->acds_transport && !session->is_initiator && !session->host_address[0]) {
1602 packet_type_t type;
1603 void *data = NULL;
1604 size_t len = 0;
1605 void *alloc_buffer = NULL;
1606
1607 asciichat_error_t recv_result =
1608 packet_receive_via_transport(session->acds_transport, &type, &data, &len, &alloc_buffer);
1609 if (recv_result == ASCIICHAT_OK && type == PACKET_TYPE_ACIP_HOST_ANNOUNCEMENT) {
1610 acip_host_announcement_t *announcement = (acip_host_announcement_t *)data;
1611 log_info("Received HOST_ANNOUNCEMENT from ACDS: %s:%u", announcement->host_address, announcement->host_port);
1612 SAFE_STRNCPY(session->host_address, announcement->host_address, sizeof(session->host_address));
1613 session->host_port = announcement->host_port;
1614 buffer_pool_free(NULL, alloc_buffer, 0);
1615 // Continue to host role determination below
1616 } else if (recv_result == ASCIICHAT_OK) {
1617 buffer_pool_free(NULL, alloc_buffer, 0);
1618 }
1619 }
1620
1621 // Determine host role (now that peer_quality is set)
1623 log_info("Determining host role based on NAT qualities...");
1625 if (result == ASCIICHAT_OK) {
1626 session->is_host = session->negotiate.we_are_host;
1627
1628 if (session->is_host) {
1629 // We become the host
1630 log_info("🏠 NAT negotiation complete: WE ARE HOST (tier:%d vs tier:%d)",
1633 memcpy(session->host_id, session->participant_id, 16);
1634 SAFE_STRNCPY(session->host_address, session->negotiate.host_address, sizeof(session->host_address));
1635 session->host_port = session->negotiate.host_port;
1636
1637 // Fallback to localhost if NAT detection failed to determine address
1638 if (!session->host_address[0]) {
1639 log_warn("NAT address empty, using localhost fallback");
1640 SAFE_STRNCPY(session->host_address, "127.0.0.1", sizeof(session->host_address));
1641 }
1642
1643 set_state(session, DISCOVERY_STATE_STARTING_HOST);
1644 } else {
1645 // They are host (shouldn't happen in solo mode, but handle it)
1646 log_info("👤 NAT negotiation complete: THEY ARE HOST at %s:%u (tier:%d vs tier:%d)",
1647 session->negotiate.host_address, session->negotiate.host_port,
1650 SAFE_STRNCPY(session->host_address, session->negotiate.host_address, sizeof(session->host_address));
1651 session->host_port = session->negotiate.host_port;
1652
1653 // If host address is empty, wait for HOST_ANNOUNCEMENT from ACDS instead of using fallback
1654 if (!session->host_address[0]) {
1655 log_info("Waiting for HOST_ANNOUNCEMENT from ACDS to discover host address...");
1656 // Stay in NEGOTIATING state to continue waiting for HOST_ANNOUNCEMENT
1657 break;
1658 }
1659
1660 set_state(session, DISCOVERY_STATE_CONNECTING_HOST);
1661 }
1662 } else {
1663 log_error("Failed to determine negotiation result: %d", result);
1664 set_error(session, result, "Host election failed");
1665 }
1666 }
1667 break;
1668 }
1669
1671 // Start hosting
1672 if (!session->host_ctx) {
1673 // Create host context with configured port
1674 int host_port = GET_OPTION(port);
1675
1676 session_host_config_t hconfig = {
1677 .port = host_port,
1678 .ipv4_address = "0.0.0.0",
1679 .max_clients = 32,
1680 .encryption_enabled = true,
1681 };
1682
1683 session->host_ctx = session_host_create(&hconfig);
1684 if (!session->host_ctx) {
1685 log_error("Failed to create host context");
1686 set_error(session, ERROR_MEMORY, "Failed to create host context");
1687 break;
1688 }
1689
1690 // Start listening
1692 if (hstart != ASCIICHAT_OK) {
1693 log_error("Failed to start host: %d", hstart);
1694 set_error(session, hstart, "Failed to start host");
1695 break;
1696 }
1697
1698 log_info("Host started, listening for connections");
1699 }
1700
1701 // Notify listeners that we're ready
1702 if (session->on_session_ready) {
1703 session->on_session_ready(session->session_string, session->callback_user_data);
1704 }
1705
1706 // Send HOST_ANNOUNCEMENT to ACDS so participants can discover us
1707 int host_port = GET_OPTION(port);
1708 acip_host_announcement_t announcement = {0};
1709 memcpy(announcement.session_id, session->session_id, 16);
1710 memcpy(announcement.host_id, session->host_id, 16);
1711 SAFE_STRNCPY(announcement.host_address, session->host_address, sizeof(announcement.host_address));
1712 announcement.host_port = host_port;
1713 announcement.connection_type = ACIP_CONNECTION_TYPE_DIRECT_PUBLIC;
1714
1715 if (session->acds_transport) {
1717 sizeof(announcement), 0);
1718 log_info("Sent HOST_ANNOUNCEMENT to ACDS at %s:%u", announcement.host_address, announcement.host_port);
1719 }
1720
1721 set_state(session, DISCOVERY_STATE_ACTIVE);
1722 break;
1723 }
1724
1726 // Connect to host as participant
1727
1728 // For WebRTC sessions, wait for DataChannel to open
1729 if (session->session_type == SESSION_TYPE_WEBRTC) {
1730 // Check if transport is ready (DataChannel opened)
1731 if (session->webrtc_transport_ready) {
1732 log_info("WebRTC DataChannel established, transitioning to ACTIVE state");
1733 set_state(session, DISCOVERY_STATE_ACTIVE);
1734 break;
1735 }
1736
1737 // If not ready and we haven't initiated connection yet, initiate it
1738 if (session->peer_manager && !session->webrtc_connection_initiated) {
1739 log_info("Initiating WebRTC connection to host (attempt %d/%d)...", session->webrtc_retry_attempt + 1,
1740 GET_OPTION(webrtc_reconnect_attempts) + 1);
1741
1742 // Initiate connection (generates offer, triggers SDP exchange)
1743 asciichat_error_t conn_result =
1744 webrtc_peer_manager_connect(session->peer_manager, session->session_id, session->host_id);
1745
1746 if (conn_result != ASCIICHAT_OK) {
1747 log_error("Failed to initiate WebRTC connection: %d", conn_result);
1748 set_error(session, conn_result, "Failed to initiate WebRTC connection");
1749 break;
1750 }
1751
1752 log_info("WebRTC connection initiated, waiting for DataChannel...");
1753 session->webrtc_connection_initiated = true;
1755 }
1756
1757 // Check for ICE gathering timeout on all peers
1758 if (session->webrtc_connection_initiated && session->peer_manager) {
1759 int timeout_ms = GET_OPTION(webrtc_ice_timeout_ms);
1760 int timed_out_count = webrtc_peer_manager_check_gathering_timeouts(session->peer_manager, timeout_ms);
1761
1762 if (timed_out_count > 0) {
1763 log_error("WebRTC negotiation failed: %d peer(s) failed or exceeded the %dms gathering timeout",
1764 timed_out_count, timeout_ms);
1765
1766 // Check if we have retries remaining
1767 int max_attempts = GET_OPTION(webrtc_reconnect_attempts);
1768 if (session->webrtc_retry_attempt < max_attempts) {
1769 // Calculate exponential backoff delay
1770 uint32_t backoff_ms = calculate_backoff_delay_ms(session->webrtc_retry_attempt);
1771
1772 log_warn("WebRTC connection attempt %d/%d failed, retrying in %ums...", session->webrtc_retry_attempt + 1,
1773 max_attempts + 1, backoff_ms);
1774
1775 // Wait for backoff period
1776 APP_CALLBACK_VOID(platform_pump_events);
1778
1779 // Destroy old peer manager
1781 session->peer_manager = NULL;
1782
1783 // Re-initialize peer manager for retry
1784 asciichat_error_t reinit_result = initialize_webrtc_peer_manager(session);
1785 if (reinit_result != ASCIICHAT_OK) {
1786 log_error("Failed to re-initialize WebRTC for retry: %d", reinit_result);
1787 set_error(session, reinit_result, "Failed to re-initialize WebRTC for retry");
1788
1789 // If --prefer-webrtc is set, this is a fatal error
1790 if (GET_OPTION(prefer_webrtc) || GET_OPTION(webrtc_relay_only)) {
1791 log_fatal("WebRTC connection failed and direct fallback is disabled");
1792 set_state(session, DISCOVERY_STATE_FAILED);
1793 return ERROR_NETWORK_TIMEOUT;
1794 }
1795
1796 break; // Exit this case to allow fallback to TCP
1797 }
1798
1799 // Reset connection state for retry
1800 session->webrtc_connection_initiated = false;
1801 session->webrtc_retry_attempt++;
1802
1803 log_info("WebRTC peer manager re-initialized for attempt %d/%d", session->webrtc_retry_attempt + 1,
1804 max_attempts + 1);
1805 } else {
1806 // No more retries - connection failed
1807 log_error("WebRTC connection failed after %d attempts (timeout: %dms)", max_attempts + 1, timeout_ms);
1808
1809 // Set error state - WebRTC connection failed
1810 char error_msg[256];
1811 snprintf(error_msg, sizeof(error_msg), "WebRTC connection failed after %d attempts (%dms timeout)",
1812 max_attempts + 1, timeout_ms);
1813 set_error(session, ERROR_NETWORK_TIMEOUT, error_msg);
1814
1815 // If --prefer-webrtc is set, this is a fatal error (no TCP fallback)
1816 if (GET_OPTION(prefer_webrtc) || GET_OPTION(webrtc_relay_only)) {
1817 log_fatal("WebRTC connection failed and direct fallback is disabled");
1818 set_state(session, DISCOVERY_STATE_FAILED);
1819 return ERROR_NETWORK_TIMEOUT;
1820 }
1821
1822 break; // Exit this case to allow fallback to TCP
1823 }
1824 }
1825 }
1826
1827 // Poll ACDS for WebRTC signaling messages (SDP answer, ICE candidates)
1828 // while waiting for DataChannel to open
1829 if (session->acds_transport && acip_transport_has_pending_data(session->acds_transport)) {
1830 packet_type_t type;
1831 void *data = NULL;
1832 size_t len = 0;
1833 void *alloc_buffer = NULL;
1834
1835 asciichat_error_t recv_result =
1836 packet_receive_via_transport(session->acds_transport, &type, &data, &len, &alloc_buffer);
1837
1838 if (recv_result == ASCIICHAT_OK) {
1839 // Dispatch to appropriate handler
1840 handle_acds_webrtc_packet(session, type, data, len);
1841 buffer_pool_free(NULL, alloc_buffer, 0);
1842 } else if (!acip_transport_is_connected(session->acds_transport)) {
1843 set_error(session, recv_result, "Discovery signaling connection closed during WebRTC negotiation");
1844 set_state(session, DISCOVERY_STATE_FAILED);
1845 return recv_result;
1846 } else {
1847 log_debug("Failed to receive ACDS packet: %d", recv_result);
1848 }
1849 }
1850
1851 // Stay in CONNECTING_HOST until DataChannel opens (webrtc_transport_ready becomes true)
1852 // The discovery_on_transport_ready callback will set webrtc_transport_ready=true
1853 break;
1854 }
1855
1856 if (!session->participant_ctx) {
1857 // Create participant context for this session
1859 .address = session->host_address,
1860 .port = session->host_port,
1861 .enable_audio = true,
1862 .enable_video = true,
1863 .encryption_enabled = true,
1864 };
1865
1866 session->participant_ctx = session_participant_create(&pconfig);
1867 if (!session->participant_ctx) {
1868 log_error("Failed to create participant context");
1869 set_error(session, ERROR_MEMORY, "Failed to create participant context");
1870 break;
1871 }
1872
1873 // Attempt connection
1875 if (pconn != ASCIICHAT_OK) {
1876 log_error("Failed to connect as participant: %d", pconn);
1877 // Stay in this state, will retry on next process() call
1878 break;
1879 }
1880
1881 log_info("Connected to host, performing crypto handshake...");
1882
1883 // Perform crypto handshake with server
1884 // Get the socket from participant context
1885 socket_t participant_socket = session_participant_get_socket(session->participant_ctx);
1886 if (participant_socket == INVALID_SOCKET_VALUE) {
1887 log_error("Failed to get socket from participant context");
1888 set_error(session, ERROR_NETWORK, "Failed to get participant socket");
1890 break;
1891 }
1892
1893 // Step 1: Send client protocol version
1894 log_debug("Sending protocol version to server...");
1895 protocol_version_packet_t client_version = {0};
1896 client_version.protocol_version = HOST_TO_NET_U16(1); // Protocol version 1
1897 client_version.protocol_revision = HOST_TO_NET_U16(0); // Revision 0
1898 client_version.supports_encryption = 1; // We support encryption
1899 client_version.compression_algorithms = 0;
1900 client_version.compression_threshold = 0;
1901 client_version.feature_flags = 0;
1902
1903 int result = send_protocol_version_packet(participant_socket, &client_version);
1904 if (result != 0) {
1905 log_error("Failed to send protocol version to server");
1906 set_error(session, ERROR_NETWORK, "Failed to send protocol version");
1908 break;
1909 }
1910
1911 // Step 2: Receive server protocol version
1912 log_debug("Receiving server protocol version...");
1913 packet_type_t packet_type;
1914 void *payload = NULL;
1915 size_t payload_len = 0;
1916
1917 int recv_result = receive_packet(participant_socket, &packet_type, &payload, &payload_len);
1918 if (recv_result != ASCIICHAT_OK || packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
1919 log_error("Failed to receive server protocol version (got type 0x%x)", packet_type);
1920 if (payload) {
1921 buffer_pool_free(NULL, payload, payload_len);
1922 }
1923 set_error(session, ERROR_NETWORK, "Failed to receive protocol version from server");
1925 break;
1926 }
1927
1928 if (payload_len != sizeof(protocol_version_packet_t)) {
1929 log_error("Invalid protocol version packet size: %zu", payload_len);
1930 buffer_pool_free(NULL, payload, payload_len);
1931 set_error(session, ERROR_NETWORK, "Invalid protocol version packet");
1933 break;
1934 }
1935
1936 protocol_version_packet_t server_version;
1937 memcpy(&server_version, payload, sizeof(protocol_version_packet_t));
1938 buffer_pool_free(NULL, payload, payload_len);
1939
1940 if (!server_version.supports_encryption) {
1941 log_error("Server does not support encryption");
1942 set_error(session, ERROR_NETWORK, "Server does not support encryption");
1944 break;
1945 }
1946
1947 log_info("Server protocol version: %u.%u (encryption: yes)", NET_TO_HOST_U16(server_version.protocol_version),
1948 NET_TO_HOST_U16(server_version.protocol_revision));
1949
1950 // Step 3: Send crypto capabilities
1951 log_debug("Sending crypto capabilities...");
1952 crypto_capabilities_packet_t client_caps = {0};
1953 client_caps.supported_kex_algorithms = HOST_TO_NET_U16(0x0001); // KEX_ALGO_X25519
1954 client_caps.supported_auth_algorithms = HOST_TO_NET_U16(0x0003); // AUTH_ALGO_ED25519 | AUTH_ALGO_NONE
1955 client_caps.supported_cipher_algorithms = HOST_TO_NET_U16(0x0001); // CIPHER_ALGO_XSALSA20_POLY1305
1956 client_caps.requires_verification = 0;
1957 client_caps.preferred_kex = 0x0001; // KEX_ALGO_X25519
1958 client_caps.preferred_auth = 0x0001; // AUTH_ALGO_ED25519
1959 client_caps.preferred_cipher = 0x0001; // CIPHER_ALGO_XSALSA20_POLY1305
1960
1961 result = send_crypto_capabilities_packet(participant_socket, &client_caps);
1962 if (result != 0) {
1963 log_error("Failed to send crypto capabilities");
1964 set_error(session, ERROR_NETWORK, "Failed to send crypto capabilities");
1966 break;
1967 }
1968
1969 log_info("Crypto handshake initiated successfully");
1970 log_warn("*** Connected to host as participant - transitioning to ACTIVE ***");
1971 }
1972
1973 // Check if connection is established
1974 bool ctx_valid = session->participant_ctx != NULL;
1975 bool is_connected = ctx_valid ? session_participant_is_connected(session->participant_ctx) : false;
1976 log_debug("discovery_session_process: CONNECTING_HOST - participant_ctx=%p, ctx_valid=%d, is_connected=%d",
1977 session->participant_ctx, ctx_valid, is_connected);
1978
1979 if (ctx_valid && !is_connected) {
1980 // Log more details about why it's not connected
1981 log_debug("discovery_session_process: CONNECTING_HOST - context exists but not connected yet");
1982 }
1983
1984 if (is_connected) {
1985 log_info("Participant connection confirmed, transitioning to ACTIVE state");
1986 set_state(session, DISCOVERY_STATE_ACTIVE);
1987 } else {
1988 log_debug("discovery_session_process: CONNECTING_HOST - awaiting connection establishment (ctx_valid=%d, "
1989 "is_connected=%d)",
1990 ctx_valid, is_connected);
1991 }
1992 break;
1993 }
1994
1996 // Receive and handle ACDS packets for WebRTC signaling (NEW)
1997 if (session->session_type == SESSION_TYPE_WEBRTC && session->acds_transport &&
1998 acip_transport_has_pending_data(session->acds_transport)) {
1999 packet_type_t type;
2000 void *data = NULL;
2001 size_t len = 0;
2002 void *alloc_buffer = NULL;
2003
2004 asciichat_error_t recv_result =
2005 packet_receive_via_transport(session->acds_transport, &type, &data, &len, &alloc_buffer);
2006
2007 if (recv_result == ASCIICHAT_OK) {
2008 // Dispatch to appropriate handler
2009 handle_acds_webrtc_packet(session, type, data, len);
2010 buffer_pool_free(NULL, alloc_buffer, 0);
2011 }
2012 }
2013
2014 if (session->session_type == SESSION_TYPE_WEBRTC && !session->is_host && session->peer_manager) {
2016 bool peer_found = webrtc_peer_manager_get_state(session->peer_manager, session->host_id, &peer_state);
2017 uint64_t now_ms = session_get_current_time_ms();
2018 bool disconnected_too_long = false;
2019
2020 if (peer_found && peer_state == WEBRTC_STATE_DISCONNECTED) {
2021 if (session->webrtc_disconnected_since_ms == 0) {
2022 session->webrtc_disconnected_since_ms = now_ms;
2023 } else {
2024 disconnected_too_long = now_ms - session->webrtc_disconnected_since_ms >= 5000;
2025 }
2026 } else {
2027 session->webrtc_disconnected_since_ms = 0;
2028 }
2029
2030 bool peer_failed = !peer_found || peer_state == WEBRTC_STATE_FAILED || peer_state == WEBRTC_STATE_CLOSED;
2031 if (session->webrtc_transport_ready && (peer_failed || disconnected_too_long)) {
2032 int max_attempts = GET_OPTION(webrtc_reconnect_attempts);
2033 if (session->webrtc_retry_attempt >= max_attempts) {
2034 set_error(session, ERROR_NETWORK, "WebRTC peer remained disconnected after retry attempts");
2035 return ERROR_NETWORK;
2036 }
2037
2038 log_warn("WebRTC peer lost connectivity; resetting its transport and renegotiating");
2039 if (session->participant_ctx) {
2042 acip_transport_destroy(transport);
2043 }
2044
2045 // The transport has now closed its borrowed peer objects. The manager
2046 // can release the failed peer before creating the next offer.
2048 session->webrtc_transport_ready = false;
2049 session->webrtc_connection_initiated = false;
2050 session->webrtc_disconnected_since_ms = 0;
2051 session->webrtc_retry_attempt++;
2052 set_state(session, DISCOVERY_STATE_CONNECTING_HOST);
2053 break;
2054 }
2055 }
2056
2057 // Session is active - check for host disconnect
2058 if (!session->is_host) {
2059 // We are a participant - check if host is still alive
2061 if (host_check != ASCIICHAT_OK) {
2062 // Host is down! Trigger automatic failover to pre-elected future host
2063 log_warn("Host disconnect detected during ACTIVE state");
2064
2065 // If --prefer-webrtc is set, WebRTC failure is fatal (no TCP fallback)
2066 const options_t *opts = options_get();
2067 if (opts && (opts->prefer_webrtc || opts->webrtc_relay_only)) {
2068 log_fatal("WebRTC connection failed and direct fallback is disabled");
2069 set_state(session, DISCOVERY_STATE_FAILED);
2070 session->error = ERROR_NETWORK;
2071 return ERROR_NETWORK;
2072 }
2073
2074 set_state(session, DISCOVERY_STATE_MIGRATING);
2075 // Reason code: 1 = timeout/disconnect detected during ACTIVE session
2077 }
2078 }
2079 platform_sleep_us(timeout_ns / 1000);
2080 break;
2081
2083 // Host migration in progress
2084 // Check if migration has timed out (if it takes >30 seconds, give up)
2085 if (session->migration.state == MIGRATION_STATE_COMPLETE) {
2086 // Migration completed successfully
2087 set_state(session, DISCOVERY_STATE_ACTIVE);
2088 log_info("Migration complete, session resumed");
2089 } else if (session_get_current_time_ms() - session->migration.detection_time_ms > 30 * MS_PER_SEC_INT) {
2090 // Migration timed out after 30 seconds
2091 log_error("Host migration timeout - session cannot recover");
2092 set_state(session, DISCOVERY_STATE_FAILED);
2093 session->error = ERROR_NETWORK;
2094 }
2095 platform_sleep_us(timeout_ns / 1000);
2096 break;
2097
2099 // Session has failed - return error on further processing
2100 return session->error != ASCIICHAT_OK ? session->error : ERROR_NETWORK;
2101
2103 // Session has ended gracefully - return OK
2104 return ASCIICHAT_OK;
2105
2110 // These states are handled during discovery_session_start(), not in the process loop
2111 // If we reach here, something went wrong - sleep and wait
2112 log_debug("discovery_session_process: State %d should be handled by discovery_session_start", session->state);
2113 platform_sleep_us(timeout_ns / 1000);
2114 break;
2115
2116 default:
2117 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid session state: %d", session->state);
2118 }
2119
2120 return ASCIICHAT_OK;
2121}
#define APP_CALLBACK_UINT64(callback_name)
#define APP_CALLBACK_VOID(callback_name)
#define APP_CALLBACK_VOID_UINT(callback_name, value)
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define NET_TO_HOST_U16(val)
Definition endian.h:111
@ ACIP_NAT_TYPE_SYMMETRIC
Symmetric NAT (hardest, requires TURN)
Definition acds.h:830
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
unsigned int uint32_t
Definition common.h:58
@ ERROR_INVALID_STATE
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:80
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
Definition packet.c:1119
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:797
int send_crypto_capabilities_packet(socket_t sockfd, const crypto_capabilities_packet_t *caps)
Send crypto capabilities packet.
Definition packet.c:1133
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_ACIP_PARTICIPANT_JOINED
Participant joined notification (ACDS -> Existing Participants)
Definition packet.h:469
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:288
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
acip_transport_t * session_participant_get_transport(session_participant_t *p)
Get the current transport for the participant.
bool session_participant_is_connected(session_participant_t *p)
Check if participant is connected.
asciichat_error_t session_participant_set_transport(session_participant_t *p, acip_transport_t *transport)
Set an alternative transport for the participant.
void session_participant_disconnect(session_participant_t *p)
Disconnect from session server.
bool webrtc_peer_manager_get_state(webrtc_peer_manager_t *manager, const uint8_t participant_id[16], webrtc_state_t *state_out)
asciichat_error_t webrtc_peer_manager_remove_peer(webrtc_peer_manager_t *manager, const uint8_t participant_id[16])
asciichat_error_t webrtc_peer_manager_connect(webrtc_peer_manager_t *manager, const uint8_t session_id[16], const uint8_t participant_id[16])
Initiate connection to remote peer (joiner role only)
int webrtc_peer_manager_check_gathering_timeouts(webrtc_peer_manager_t *manager, uint32_t timeout_ms)
Report slow ICE gathering and remove failed connections.
webrtc_state_t
WebRTC connection state.
@ WEBRTC_STATE_DISCONNECTED
Connection lost.
@ WEBRTC_STATE_FAILED
Connection failed (fatal)
@ WEBRTC_STATE_CLOSED
Connection closed cleanly.
int nat_compute_tier(const nat_quality_t *quality)
Compute NAT tier for host selection (0=best, 4=worst)
Definition nat.c:39
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
asciichat_error_t packet_receive_via_transport(acip_transport_t *transport, packet_type_t *type, void **payload, size_t *payload_len, void **alloc_buffer)
Receive a packet via transport with header parsing and validation.
Definition send.c:122
asciichat_error_t acip_send_ping(acip_transport_t *transport)
Send ping packet.
Definition send.c:271
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:63
Transport instance structure.
Definition transport.h:214
Crypto capabilities packet structure (Packet Type 14)
Definition packet.h:952
uint8_t preferred_auth
Preferred authentication algorithm (AUTH_ALGO_*)
Definition packet.h:964
uint16_t supported_auth_algorithms
Supported authentication algorithms bitmask (AUTH_ALGO_*)
Definition packet.h:956
uint16_t supported_kex_algorithms
Supported key exchange algorithms bitmask (KEX_ALGO_*)
Definition packet.h:954
uint8_t preferred_cipher
Preferred cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:966
uint16_t supported_cipher_algorithms
Supported cipher algorithms bitmask (CIPHER_ALGO_*)
Definition packet.h:958
uint8_t preferred_kex
Preferred key exchange algorithm (KEX_ALGO_*)
Definition packet.h:962
uint8_t requires_verification
Server verification requirement flag (1=required, 0=optional)
Definition packet.h:960
uint64_t last_acds_keepalive_ns
Last keepalive sent to the discovery service.
uint64_t waiting_peer_start_ns
Timestamp when entered WAITING_PEER state (for timeout)
bool detection_complete
All probes finished.
Definition nat.h:51
acip_nat_type_t nat_type
NAT classification.
Definition nat.h:32
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
bool webrtc_relay_only
–webrtc-relay-only: Require TURN relay candidates
bool prefer_webrtc
–prefer-webrtc: Try WebRTC before Direct TCP
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:818
uint8_t compression_threshold
Compression threshold percentage (0-100, e.g., 80 = compress if >80% size reduction)
Definition packet.h:828
uint16_t protocol_revision
Minor protocol revision (server can be newer)
Definition packet.h:822
uint16_t protocol_version
Major protocol version (must match for compatibility)
Definition packet.h:820
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.)
Definition packet.h:830
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*)
Definition packet.h:826
uint8_t supports_encryption
Encryption & authentication mode bitmask (ACIP_CRYPTO_* constants: NONE, ENCRYPT, AUTH,...
Definition packet.h:824

References discovery_session_t::acds_transport, ACIP_CONNECTION_TYPE_DIRECT_PUBLIC, acip_host_announcement_t, ACIP_NAT_TYPE_SYMMETRIC, acip_send_ping(), acip_transport_destroy(), session_participant_config_t::address, APP_CALLBACK_UINT64, APP_CALLBACK_VOID, APP_CALLBACK_VOID_UINT, ASCIICHAT_OK, buffer_pool_free(), discovery_session_t::callback_user_data, protocol_version_packet_t::compression_algorithms, protocol_version_packet_t::compression_threshold, nat_quality_t::detection_complete, migration_ctx_t::detection_time_ms, discovery_session_check_host_alive(), discovery_session_handle_host_disconnect(), DISCOVERY_STATE_ACTIVE, DISCOVERY_STATE_CONNECTING_ACDS, DISCOVERY_STATE_CONNECTING_HOST, DISCOVERY_STATE_CREATING_SESSION, DISCOVERY_STATE_ENDED, DISCOVERY_STATE_FAILED, DISCOVERY_STATE_INIT, DISCOVERY_STATE_JOINING_SESSION, DISCOVERY_STATE_MIGRATING, DISCOVERY_STATE_NEGOTIATING, DISCOVERY_STATE_STARTING_HOST, DISCOVERY_STATE_WAITING_PEER, discovery_session_t::error, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK, ERROR_NETWORK_TIMEOUT, protocol_version_packet_t::feature_flags, GET_OPTION, 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, HOST_TO_NET_U16, INVALID_SOCKET_VALUE, discovery_session_t::is_host, discovery_session_t::is_initiator, discovery_session_t::last_acds_keepalive_ns, log_debug, log_error, log_fatal, log_info, log_warn, discovery_session_t::migration, MIGRATION_STATE_COMPLETE, MS_PER_SEC_INT, nat_compute_tier(), nat_quality_t::nat_type, discovery_session_t::negotiate, negotiate_determine_result(), negotiate_init(), NEGOTIATE_STATE_COMPARING, NEGOTIATE_STATE_INIT, NET_TO_HOST_U16, NS_PER_SEC_INT, discovery_session_t::on_session_ready, options_get(), negotiate_ctx_t::our_quality, packet_receive_via_transport(), packet_send_via_transport(), PACKET_TYPE_ACIP_HOST_ANNOUNCEMENT, PACKET_TYPE_ACIP_PARTICIPANT_JOINED, PACKET_TYPE_PROTOCOL_VERSION, 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(), session_host_config_t::port, options_state::prefer_webrtc, crypto_capabilities_packet_t::preferred_auth, crypto_capabilities_packet_t::preferred_cipher, crypto_capabilities_packet_t::preferred_kex, protocol_version_packet_t::protocol_revision, protocol_version_packet_t::protocol_version, receive_packet(), crypto_capabilities_packet_t::requires_verification, SAFE_STRNCPY, 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_get_transport(), session_participant_is_connected(), session_participant_set_transport(), discovery_session_t::session_string, discovery_session_t::session_type, SESSION_TYPE_WEBRTC, SET_ERRNO, negotiate_ctx_t::state, migration_ctx_t::state, discovery_session_t::state, crypto_capabilities_packet_t::supported_auth_algorithms, crypto_capabilities_packet_t::supported_cipher_algorithms, crypto_capabilities_packet_t::supported_kex_algorithms, protocol_version_packet_t::supports_encryption, time_get_ns(), discovery_session_t::waiting_peer_start_ns, negotiate_ctx_t::we_are_host, discovery_session_t::webrtc_connection_initiated, discovery_session_t::webrtc_disconnected_since_ms, discovery_session_t::webrtc_last_attempt_time_ms, webrtc_peer_manager_check_gathering_timeouts(), webrtc_peer_manager_connect(), webrtc_peer_manager_destroy(), webrtc_peer_manager_get_state(), webrtc_peer_manager_remove_peer(), options_state::webrtc_relay_only, discovery_session_t::webrtc_retry_attempt, WEBRTC_STATE_CLOSED, WEBRTC_STATE_DISCONNECTED, WEBRTC_STATE_FAILED, 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 1327 of file src/discovery/session.c.

1327 {
1328 log_info("discovery_session_start: ENTRY - session=%p", session);
1329
1330 if (!session) {
1331 return SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
1332 }
1333
1334 log_info("discovery_session_start: is_initiator=%d, session_string='%s'", session->is_initiator,
1335 session->session_string);
1336
1337 // Check if we should exit before starting blocking operations
1338 if (session->should_exit_callback && session->should_exit_callback(session->exit_callback_data)) {
1339 log_info("discovery_session_start: Exiting early due to should_exit_callback (before ACDS connect)");
1340 return ASCIICHAT_OK; // Return success to allow clean shutdown
1341 }
1342
1343 log_info("discovery_session_start: Calling connect_to_acds...");
1344 // Connect to ACDS
1345 asciichat_error_t result = connect_to_acds(session);
1346 if (result != ASCIICHAT_OK) {
1347 log_error("discovery_session_start: connect_to_acds FAILED - result=%d", result);
1348 set_state(session, DISCOVERY_STATE_FAILED);
1349 return result;
1350 }
1351 log_info("discovery_session_start: connect_to_acds succeeded");
1352
1353 // Check again after connection
1354 if (session->should_exit_callback && session->should_exit_callback(session->exit_callback_data)) {
1355 log_info("discovery_session_start: Exiting early due to should_exit_callback (after ACDS connect)");
1356 return ASCIICHAT_OK; // Return success to allow clean shutdown
1357 }
1358
1359 // Perform crypto handshake with ACDS (same as client-server handshake)
1360 log_info("discovery_session_start: Performing crypto handshake with ACDS...");
1361 crypto_handshake_context_t handshake_ctx;
1362 memset(&handshake_ctx, 0, sizeof(handshake_ctx));
1363
1364 result = crypto_handshake_init("discovery_client", &handshake_ctx, false);
1365 if (result != ASCIICHAT_OK) {
1366 log_error("discovery_session_start: Handshake init failed - %s", asciichat_error_string(result));
1367 set_error(session, result, "Crypto handshake init failed");
1368 return result;
1369 }
1370
1371 // Store identity key in handshake context
1372 handshake_ctx.client_public_key.type = KEY_TYPE_ED25519;
1374 memcpy(handshake_ctx.client_public_key.key, session->identity_pubkey, 32);
1375 memcpy(handshake_ctx.client_private_key.key.ed25519, session->identity_seckey, 64);
1376
1377 // Step 0: Send protocol version to ACDS
1378 log_debug("Sending protocol version to ACDS...");
1379 protocol_version_packet_t client_version = {0};
1380 client_version.protocol_version = HOST_TO_NET_U16(1);
1381 client_version.protocol_revision = HOST_TO_NET_U16(0);
1382 client_version.supports_encryption = 1;
1383 client_version.compression_algorithms = 0;
1384 client_version.compression_threshold = 0;
1385 client_version.feature_flags = 0;
1386
1387 result = packet_send_via_transport(session->acds_transport, PACKET_TYPE_PROTOCOL_VERSION, &client_version,
1388 sizeof(client_version), 0);
1389 if (result != ASCIICHAT_OK) {
1390 log_error("discovery_session_start: Failed to send protocol version to ACDS");
1391 set_error(session, result, "Failed to send protocol version to ACDS");
1392 return result;
1393 }
1394
1395 // Step 0.5: Receive CRYPTO_PARAMETERS from ACDS
1396 {
1397 packet_type_t pkt_type;
1398 void *pkt_data = NULL;
1399 size_t pkt_len = 0;
1400 void *alloc_buffer = NULL;
1401
1402 result = packet_receive_via_transport(session->acds_transport, &pkt_type, &pkt_data, &pkt_len, &alloc_buffer);
1403 if (result != ASCIICHAT_OK || pkt_type != PACKET_TYPE_CRYPTO_PARAMETERS) {
1404 log_error("discovery_session_start: Failed to receive CRYPTO_PARAMETERS (got type %u)", pkt_type);
1405 buffer_pool_free(NULL, alloc_buffer, 0);
1406 set_error(session, result != ASCIICHAT_OK ? result : ERROR_NETWORK_PROTOCOL,
1407 "Failed to receive CRYPTO_PARAMETERS");
1408 return result != ASCIICHAT_OK ? result : ERROR_NETWORK_PROTOCOL;
1409 }
1410
1411 if (pkt_len != sizeof(crypto_parameters_packet_t)) {
1412 log_error("discovery_session_start: Invalid CRYPTO_PARAMETERS size: %zu (expected %zu)", pkt_len,
1414 buffer_pool_free(NULL, alloc_buffer, 0);
1415 set_error(session, ERROR_NETWORK_PROTOCOL, "Invalid CRYPTO_PARAMETERS size");
1417 }
1418
1419 crypto_parameters_packet_t server_params;
1420 memcpy(&server_params, pkt_data, sizeof(crypto_parameters_packet_t));
1421 buffer_pool_free(NULL, alloc_buffer, 0);
1422
1423 // Pass params in network byte order — crypto_handshake_set_parameters
1424 // handles conversion internally for client (is_server=false)
1425 result = crypto_handshake_set_parameters(&handshake_ctx, &server_params);
1426 if (result != ASCIICHAT_OK) {
1427 log_error("discovery_session_start: Failed to set crypto parameters");
1428 set_error(session, result, "Failed to set crypto parameters");
1429 return result;
1430 }
1431 log_info("discovery_session_start: CRYPTO_PARAMETERS received and applied");
1432 }
1433
1434 // Step 1: Key exchange - receive KEY_EXCHANGE_INIT and respond
1435 {
1436 packet_type_t pkt_type;
1437 void *pkt_data = NULL;
1438 size_t pkt_len = 0;
1439 void *alloc_buffer = NULL;
1440
1441 result = packet_receive_via_transport(session->acds_transport, &pkt_type, &pkt_data, &pkt_len, &alloc_buffer);
1442 if (result != ASCIICHAT_OK) {
1443 log_error("discovery_session_start: Failed to receive KEY_EXCHANGE_INIT");
1444 set_error(session, result, "Handshake key exchange recv failed");
1445 return result;
1446 }
1447
1448 result = crypto_handshake_client_key_exchange(&handshake_ctx, session->acds_transport, pkt_type, pkt_data, pkt_len);
1449 buffer_pool_free(NULL, alloc_buffer, 0);
1450
1451 if (result != ASCIICHAT_OK) {
1452 log_error("discovery_session_start: Handshake key exchange failed");
1453 set_error(session, result, "Handshake key exchange failed");
1454 return result;
1455 }
1456 }
1457
1458 // Step 2: Receive CRYPTO_HANDSHAKE_COMPLETE from server to finalize handshake
1459 {
1460 packet_type_t pkt_type;
1461 void *pkt_data = NULL;
1462 size_t pkt_len = 0;
1463 void *alloc_buffer = NULL;
1464
1465 result = packet_receive_via_transport(session->acds_transport, &pkt_type, &pkt_data, &pkt_len, &alloc_buffer);
1466 if (result != ASCIICHAT_OK) {
1467 log_error("discovery_session_start: Failed to receive CRYPTO_HANDSHAKE_COMPLETE");
1468 set_error(session, result, "Failed to receive handshake complete");
1469 return result;
1470 }
1471
1472 if (pkt_type != PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
1473 log_error("discovery_session_start: Expected CRYPTO_HANDSHAKE_COMPLETE, got packet type %d", pkt_type);
1474 buffer_pool_free(NULL, alloc_buffer, 0);
1475 set_error(session, ERROR_NETWORK_PROTOCOL, "Invalid crypto handshake completion packet");
1477 }
1478
1479 buffer_pool_free(NULL, alloc_buffer, 0);
1480 }
1481
1482 // For ACDS, handshake completes after key exchange (no auth required)
1483 log_info("discovery_session_start: ACDS handshake complete");
1484 log_info("discovery_session_start: Crypto handshake with ACDS completed");
1485
1486 // Check again after handshake
1487 if (session->should_exit_callback && session->should_exit_callback(session->exit_callback_data)) {
1488 log_info("discovery_session_start: Exiting early due to should_exit_callback (after handshake)");
1489 return ASCIICHAT_OK; // Return success to allow clean shutdown
1490 }
1491
1492 // Create or join session
1493 if (session->is_initiator) {
1494 log_info("discovery_session_start: Calling create_session - participant_ctx before=%p", session->participant_ctx);
1495 result = create_session(session);
1496 log_info("discovery_session_start: create_session returned - participant_ctx after=%p, result=%d",
1497 session->participant_ctx, result);
1498
1499 if (result == ASCIICHAT_OK) {
1500 // Initiator also needs to register itself as a participant so it can receive PARTICIPANT_JOINED notifications
1501 // when other peers join. This makes the initiator discoverable to other participants.
1502 log_info("Initiator: now registering self as participant in created session");
1503 result = join_session(session);
1504 log_info("Initiator: join_session result=%d", result);
1505 }
1506 return result;
1507 } else {
1508 log_info("discovery_session_start: Calling join_session - participant_ctx before=%p", session->participant_ctx);
1509 asciichat_error_t result = join_session(session);
1510 log_info("discovery_session_start: join_session returned - participant_ctx after=%p, state=%d, result=%d",
1511 session->participant_ctx, session->state, result);
1512 return result;
1513 }
1514}
asciichat_error_t crypto_handshake_init(const char *name, crypto_handshake_context_t *ctx, bool is_server)
Initialize named crypto handshake context.
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
Set crypto parameters from crypto_parameters_packet_t.
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
uint8_t key[32]
Definition key_types.h:71
key_type_t type
Definition key_types.h:92
uint8_t ed25519[64]
Definition key_types.h:94
union private_key_t::@14 key
key_type_t type
Definition key_types.h:70
@ KEY_TYPE_ED25519
Definition key_types.h:52
@ PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE
Server -> Client: "encryption ready" (UNENCRYPTED)
Definition packet.h:323
@ PACKET_TYPE_CRYPTO_PARAMETERS
Server -> Client: Chosen algorithms + data sizes (UNENCRYPTED)
Definition packet.h:309
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Process server's public key and send our public key.
Cryptographic handshake context structure.
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:981

References discovery_session_t::acds_transport, ASCIICHAT_OK, buffer_pool_free(), crypto_handshake_context_t::client_private_key, crypto_handshake_context_t::client_public_key, protocol_version_packet_t::compression_algorithms, protocol_version_packet_t::compression_threshold, crypto_handshake_client_key_exchange(), crypto_handshake_init(), crypto_handshake_set_parameters(), DISCOVERY_STATE_FAILED, private_key_t::ed25519, ERROR_INVALID_PARAM, ERROR_NETWORK_PROTOCOL, discovery_session_t::exit_callback_data, protocol_version_packet_t::feature_flags, HOST_TO_NET_U16, discovery_session_t::identity_pubkey, discovery_session_t::identity_seckey, discovery_session_t::is_initiator, public_key_t::key, private_key_t::key, KEY_TYPE_ED25519, log_debug, log_error, log_info, packet_receive_via_transport(), packet_send_via_transport(), PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, PACKET_TYPE_CRYPTO_PARAMETERS, PACKET_TYPE_PROTOCOL_VERSION, discovery_session_t::participant_ctx, protocol_version_packet_t::protocol_revision, protocol_version_packet_t::protocol_version, discovery_session_t::session_string, SET_ERRNO, discovery_session_t::should_exit_callback, discovery_session_t::state, protocol_version_packet_t::supports_encryption, public_key_t::type, and private_key_t::type.

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 2303 of file src/discovery/session.c.

2303 {
2304 if (!session) {
2305 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2306 return ERROR_INVALID_PARAM;
2307 }
2308
2309 session->ring.last_ring_round_ms = session_get_current_time_ms();
2310
2311 if (session->is_host) {
2312 log_info("Starting 5-minute proactive election round (collecting NETWORK_QUALITY from participants)");
2313
2314 // HOST: Run proactive host election
2315 // 1. Collect NETWORK_QUALITY from all connected participants (TODO: request fresh data)
2316 // 2. Measure the host's own NAT quality
2317 // 3. Run deterministic election to determine future host
2318 // 4. Broadcast FUTURE_HOST_ELECTED to all participants via ACDS
2319 asciichat_error_t result = discovery_session_run_election(session);
2320 if (result != ASCIICHAT_OK) {
2321 log_error("Host election failed: %d", result);
2322 return result;
2323 }
2324
2325 return ASCIICHAT_OK;
2326 } else {
2327 log_debug("Ring round timer triggered (waiting for host to broadcast FUTURE_HOST_ELECTED)");
2328 return ASCIICHAT_OK;
2329 }
2330}

References ASCIICHAT_OK, ERROR_INVALID_PARAM, discovery_session_t::is_host, ring_consensus_t::last_ring_round_ms, log_debug, log_error, log_info, discovery_session_t::ring, and SET_ERRNO.

◆ discovery_session_stop()

void discovery_session_stop ( discovery_session_t *  session)

Stop the discovery session.

Parameters
sessionSession context

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

2123 {
2124 if (!session) {
2125 SET_ERRNO(ERROR_INVALID_PARAM, "session is NULL");
2126 return;
2127 }
2128
2129 log_info("Stopping discovery session...");
2130
2131 // Send SESSION_LEAVE if connected
2132 if (session->acds_transport && session->session_id[0] != 0) {
2133 acip_session_leave_t leave_msg;
2134 memset(&leave_msg, 0, sizeof(leave_msg));
2135 memcpy(leave_msg.session_id, session->session_id, 16);
2136 memcpy(leave_msg.participant_id, session->participant_id, 16);
2137
2138 packet_send_via_transport(session->acds_transport, PACKET_TYPE_ACIP_SESSION_LEAVE, &leave_msg, sizeof(leave_msg),
2139 0);
2140 }
2141
2142 set_state(session, DISCOVERY_STATE_ENDED);
2143}
acip_session_leave_t
Definition acds.h:524
@ PACKET_TYPE_ACIP_SESSION_LEAVE
Leave session (Client -> Discovery Server)
Definition packet.h:419

References discovery_session_t::acds_transport, acip_session_leave_t, DISCOVERY_STATE_ENDED, ERROR_INVALID_PARAM, log_info, packet_send_via_transport(), PACKET_TYPE_ACIP_SESSION_LEAVE, discovery_session_t::participant_id, discovery_session_t::session_id, and SET_ERRNO.

Referenced by discovery_main().