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

Go to the source code of this file.

Data Structures

struct  discovery_result_t
 Result from session discovery. More...
 
struct  discovery_config_t
 Configuration for discovery session. More...
 

Functions

void discovery_config_init_defaults (discovery_config_t *config)
 Initialize discovery config with defaults.
 
asciichat_error_t discover_session_parallel (const char *session_string, const discovery_config_t *config, discovery_result_t *result)
 Look up session in parallel on mDNS and ACDS.
 
ui_mdns_server_t * discovery_mdns_query (int timeout_ms, int max_servers, bool quiet, int *out_count)
 Discover ascii-chat servers on local network via mDNS.
 
void discovery_mdns_destroy (ui_mdns_server_t *servers)
 Free memory from mDNS discovery results.
 
void pubkey_to_hex (const uint8_t pubkey[32], char hex_out[65])
 Convert Ed25519 public key to hex string.
 
asciichat_error_t hex_to_pubkey (const char *hex_str, uint8_t pubkey_out[32])
 Convert hex string to Ed25519 public key.
 

Function Documentation

◆ discover_session_parallel()

asciichat_error_t discover_session_parallel ( const char *  session_string,
const discovery_config_t *  config,
discovery_result_t *  result 
)

Look up session in parallel on mDNS and ACDS.

Spawns two concurrent threads to search both mDNS (local LAN) and ACDS (internet). Returns immediately when either finds the session, cancelling the other search.

Verification Logic:

If config->expected_pubkey is set:

  • mDNS result: Verify TXT record contains matching host_pubkey
  • ACDS result: Verify SESSION_LOOKUP response contains matching host_pubkey
  • If both find result: Verify they have same pubkey (cross-check)
  • On mismatch: Return ERROR_CRYPTO_VERIFY_FAILED

If config->expected_pubkey is NULL but config->insecure_mode is true:

  • Skip all verification, accept first result found
  • WARNING: Vulnerable to MITM attacks!

If config->expected_pubkey is NULL and config->insecure_mode is false:

  • Search mDNS only (ACDS thread not started)
  • Use TOFU (Trust On First Use): accept first mDNS result found

Thread Lifecycle:

  • Both threads start immediately
  • Whichever completes first signals success via condition variable
  • Other thread is joined (cleaned up)
  • Function returns with result
Parameters
session_stringSession string to find (e.g., "swift-river-mountain")
configDiscovery configuration
resultDiscovery result (output)
Returns
ASCIICHAT_OK on success, error code on failure
Note
This function BLOCKS until one discovery method succeeds or both timeout
Not thread-safe for concurrent calls - use only one discovery at a time

Definition at line 516 of file discovery.c.

