ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
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...

Macros

#define NETWORK_MDNS_DISCOVERY_ERROR_MESSAGE_BUFFER_SIZE   256

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[(32 *2+1)])
 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.

Macro Definition Documentation

◆ NETWORK_MDNS_DISCOVERY_ERROR_MESSAGE_BUFFER_SIZE

#define NETWORK_MDNS_DISCOVERY_ERROR_MESSAGE_BUFFER_SIZE   256

Definition at line 67 of file discovery.h.

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 541 of file discovery.c.

542 {
543 if (!session_string || !config || !result) {
544 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
545 return ERROR_INVALID_PARAM;
546 }
547
548 // Validate session string format
549 if (!is_session_string(session_string)) {
550 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid session string format");
551 return ERROR_INVALID_PARAM;
552 }
553
554 memset(result, 0, sizeof(*result));
555 log_info("Discovery: Looking up session '%s'", session_string);
556
557 // Initialize thread state
559 memset(&state, 0, sizeof(state));
560 state.result = result;
561 mutex_init(&state.lock, "mdns_resolve");
562 cond_init(&state.signal, "mdns_signal");
563
564 // Determine which discovery methods to use
565 bool use_mdns = true;
566 bool use_acds = config->expected_pubkey != NULL || config->insecure_mode;
567 if (config->insecure_mode && !config->expected_pubkey)
568 NOTICE(DANGER, "DISCOVERY HOST VERIFICATION DISABLED",
569 "Insecure discovery is enabled without an expected host key.\n"
570 "The discovered host identity cannot be verified; this is vulnerable to man-in-the-middle attacks.");
571
572 if (!use_acds) {
573 log_debug("Discovery: mDNS-only mode (no --server-key and no --acds-insecure)");
574 }
575
576 // Spawn mDNS thread
577 platform_thread_t mdns_thread;
578 thread_init(&mdns_thread);
579
580 if (use_mdns) {
581 mdns_thread_context_t *mdns_ctx = SAFE_MALLOC(sizeof(*mdns_ctx), mdns_thread_context_t *);
582 if (!mdns_ctx) {
583 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS context");
584 mutex_destroy(&state.lock);
585 cond_destroy(&state.signal);
586 return ERROR_MEMORY;
587 }
588
589 mdns_ctx->session_string = session_string;
590 mdns_ctx->state = &state;
591 mdns_ctx->expected_pubkey = config->expected_pubkey;
592
593 int thread_err = platform_thread_create(&mdns_thread, "mdns", mdns_thread_fn, mdns_ctx);
594 if (thread_err != 0) {
595 log_warn("Discovery: Failed to spawn mDNS thread");
596 SAFE_FREE(mdns_ctx);
597 use_mdns = false;
598 }
599 }
600
601 // Spawn ACDS thread
602 platform_thread_t acds_thread;
603 thread_init(&acds_thread);
604
605 if (use_acds) {
606 acds_thread_context_t *acds_ctx = SAFE_MALLOC(sizeof(*acds_ctx), acds_thread_context_t *);
607 if (!acds_ctx) {
608 SET_ERRNO(ERROR_MEMORY, "Failed to allocate ACDS context");
609 if (use_mdns && thread_is_initialized(&mdns_thread)) {
610 thread_join(&mdns_thread, NULL);
611 }
612 mutex_destroy(&state.lock);
613 cond_destroy(&state.signal);
614 return ERROR_MEMORY;
615 }
616
617 acds_ctx->session_string = session_string;
618 acds_ctx->state = &state;
619 acds_ctx->config = config;
620
621 int thread_err = platform_thread_create(&acds_thread, "acds_client", acds_thread_fn, acds_ctx);
622 if (thread_err != 0) {
623 log_warn("Discovery: Failed to spawn ACDS thread");
624 SAFE_FREE(acds_ctx);
625 use_acds = false;
626 }
627 }
628
629 // Wait for result (mDNS timeout 2s + ACDS timeout 5s max)
630 uint32_t wait_timeout_ms = config->acds_timeout_ms + 1000;
631 mutex_lock(&state.lock);
632 {
633 uint32_t elapsed_ms = 0;
634 while (!state.found && elapsed_ms < wait_timeout_ms) {
635 // Check if both threads are done
636 if (state.mdns_done && state.acds_done) {
637 break;
638 }
639
640 // Wait with timeout
641 cond_timedwait(&state.signal, &state.lock, 500 * NS_PER_MS_INT);
642 elapsed_ms += 500;
643 }
644 }
645 mutex_unlock(&state.lock);
646
647 // Join threads
648 if (use_mdns && thread_is_initialized(&mdns_thread)) {
649 thread_join(&mdns_thread, NULL);
650 }
651 if (use_acds && thread_is_initialized(&acds_thread)) {
652 thread_join(&acds_thread, NULL);
653 }
654
655 // Cleanup
656 mutex_destroy(&state.lock);
657 cond_destroy(&state.signal);
658
659 // Check result
660 if (!result->success) {
661 SET_ERRNO(ERROR_NOT_FOUND, "Session '%s' not found (mDNS/ACDS timeout)", session_string);
662 return ERROR_NOT_FOUND;
663 }
664
665 log_info("Discovery: Session '%s' discovered via %s", session_string,
666 result->source == DISCOVERY_SOURCE_MDNS ? "mDNS" : "ACDS");
667
668 return ASCIICHAT_OK;
669}
#define SET_ERRNO(code,...)
bool is_session_string(const char *str)
Check if a string is a valid session string.
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
@ ERROR_NOT_FOUND
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define NS_PER_MS_INT
Definition time.h:168
#define NOTICE(severity, title,...)
Definition notice.h:68
Context for ACDS discovery thread.
Definition discovery.c:379
discovery_thread_state_t * state
Shared discovery state.
Definition discovery.c:381
const discovery_config_t * config
Discovery configuration.
Definition discovery.c:382
const char * session_string
Session string to look up.
Definition discovery.c:380
uint32_t acds_timeout_ms
ACDS lookup timeout (default: 5000ms).
Definition discovery.h:132
bool insecure_mode
Allow no verification (–acds-insecure flag).
Definition discovery.h:124
const uint8_t * expected_pubkey
Expected server pubkey (NULL = no verification).
Definition discovery.h:123
enum discovery_result_t::@335075210033063074377072145063374373365157111022 source
Which discovery method found the server.
bool success
Discovery succeeded.
Definition discovery.h:93
Context for ACDS discovery thread.
Definition discovery.c:45
Context for mDNS discovery thread.
Definition discovery.c:299
discovery_thread_state_t * state
Shared discovery state.
Definition discovery.c:301
const uint8_t * expected_pubkey
Expected server public key (optional).
Definition discovery.c:302
const char * session_string
Session string to discover.
Definition discovery.c:300

