ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
discovery.c
Go to the documentation of this file.
1
16#include <ascii-chat/network/mdns/discovery.h>
17#include <ascii-chat/network/mdns/discovery_tui.h> // For discovery_tui_server_t struct only
18#include <ascii-chat/network/acip/acds_client.h>
19#include <ascii-chat/platform/thread.h>
20#include <ascii-chat/platform/mutex.h>
21#include <ascii-chat/log/logging.h>
22#include <ascii-chat/network/mdns/mdns.h>
23#include <ascii-chat/util/time.h>
24#include <string.h>
25#include <ctype.h>
26#include <stdlib.h>
27#include <time.h>
28
29// ============================================================================
30// Thread Coordination
31// ============================================================================
32
36typedef struct {
37 mutex_t lock;
38 cond_t signal;
39 discovery_result_t *result;
40 bool mdns_done;
41 bool acds_done;
42 bool found;
44
45// ============================================================================
46// Utility Functions
47// ============================================================================
48
49void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65]) {
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}
57
58asciichat_error_t hex_to_pubkey(const char *hex_str, uint8_t pubkey_out[32]) {
59 if (!hex_str || !pubkey_out) {
60 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid hex_str or pubkey_out pointer");
61 return ERROR_INVALID_PARAM;
62 }
63
64 if (strlen(hex_str) != 64) {
65 SET_ERRNO(ERROR_INVALID_PARAM, "Hex string must be exactly 64 characters");
66 return ERROR_INVALID_PARAM;
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");
77 return ERROR_INVALID_PARAM;
78 }
79
80 pubkey_out[i] = (uint8_t)strtol(hex_byte, NULL, 16);
81 }
82
83 return ASCIICHAT_OK;
84}
85
86// NOTE: is_session_string() has been moved to lib/discovery/strings.c with enhanced
87// validation against cached wordlists. See that module for the implementation.
88
89// ============================================================================
90// TXT Record Parsing
91// ============================================================================
92// mDNS Query Implementation (Core Module)
93// ============================================================================
94
98typedef struct {
99 discovery_tui_server_t *servers;
100 int count;
106
111static void discovery_mdns_callback(const asciichat_mdns_discovery_t *discovery, void *user_data) {
112 mdns_query_state_t *state = (mdns_query_state_t *)user_data;
113 if (!state || !discovery) {
114 return;
115 }
116
117 // Check if we've exceeded capacity
118 if (state->count >= state->capacity) {
119 log_warn("mDNS: Reached maximum server capacity (%d)", state->capacity);
120 return;
121 }
122
123 // Check if timeout exceeded
124 int64_t elapsed = (int64_t)time_ns_to_ms(time_get_ns()) - state->start_time_ms;
125 if (elapsed > state->timeout_ms) {
126 state->query_complete = true;
127 return;
128 }
129
130 // Only accept services of the right type
131 if (strstr(discovery->type, "_ascii-chat._tcp") == NULL) {
132 return;
133 }
134
135 // Check if we already have this server (avoid duplicates)
136 for (int i = 0; i < state->count; i++) {
137 if (strcmp(state->servers[i].name, discovery->name) == 0 && state->servers[i].port == discovery->port) {
138 // Update TTL if newer
139 if (discovery->ttl > state->servers[i].ttl) {
140 state->servers[i].ttl = discovery->ttl;
141 }
142 return; // Already have this server
143 }
144 }
145
146 // Add new server to our array
147 discovery_tui_server_t *server = &state->servers[state->count];
148 memset(server, 0, sizeof(discovery_tui_server_t));
149
150 // Copy service information
151 SAFE_STRNCPY(server->name, discovery->name, sizeof(server->name));
152 SAFE_STRNCPY(server->ipv4, discovery->ipv4, sizeof(server->ipv4));
153 SAFE_STRNCPY(server->ipv6, discovery->ipv6, sizeof(server->ipv6));
154 server->port = discovery->port;
155 server->ttl = discovery->ttl;
156
157 // Prefer IPv4 address as the primary address, fall back to hostname
158 if (discovery->ipv4[0] != '\0') {
159 SAFE_STRNCPY(server->address, discovery->ipv4, sizeof(server->address));
160 } else if (discovery->host[0] != '\0') {
161 SAFE_STRNCPY(server->address, discovery->host, sizeof(server->address));
162 } else if (discovery->ipv6[0] != '\0') {
163 SAFE_STRNCPY(server->address, discovery->ipv6, sizeof(server->address));
164 }
165
166 state->count++;
167 log_debug("mDNS: Found server '%s' at %s:%u", discovery->name, server->address, discovery->port);
168}
169
179discovery_tui_server_t *discovery_mdns_query(int timeout_ms, int max_servers, bool quiet, int *out_count) {
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(discovery_tui_server_t), discovery_tui_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(discovery_tui_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 SAFE_FREE(state.servers);
220 return NULL;
221 }
222
223 // Start mDNS query for _ascii-chat._tcp services
224 asciichat_error_t query_result =
225 asciichat_mdns_query(mdns, "_ascii-chat._tcp.local", discovery_mdns_callback, &state);
226
227 if (query_result != ASCIICHAT_OK) {
228 log_info("mDNS: Query failed - no servers found via service discovery");
230 SAFE_FREE(state.servers);
231 return NULL;
232 }
233
234 // Poll for responses until timeout
235 int64_t deadline = state.start_time_ms + state.timeout_ms;
236 while (!state.query_complete && (int64_t)time_ns_to_ms(time_get_ns()) < deadline) {
237 int poll_timeout = (int)(deadline - (int64_t)time_ns_to_ms(time_get_ns()));
238 if (poll_timeout < 0) {
239 poll_timeout = 0;
240 }
241 if (poll_timeout > 100) {
242 poll_timeout = 100; // Check every 100ms
243 }
244 asciichat_mdns_update(mdns, poll_timeout);
245 }
246
247 // Cleanup mDNS
249
250 if (!quiet) {
251 if (state.count > 0) {
252 printf("✅ Found %d ascii-chat server%s on LAN\n", state.count, state.count == 1 ? "" : "s");
253 log_info("mDNS: Found %d server(s)", state.count);
254 } else {
255 printf("❌ No ascii-chat servers found on LAN\n");
256 log_info("mDNS: No servers found");
257 }
258 }
259
260 *out_count = state.count;
261 return state.servers;
262}
263
267void discovery_mdns_destroy(discovery_tui_server_t *servers) {
268 SAFE_FREE(servers);
269}
270
271// ============================================================================
272// mDNS Discovery Thread
273// ============================================================================
274
284
286static void *mdns_thread_fn(void *arg) {
288 if (!ctx)
289 return NULL;
290
291 // Call the public mDNS query function in this module
292 int discovered_count = 0;
293 discovery_tui_server_t *discovered_servers =
294 discovery_mdns_query((int)(ctx->timeout_ms > 0 ? ctx->timeout_ms : 2000), 20, true, &discovered_count);
295
296 if (!discovered_servers || discovered_count == 0) {
297 log_debug("mDNS: No servers found (this is normal if no servers are on LAN)");
298 if (discovered_servers) {
299 discovery_mdns_destroy(discovered_servers);
300 }
301 mutex_lock(&ctx->state->lock);
302 ctx->state->mdns_done = true;
303 cond_signal(&ctx->state->signal);
304 mutex_unlock(&ctx->state->lock);
305 SAFE_FREE(ctx);
306 return NULL;
307 }
308
309 // Search for server matching our session string
310 for (int i = 0; i < discovered_count; i++) {
311 discovery_tui_server_t *server = &discovered_servers[i];
312
313 if (strcmp(server->name, ctx->session_string) == 0) {
314 // Found a match!
315 mutex_lock(&ctx->state->lock);
316 {
317 if (!ctx->state->found) {
318 ctx->state->found = true;
319 ctx->state->result->success = true;
320 ctx->state->result->source = DISCOVERY_SOURCE_MDNS;
321 ctx->state->result->server_port = server->port;
322
323 const char *best_addr = (server->ipv4[0] != '\0') ? server->ipv4 : server->ipv6;
324 SAFE_STRNCPY(ctx->state->result->server_address, best_addr, sizeof(ctx->state->result->server_address) - 1);
325 ctx->state->result->server_address[sizeof(ctx->state->result->server_address) - 1] = '\0';
326
327 SAFE_STRNCPY(ctx->state->result->mdns_service_name, server->name,
328 sizeof(ctx->state->result->mdns_service_name) - 1);
329 ctx->state->result->mdns_service_name[sizeof(ctx->state->result->mdns_service_name) - 1] = '\0';
330
331 log_info("mDNS: Found session '%s' at %s:%d", ctx->session_string, ctx->state->result->server_address,
332 ctx->state->result->server_port);
333 cond_signal(&ctx->state->signal);
334 }
335 }
336 mutex_unlock(&ctx->state->lock);
337 break;
338 }
339 }
340
341 discovery_mdns_destroy(discovered_servers);
342
343 mutex_lock(&ctx->state->lock);
344 ctx->state->mdns_done = true;
345 cond_signal(&ctx->state->signal);
346 mutex_unlock(&ctx->state->lock);
347 SAFE_FREE(ctx);
348 return NULL;
349}
350
351// ============================================================================
352// ACDS Discovery Thread
353// ============================================================================
354
363
365static void *acds_thread_fn(void *arg) {
367 if (!ctx)
368 return NULL;
369
370 acds_client_t client;
371 memset(&client, 0, sizeof(client));
372
373 acds_client_config_t client_config;
374 memset(&client_config, 0, sizeof(client_config));
375
376 SAFE_STRNCPY(client_config.server_address, ctx->config->acds_server, sizeof(client_config.server_address) - 1);
377 client_config.server_port = ctx->config->acds_port;
378 client_config.timeout_ms = ctx->config->acds_timeout_ms;
379
380 // Connect to ACDS server
381 asciichat_error_t conn_err = acds_client_connect(&client, &client_config);
382 if (conn_err != ASCIICHAT_OK) {
383 log_debug("ACDS: Failed to connect to %s:%d", ctx->config->acds_server, ctx->config->acds_port);
384 mutex_lock(&ctx->state->lock);
385 ctx->state->acds_done = true;
386 cond_signal(&ctx->state->signal);
387 mutex_unlock(&ctx->state->lock);
388 SAFE_FREE(ctx);
389 return NULL;
390 }
391
392 // Look up session
393 acds_session_lookup_result_t lookup_result;
394 memset(&lookup_result, 0, sizeof(lookup_result));
395
396 asciichat_error_t lookup_err = acds_session_lookup(&client, ctx->session_string, &lookup_result);
397 if (lookup_err != ASCIICHAT_OK) {
398 log_debug("ACDS: Session lookup failed for '%s'", ctx->session_string);
399 acds_client_disconnect(&client);
400 mutex_lock(&ctx->state->lock);
401 ctx->state->acds_done = true;
402 cond_signal(&ctx->state->signal);
403 mutex_unlock(&ctx->state->lock);
404 SAFE_FREE(ctx);
405 return NULL;
406 }
407
408 if (!lookup_result.found) {
409 log_debug("ACDS: Session '%s' not found", ctx->session_string);
410 acds_client_disconnect(&client);
411 mutex_lock(&ctx->state->lock);
412 ctx->state->acds_done = true;
413 cond_signal(&ctx->state->signal);
414 mutex_unlock(&ctx->state->lock);
415 SAFE_FREE(ctx);
416 return NULL;
417 }
418
419 // Verify pubkey if provided
420 if (ctx->config->expected_pubkey) {
421 if (memcmp(lookup_result.host_pubkey, ctx->config->expected_pubkey, 32) != 0) {
422 log_warn("ACDS: Session found but pubkey mismatch (MITM?)");
423 acds_client_disconnect(&client);
424 mutex_lock(&ctx->state->lock);
425 ctx->state->acds_done = true;
426 cond_signal(&ctx->state->signal);
427 mutex_unlock(&ctx->state->lock);
428 SAFE_FREE(ctx);
429 return NULL;
430 }
431 }
432
433 // Join session to get server connection details
434 acds_session_join_params_t join_params;
435 memset(&join_params, 0, sizeof(join_params));
436 join_params.session_string = ctx->session_string;
437
438 if (ctx->config->client_pubkey) {
439 memcpy(join_params.identity_pubkey, ctx->config->client_pubkey, 32);
440 }
441 if (ctx->config->client_seckey) {
442 memcpy(join_params.identity_seckey, ctx->config->client_seckey, 64);
443 }
444 if (ctx->config->password) {
445 join_params.has_password = true;
446 SAFE_STRNCPY(join_params.password, ctx->config->password, sizeof(join_params.password) - 1);
447 }
448
449 acds_session_join_result_t join_result;
450 memset(&join_result, 0, sizeof(join_result));
451
452 asciichat_error_t join_err = acds_session_join(&client, &join_params, &join_result);
453 acds_client_disconnect(&client);
454
455 if (join_err != ASCIICHAT_OK || !join_result.success) {
456 log_debug("ACDS: Session join failed: %s", join_result.error_message);
457 mutex_lock(&ctx->state->lock);
458 ctx->state->acds_done = true;
459 cond_signal(&ctx->state->signal);
460 mutex_unlock(&ctx->state->lock);
461 SAFE_FREE(ctx);
462 return NULL;
463 }
464
465 // Found it! Populate result
466 mutex_lock(&ctx->state->lock);
467 {
468 if (!ctx->state->found) {
469 ctx->state->found = true;
470 ctx->state->result->success = true;
471 ctx->state->result->source = DISCOVERY_SOURCE_ACDS;
472
473 memcpy(ctx->state->result->host_pubkey, lookup_result.host_pubkey, 32);
474 memcpy(ctx->state->result->session_id, join_result.session_id, 16);
475 memcpy(ctx->state->result->participant_id, join_result.participant_id, 16);
476
477 SAFE_STRNCPY(ctx->state->result->server_address, join_result.server_address,
478 sizeof(ctx->state->result->server_address) - 1);
479 ctx->state->result->server_port = join_result.server_port;
480
481 log_info("ACDS: Found session '%s' at %s:%d", ctx->session_string, ctx->state->result->server_address,
482 ctx->state->result->server_port);
483 cond_signal(&ctx->state->signal);
484 }
485 }
486 mutex_unlock(&ctx->state->lock);
487
488 SAFE_FREE(ctx);
489 return NULL;
490}
491
492// ============================================================================
493// Main Discovery Function
494// ============================================================================
495
497 if (!config)
498 return;
499
500 memset(config, 0, sizeof(*config));
501
502 // Check if debug or release build
503#ifdef NDEBUG
504 // Release: use internet ACDS
505 SAFE_STRNCPY(config->acds_server, "discovery.ascii-chat.com", sizeof(config->acds_server) - 1);
506#else
507 // Debug: use local ACDS
508 SAFE_STRNCPY(config->acds_server, "127.0.0.1", sizeof(config->acds_server) - 1);
509#endif
510
511 config->acds_port = OPT_ACDS_PORT_INT_DEFAULT;
512 config->mdns_timeout_ms = 2 * MS_PER_SEC_INT;
513 config->acds_timeout_ms = 5 * MS_PER_SEC_INT;
514 config->insecure_mode = false;
515 config->expected_pubkey = NULL;
516}
517
518asciichat_error_t discover_session_parallel(const char *session_string, const discovery_config_t *config,
519 discovery_result_t *result) {
520 if (!session_string || !config || !result) {
521 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to discover_session_parallel");
522 return ERROR_INVALID_PARAM;
523 }
524
525 // Validate session string format
526 if (!is_session_string(session_string)) {
527 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid session string format");
528 return ERROR_INVALID_PARAM;
529 }
530
531 memset(result, 0, sizeof(*result));
532 log_info("Discovery: Looking up session '%s'", session_string);
533
534 // Initialize thread state
536 memset(&state, 0, sizeof(state));
537 state.result = result;
538 mutex_init(&state.lock);
539 cond_init(&state.signal);
540
541 // Determine which discovery methods to use
542 bool use_mdns = true;
543 bool use_acds = config->expected_pubkey != NULL || config->insecure_mode;
544
545 if (!use_acds) {
546 log_debug("Discovery: mDNS-only mode (no --server-key and no --acds-insecure)");
547 }
548
549 // Spawn mDNS thread
550 asciichat_thread_t mdns_thread;
551 asciichat_thread_init(&mdns_thread);
552
553 if (use_mdns) {
554 mdns_thread_context_t *mdns_ctx = SAFE_MALLOC(sizeof(*mdns_ctx), mdns_thread_context_t *);
555 if (!mdns_ctx) {
556 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS context");
557 mutex_destroy(&state.lock);
558 cond_destroy(&state.signal);
559 return ERROR_MEMORY;
560 }
561
562 mdns_ctx->session_string = session_string;
563 mdns_ctx->state = &state;
564 mdns_ctx->expected_pubkey = config->expected_pubkey;
565
566 int thread_err = asciichat_thread_create(&mdns_thread, mdns_thread_fn, mdns_ctx);
567 if (thread_err != 0) {
568 log_warn("Discovery: Failed to spawn mDNS thread");
569 SAFE_FREE(mdns_ctx);
570 use_mdns = false;
571 }
572 }
573
574 // Spawn ACDS thread
575 asciichat_thread_t acds_thread;
576 asciichat_thread_init(&acds_thread);
577
578 if (use_acds) {
579 acds_thread_context_t *acds_ctx = SAFE_MALLOC(sizeof(*acds_ctx), acds_thread_context_t *);
580 if (!acds_ctx) {
581 SET_ERRNO(ERROR_MEMORY, "Failed to allocate ACDS context");
582 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
583 asciichat_thread_join(&mdns_thread, NULL);
584 }
585 mutex_destroy(&state.lock);
586 cond_destroy(&state.signal);
587 return ERROR_MEMORY;
588 }
589
590 acds_ctx->session_string = session_string;
591 acds_ctx->state = &state;
592 acds_ctx->config = config;
593
594 int thread_err = asciichat_thread_create(&acds_thread, acds_thread_fn, acds_ctx);
595 if (thread_err != 0) {
596 log_warn("Discovery: Failed to spawn ACDS thread");
597 SAFE_FREE(acds_ctx);
598 use_acds = false;
599 }
600 }
601
602 // Wait for result (mDNS timeout 2s + ACDS timeout 5s max)
603 uint32_t wait_timeout_ms = config->acds_timeout_ms + 1000;
604 mutex_lock(&state.lock);
605 {
606 uint32_t elapsed_ms = 0;
607 while (!state.found && elapsed_ms < wait_timeout_ms) {
608 // Check if both threads are done
609 if (state.mdns_done && state.acds_done) {
610 break;
611 }
612
613 // Wait with timeout
614 cond_timedwait(&state.signal, &state.lock, 500 * NS_PER_MS_INT);
615 elapsed_ms += 500;
616 }
617 }
618 mutex_unlock(&state.lock);
619
620 // Join threads
621 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
622 asciichat_thread_join(&mdns_thread, NULL);
623 }
624 if (use_acds && asciichat_thread_is_initialized(&acds_thread)) {
625 asciichat_thread_join(&acds_thread, NULL);
626 }
627
628 // Cleanup
629 mutex_destroy(&state.lock);
630 cond_destroy(&state.signal);
631
632 // Check result
633 if (!result->success) {
634 SET_ERRNO(ERROR_NOT_FOUND, "Session '%s' not found (mDNS/ACDS timeout)", session_string);
635 return ERROR_NOT_FOUND;
636 }
637
638 log_info("Discovery: Session '%s' discovered via %s", session_string,
639 result->source == DISCOVERY_SOURCE_MDNS ? "mDNS" : "ACDS");
640
641 return ASCIICHAT_OK;
642}
asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string, acds_session_lookup_result_t *result)
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Definition acds_client.c:60
void acds_client_disconnect(acds_client_t *client)
bool is_session_string(const char *str)
void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65])
Definition discovery.c:49
asciichat_error_t hex_to_pubkey(const char *hex_str, uint8_t pubkey_out[32])
Definition discovery.c:58
void discovery_config_init_defaults(discovery_config_t *config)
Definition discovery.c:496
void discovery_mdns_destroy(discovery_tui_server_t *servers)
Free memory from mDNS discovery results.
Definition discovery.c:267
discovery_tui_server_t * discovery_mdns_query(int timeout_ms, int max_servers, bool quiet, int *out_count)
Public mDNS query function used by both parallel discovery and TUI wrapper.
Definition discovery.c:179
asciichat_error_t discover_session_parallel(const char *session_string, const discovery_config_t *config, discovery_result_t *result)
Definition discovery.c:518
asciichat_error_t asciichat_mdns_update(asciichat_mdns_t *mdns, int timeout_ms)
Definition mdns.c:260
void asciichat_mdns_destroy(asciichat_mdns_t *mdns)
Definition mdns.c:66
asciichat_mdns_t * asciichat_mdns_init(void)
Definition mdns.c:35
asciichat_error_t asciichat_mdns_query(asciichat_mdns_t *mdns, const char *service_type, asciichat_mdns_discovery_callback_fn callback, void *user_data)
Definition mdns.c:224
Context for ACDS discovery thread.
Definition discovery.c:358
discovery_thread_state_t * state
Shared discovery state.
Definition discovery.c:360
const discovery_config_t * config
Discovery configuration.
Definition discovery.c:361
const char * session_string
Session string to look up.
Definition discovery.c:359
Internal mDNS context structure.
Definition mdns.c:23
Configuration for discovery session.
Definition session.h:211
uint16_t acds_port
ACDS port (default: 27225)
Definition session.h:214
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
Internal state for collecting discovered services.
Definition discovery.c:98
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
discovery_tui_server_t * servers
Array of discovered servers.
Definition discovery.c:99
Context for mDNS discovery thread.
Definition discovery.c:278
discovery_thread_state_t * state
Shared discovery state.
Definition discovery.c:280
uint32_t timeout_ms
Discovery timeout in milliseconds.
Definition discovery.c:282
const uint8_t * expected_pubkey
Expected server public key (optional)
Definition discovery.c:281
const char * session_string
Session string to discover.
Definition discovery.c:279
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
Definition threading.c:42
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Definition threading.c:46
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21
uint64_t time_get_ns(void)
Definition util/time.c:48