517 {
518 if (!session_string || !config || !result) {
519 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to discover_session_parallel");
520 return ERROR_INVALID_PARAM;
521 }
522
523 // Validate session string format
524 if (!is_session_string(session_string)) {
525 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid session string format");
526 return ERROR_INVALID_PARAM;
527 }
528
529 memset(result, 0, sizeof(*result));
530 log_info("Discovery: Looking up session '%s'", session_string);
531
532 // Initialize thread state
534 memset(&state, 0, sizeof(state));
535 state.result = result;
536 mutex_init(&state.lock, "mdns_resolve");
537 cond_init(&state.signal, "mdns_signal");
538
539 // Determine which discovery methods to use
540 bool use_mdns = true;
541 bool use_acds = config->expected_pubkey != NULL || config->insecure_mode;
542
543 if (!use_acds) {
544 log_debug("Discovery: mDNS-only mode (no --server-key and no --acds-insecure)");
545 }
546
547 // Spawn mDNS thread
548 asciichat_thread_t mdns_thread;
549 asciichat_thread_init(&mdns_thread);
550
551 if (use_mdns) {
552 mdns_thread_context_t *mdns_ctx = SAFE_MALLOC(sizeof(*mdns_ctx), mdns_thread_context_t *);
553 if (!mdns_ctx) {
554 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS context");
555 mutex_destroy(&state.lock);
556 cond_destroy(&state.signal);
557 return ERROR_MEMORY;
558 }
559
560 mdns_ctx->session_string = session_string;
561 mdns_ctx->state = &state;
562 mdns_ctx->expected_pubkey = config->expected_pubkey;
563
564 int thread_err = asciichat_thread_create(&mdns_thread, "mdns", mdns_thread_fn, mdns_ctx);
565 if (thread_err != 0) {
566 log_warn("Discovery: Failed to spawn mDNS thread");
567 SAFE_FREE(mdns_ctx);
568 use_mdns = false;
569 }
570 }
571
572 // Spawn ACDS thread
573 asciichat_thread_t acds_thread;
574 asciichat_thread_init(&acds_thread);
575
576 if (use_acds) {
577 acds_thread_context_t *acds_ctx = SAFE_MALLOC(sizeof(*acds_ctx), acds_thread_context_t *);
578 if (!acds_ctx) {
579 SET_ERRNO(ERROR_MEMORY, "Failed to allocate ACDS context");
580 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
581 asciichat_thread_join(&mdns_thread, NULL);
582 }
583 mutex_destroy(&state.lock);
584 cond_destroy(&state.signal);
585 return ERROR_MEMORY;
586 }
587
588 acds_ctx->session_string = session_string;
589 acds_ctx->state = &state;
590 acds_ctx->config = config;
591
592 int thread_err = asciichat_thread_create(&acds_thread, "acds_client", acds_thread_fn, acds_ctx);
593 if (thread_err != 0) {
594 log_warn("Discovery: Failed to spawn ACDS thread");
595 SAFE_FREE(acds_ctx);
596 use_acds = false;
597 }
598 }
599
600 // Wait for result (mDNS timeout 2s + ACDS timeout 5s max)
601 uint32_t wait_timeout_ms = config->acds_timeout_ms + 1000;
602 mutex_lock(&state.lock);
603 {
604 uint32_t elapsed_ms = 0;
605 while (!state.found && elapsed_ms < wait_timeout_ms) {
606 // Check if both threads are done
607 if (state.mdns_done && state.acds_done) {
608 break;
609 }
610
611 // Wait with timeout
612 cond_timedwait(&state.signal, &state.lock, 500 * NS_PER_MS_INT);
613 elapsed_ms += 500;
614 }
615 }
616 mutex_unlock(&state.lock);
617
618 // Join threads
619 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
620 asciichat_thread_join(&mdns_thread, NULL);
621 }
622 if (use_acds && asciichat_thread_is_initialized(&acds_thread)) {
623 asciichat_thread_join(&acds_thread, NULL);
624 }
625
626 // Cleanup
627 mutex_destroy(&state.lock);
628 cond_destroy(&state.signal);
629
630 // Check result
631 if (!result->success) {
632 SET_ERRNO(ERROR_NOT_FOUND, "Session '%s' not found (mDNS/ACDS timeout)", session_string);
633 return ERROR_NOT_FOUND;
634 }
635
636 log_info("Discovery: Session '%s' discovered via %s", session_string,
637 result->source == DISCOVERY_SOURCE_MDNS ? "mDNS" : "ACDS");
638
639 return ASCIICHAT_OK;
640}
bool is_session_string(const char *str)
Check if a string is a valid session string.
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_NOT_FOUND
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define NS_PER_MS_INT
Definition time.h:156
void asciichat_thread_init(asciichat_thread_t *thread)
Initialize a thread handle to an uninitialized state.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
bool asciichat_thread_is_initialized(asciichat_thread_t *thread)
Check if a thread handle has been initialized.
int cond_init(cond_t *cond, const char *name)
Initialize a condition variable with a name.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
#define cond_timedwait(cond, mutex, timeout_ns)
Wait on a condition variable with timeout (with debug tracking in debug builds)
Definition cond.h:300
int cond_destroy(cond_t *cond)
Destroy a condition variable.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
void * asciichat_thread_t
#define asciichat_thread_join(thread, timeout_ms)
Context for ACDS discovery thread.
Definition discovery.c:356
discovery_thread_state_t * state
Shared discovery state.
Definition discovery.c:358
const discovery_config_t * config
Discovery configuration.
Definition discovery.c:359
const char * session_string
Session string to look up.
Definition discovery.c:357
uint32_t acds_timeout_ms
ACDS lookup timeout (default: 5000ms)
Definition discovery.h:123
bool insecure_mode
Allow no verification (–acds-insecure flag)
Definition discovery.h:115
const uint8_t * expected_pubkey
Expected server pubkey (NULL = no verification)
Definition discovery.h:114
bool success
Discovery succeeded.
Definition discovery.h:87
enum discovery_result_t::@15 source
Which discovery method found the server.
Thread-safe result sharing between discovery threads.
Definition discovery.c:36
bool found
Whether a session was found.
Definition discovery.c:42
discovery_result_t * result
Shared discovery result.
Definition discovery.c:39
cond_t signal
Condition variable for signaling.
Definition discovery.c:38
bool mdns_done
Whether mDNS discovery completed.
Definition discovery.c:40
mutex_t lock
Mutex for thread-safe access.
Definition discovery.c:37
bool acds_done
Whether ACDS discovery completed.
Definition discovery.c:41
Context for mDNS discovery thread.
Definition discovery.c:276
discovery_thread_state_t * state
Shared discovery state.
Definition discovery.c:278
const uint8_t * expected_pubkey
Expected server public key (optional)
Definition discovery.c:279
const char * session_string
Session string to discover.
Definition discovery.c:277