References discovery_config_t::acds_timeout_ms, ASCIICHAT_OK, 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_config_t::insecure_mode, is_session_string(), log_debug, log_info, log_warn, NOTICE, NS_PER_MS_INT, SAFE_FREE, SAFE_MALLOC, acds_thread_context_t::session_string, mdns_thread_context_t::session_string, SET_ERRNO, discovery_result_t::source, acds_thread_context_t::state, mdns_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 519 of file discovery.c.

519 {
520 if (!config)
521 return;
522
523 memset(config, 0, sizeof(*config));
524
525 // Check if debug or release build
526#ifdef NDEBUG
527 // Release: use internet ACDS
528 SAFE_STRNCPY(config->acds_server, "discovery.ascii-chat.com", sizeof(config->acds_server) - 1);
529#else
530 // Debug: use local ACDS
531 SAFE_STRNCPY(config->acds_server, "127.0.0.1", sizeof(config->acds_server) - 1);
532#endif
533
535 config->mdns_timeout_ms = 2 * MS_PER_SEC_INT;
536 config->acds_timeout_ms = 5 * MS_PER_SEC_INT;
537 config->insecure_mode = false;
538 config->expected_pubkey = NULL;
539}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define MS_PER_SEC_INT
Definition time.h:176
#define OPT_ACDS_PORT_INT_DEFAULT
Default ACDS discovery service port (integer).
Definition options.h:594
uint32_t mdns_timeout_ms
mDNS search timeout (default: 2000ms)
Definition discovery.h:131
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:127

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 288 of file discovery.c.

288 {
289 SAFE_FREE(servers);
290}

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 192 of file discovery.c.