References discovery_thread_state_t::acds_done, discovery_config_t::acds_timeout_ms, ASCIICHAT_OK, asciichat_thread_create, asciichat_thread_init(), asciichat_thread_is_initialized(), asciichat_thread_join, cond_destroy(), cond_init(), cond_timedwait, acds_thread_context_t::config, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_NOT_FOUND, discovery_config_t::expected_pubkey, mdns_thread_context_t::expected_pubkey, discovery_thread_state_t::found, discovery_config_t::insecure_mode, is_session_string(), discovery_thread_state_t::lock, log_debug, log_info, log_warn, discovery_thread_state_t::mdns_done, mutex_destroy(), mutex_init(), mutex_lock, mutex_unlock, NS_PER_MS_INT, discovery_thread_state_t::result, SAFE_FREE, SAFE_MALLOC, mdns_thread_context_t::session_string, acds_thread_context_t::session_string, SET_ERRNO, discovery_thread_state_t::signal, discovery_result_t::source, mdns_thread_context_t::state, acds_thread_context_t::state, and discovery_result_t::success.

◆ discovery_config_init_defaults()

void discovery_config_init_defaults ( discovery_config_t *  config)

Initialize discovery config with defaults.

Sets sensible defaults:

  • ACDS server: localhost:27225 (debug), discovery.ascii-chat.com (release)
  • Timeouts: mdns=2000ms, acds=5000ms
  • Verification: None (expected_pubkey=NULL)
  • Insecure mode: false
Parameters
configConfiguration to initialize

Definition at line 494 of file discovery.c.

494 {
495 if (!config)
496 return;
497
498 memset(config, 0, sizeof(*config));
499
500 // Check if debug or release build
501#ifdef NDEBUG
502 // Release: use internet ACDS
503 SAFE_STRNCPY(config->acds_server, "discovery.ascii-chat.com", sizeof(config->acds_server) - 1);
504#else
505 // Debug: use local ACDS
506 SAFE_STRNCPY(config->acds_server, "127.0.0.1", sizeof(config->acds_server) - 1);
507#endif
508
510 config->mdns_timeout_ms = 2 * MS_PER_SEC_INT;
511 config->acds_timeout_ms = 5 * MS_PER_SEC_INT;
512 config->insecure_mode = false;
513 config->expected_pubkey = NULL;
514}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define MS_PER_SEC_INT
Definition time.h:164
#define OPT_ACDS_PORT_INT_DEFAULT
Default ACDS discovery service port (integer)
uint32_t mdns_timeout_ms
mDNS search timeout (default: 2000ms)
Definition discovery.h:122
uint16_t acds_port
ACDS port (default: 27225)
char acds_server[256]
ACDS server address (e.g., "localhost" or "discovery.ascii-chat.com")
Definition discovery.h:118

References discovery_config_t::acds_port, discovery_config_t::acds_server, discovery_config_t::acds_timeout_ms, discovery_config_t::expected_pubkey, discovery_config_t::insecure_mode, discovery_config_t::mdns_timeout_ms, MS_PER_SEC_INT, OPT_ACDS_PORT_INT_DEFAULT, and SAFE_STRNCPY.

◆ discovery_mdns_destroy()

void discovery_mdns_destroy ( ui_mdns_server_t *  servers)

Free memory from mDNS discovery results.

Definition at line 265 of file discovery.c.

265 {
266 SAFE_FREE(servers);
267}

References SAFE_FREE.

Referenced by ui_mdns_free_results().

◆ discovery_mdns_query()

ui_mdns_server_t * discovery_mdns_query ( int  timeout_ms,
int  max_servers,
bool  quiet,
int *  out_count 
)

Discover ascii-chat servers on local network via mDNS.

Queries for mDNS _ascii-chat._tcp services and returns discovered servers. This is the core discovery function used by both:

  • Parallel discovery threads (discover_session_parallel)
  • TUI wrapper (lan_discovery_query)
Parameters
timeout_msQuery timeout in milliseconds (default: 2000)
max_serversMaximum servers to discover (default: 20)
quietIf true, suppresses progress messages
out_countOutput: number of servers discovered
Returns
Array of discovered servers, or NULL on error. Use discovery_mdns_destroy() to free.

Discover ascii-chat servers on local network via mDNS.

Parameters
timeout_msQuery timeout in milliseconds
max_serversMaximum servers to discover
quietIf true, suppresses progress messages
out_countOutput: number of servers discovered
Returns
Array of discovered servers, or NULL on error. Use discovery_mdns_destroy() to free.

Definition at line 179 of file discovery.c.

179 {
180 if (!out_count) {
181 SET_ERRNO(ERROR_INVALID_PARAM, "out_count pointer is NULL");
182 return NULL;
183 }
184
185 *out_count = 0;
186
187 // Apply defaults
188 if (timeout_ms <= 0) {
189 timeout_ms = 2 * MS_PER_SEC_INT;
190 }
191 if (max_servers <= 0) {
192 max_servers = 20;
193 }
194
195 // Allocate state for collecting servers
196 mdns_query_state_t state;
197 memset(&state, 0, sizeof(state));
198 state.capacity = max_servers;
199 state.timeout_ms = timeout_ms;
200 state.start_time_ms = (int64_t)time_ns_to_ms(time_get_ns());
201
202 // Allocate server array
203 state.servers = SAFE_MALLOC((size_t)state.capacity * sizeof(ui_mdns_server_t), ui_mdns_server_t *);
204 if (!state.servers) {
205 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS discovery server array");
206 return NULL;
207 }
208 memset(state.servers, 0, state.capacity * sizeof(ui_mdns_server_t));
209
210 if (!quiet) {
211 log_info("mDNS: Searching for ascii-chat servers on local network (timeout: %dms)", state.timeout_ms);
212 printf("🔍 Searching for ascii-chat servers on LAN...\n");
213 }
214
215 // Initialize mDNS
217 if (!mdns) {
218 log_warn("mDNS: Failed to initialize mDNS - discovery unavailable");
219 return state.servers;
220 }
221
222 // Start mDNS query for _ascii-chat._tcp services
223 asciichat_error_t query_result =
224 asciichat_mdns_query(mdns, "_ascii-chat._tcp.local", discovery_mdns_callback, &state);
225
226 if (query_result != ASCIICHAT_OK) {
227 log_info("mDNS: Query failed - no servers found via service discovery");
229 return state.servers;
230 }
231
232 // Poll for responses until timeout
233 int64_t deadline = state.start_time_ms + state.timeout_ms;
234 while (!state.query_complete && (int64_t)time_ns_to_ms(time_get_ns()) < deadline) {
235 int poll_timeout = (int)(deadline - (int64_t)time_ns_to_ms(time_get_ns()));
236 if (poll_timeout < 0) {
237 poll_timeout = 0;
238 }
239 if (poll_timeout > 100) {
240 poll_timeout = 100; // Check every 100ms
241 }
242 asciichat_mdns_update(mdns, poll_timeout);
243 }
244
245 // Cleanup mDNS
247
248 if (!quiet) {
249 if (state.count > 0) {
250 printf("✅ Found %d ascii-chat server%s on LAN\n", state.count, state.count == 1 ? "" : "s");
251 log_info("mDNS: Found %d server(s)", state.count);
252 } else {
253 printf("❌ No ascii-chat servers found on LAN\n");
254 log_info("mDNS: No servers found");
255 }
256 }
257
258 *out_count = state.count;
259 return state.servers;
260}
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
asciichat_error_t asciichat_mdns_update(asciichat_mdns_t *mdns, int timeout_ms)
Process pending mDNS events (must be called regularly)
void asciichat_mdns_destroy(asciichat_mdns_t *mdns)
Shutdown mDNS context and cleanup.
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
asciichat_error_t asciichat_mdns_query(asciichat_mdns_t *mdns, const char *service_type, asciichat_mdns_discovery_callback_fn callback, void *user_data)
Query for services on the local network.
Internal mDNS context structure.
Internal state for collecting discovered services.
Definition discovery.c:98
ui_mdns_server_t * servers
Array of discovered servers.
Definition discovery.c:99
int count
Number of servers discovered so far.
Definition discovery.c:100
int64_t start_time_ms
When discovery started (for timeout)
Definition discovery.c:102
bool query_complete
Set when discovery completes.
Definition discovery.c:104
int capacity
Allocated capacity.
Definition discovery.c:101
int timeout_ms
Discovery timeout in milliseconds.
Definition discovery.c:103
Discovered server information from mDNS.
Definition ui/mdns.h:57