192 {
193 if (!out_count) {
194 SET_ERRNO(ERROR_INVALID_PARAM, "out_count pointer is NULL");
195 return NULL;
196 }
197
198 *out_count = 0;
199
200 // Apply defaults
201 if (timeout_ms <= 0) {
202 timeout_ms = 2 * MS_PER_SEC_INT;
203 }
204 if (max_servers <= 0) {
205 max_servers = 20;
206 }
207
208 // Allocate state for collecting servers
209 mdns_query_state_t state;
210 memset(&state, 0, sizeof(state));
211 state.capacity = max_servers;
212 state.timeout_ms = timeout_ms;
213 state.start_time_ms = (int64_t)time_ns_to_ms(time_get_ns());
214
215 // Allocate server array
216 state.servers = SAFE_MALLOC((size_t)state.capacity * sizeof(ui_mdns_server_t), ui_mdns_server_t *);
217 if (!state.servers) {
218 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS discovery server array");
219 return NULL;
220 }
221 memset(state.servers, 0, state.capacity * sizeof(ui_mdns_server_t));
222
223 if (!quiet) {
224 log_info("mDNS: Searching for ascii-chat servers on local network (timeout: %dms)", state.timeout_ms);
225 printf("🔍 Searching for ascii-chat servers on LAN...\n");
226 }
227
228 // Discovery is retried in the live picker; failures remain in logs.
230 bool previous_notifications = errno_notifications_suppress(true);
231 // Initialize mDNS
233 if (!mdns) {
234 log_warn("mDNS: Failed to initialize mDNS - discovery unavailable");
235 errno_notifications_suppress(previous_notifications);
237 return state.servers;
238 }
239
240 // Start mDNS query for _ascii-chat._tcp services
241 asciichat_error_t query_result =
242 asciichat_mdns_query(mdns, "_ascii-chat._tcp.local", discovery_mdns_callback, &state);
243
244 if (query_result != ASCIICHAT_OK) {
245 log_info("mDNS: Query failed - no servers found via service discovery");
247 errno_notifications_suppress(previous_notifications);
249 return state.servers;
250 }
251
252 // Poll for responses until timeout
253 int64_t deadline = state.start_time_ms + state.timeout_ms;
254 while (!state.query_complete && (int64_t)time_ns_to_ms(time_get_ns()) < deadline) {
255 int poll_timeout = (int)(deadline - (int64_t)time_ns_to_ms(time_get_ns()));
256 if (poll_timeout < 0) {
257 poll_timeout = 0;
258 }
259 if (poll_timeout > 100) {
260 poll_timeout = 100; // Check every 100ms
261 }
262 if (asciichat_mdns_update(mdns, poll_timeout) != ASCIICHAT_OK)
263 break;
264 }
265
266 // Cleanup mDNS
268 errno_notifications_suppress(previous_notifications);
270
271 if (!quiet) {
272 if (state.count > 0) {
273 printf("✅ Found %d ascii-chat server%s on LAN\n", state.count, state.count == 1 ? "" : "s");
274 log_info("mDNS: Found %d server(s)", state.count);
275 } else {
276 printf("❌ No ascii-chat servers found on LAN\n");
277 log_info("mDNS: No servers found");
278 }
279 }
280
281 *out_count = state.count;
282 return state.servers;
283}
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
bool errno_notifications_suppress(bool suppress)
asciichat_errno_scope_t errno_scope_begin(void)
@ ASCIICHAT_ERRNO_HANDLED
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
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.
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
Owns one mDNS browse or advertisement session, including its socket, event-loop registration,...
Internal state for collecting discovered services.
Definition discovery.c:107
Discovered server information from mDNS.
Definition tui/mdns.h:66

References ASCIICHAT_ERRNO_HANDLED, asciichat_mdns_destroy(), asciichat_mdns_init(), asciichat_mdns_query(), asciichat_mdns_update(), ASCIICHAT_OK, errno_notifications_suppress(), errno_scope_begin(), errno_scope_end(), ERROR_INVALID_PARAM, ERROR_MEMORY, log_info, log_warn, MS_PER_SEC_INT, SAFE_MALLOC, and SET_ERRNO.

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 67 of file discovery.c.

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

References ASCIICHAT_OK, ED25519_PUBLIC_KEY_SIZE, ERROR_INVALID_PARAM, and SET_ERRNO.

◆ pubkey_to_hex()

void pubkey_to_hex ( const uint8_t pubkey[32],
char hex_out[(32 *2+1)] )

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 58 of file discovery.c.

58 {
59 if (!pubkey || !hex_out)
60 return;
61 for (int i = 0; i < 32; i++) {
62 safe_snprintf(hex_out + (i * 2), 3, "%02x", pubkey[i]);
63 }
64 hex_out[64] = '\0';
65}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.

References ED25519_PUBLIC_KEY_SIZE, HEX_STRING_SIZE_32, and safe_snprintf().