References asciichat_mdns_destroy(), asciichat_mdns_init(), asciichat_mdns_query(), asciichat_mdns_update(), ASCIICHAT_OK, mdns_query_state_t::capacity, mdns_query_state_t::count, ERROR_INVALID_PARAM, ERROR_MEMORY, log_info, log_warn, MS_PER_SEC_INT, mdns_query_state_t::query_complete, SAFE_MALLOC, mdns_query_state_t::servers, SET_ERRNO, mdns_query_state_t::start_time_ms, time_get_ns(), and mdns_query_state_t::timeout_ms.

Referenced by ui_mdns_query().

◆ hex_to_pubkey()

asciichat_error_t hex_to_pubkey ( const char *  hex_str,
uint8_t  pubkey_out[32] 
)

Convert hex string to Ed25519 public key.

Converts 64-character hex string to 32-byte binary pubkey.

Parameters
hex_strHex string (64 characters)
pubkey_outOutput binary pubkey (32 bytes)
Returns
ASCIICHAT_OK on success, ERROR_INVALID_PARAM if invalid hex

Definition at line 58 of file discovery.c.

58 {
59 if (!hex_str || !pubkey_out) {
60 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid hex_str or pubkey_out pointer");
62 }
63
64 if (strlen(hex_str) != 64) {
65 SET_ERRNO(ERROR_INVALID_PARAM, "Hex string must be exactly 64 characters");
67 }
68
69 for (int i = 0; i < 32; i++) {
70 char hex_byte[3];
71 hex_byte[0] = hex_str[i * 2];
72 hex_byte[1] = hex_str[i * 2 + 1];
73 hex_byte[2] = '\0';
74
75 if (!isxdigit(hex_byte[0]) || !isxdigit(hex_byte[1])) {
76 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid hex character in string");
78 }
79
80 pubkey_out[i] = (uint8_t)strtol(hex_byte, NULL, 16);
81 }
82
83 return ASCIICHAT_OK;
84}
unsigned char uint8_t
Definition common.h:56

References ASCIICHAT_OK, ERROR_INVALID_PARAM, and SET_ERRNO.

◆ pubkey_to_hex()

void pubkey_to_hex ( const uint8_t  pubkey[32],
char  hex_out[65] 
)

Convert Ed25519 public key to hex string.

Converts 32-byte binary pubkey to lowercase hex (64 characters).

Parameters
pubkeyBinary public key (32 bytes)
hex_outOutput hex string buffer (must be at least 65 bytes)

Definition at line 49 of file discovery.c.

49 {
50 if (!pubkey || !hex_out)
51 return;
52 for (int i = 0; i < 32; i++) {
53 safe_snprintf(hex_out + (i * 2), 3, "%02x", pubkey[i]);
54 }
55 hex_out[64] = '\0';
56}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148

References safe_snprintf().

Referenced by client_crypto_handshake(), and server_crypto_handshake().