16#include <ascii-chat/network/mdns/discovery.h>
17#include <ascii-chat/network/mdns/discovery_tui.h>
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>
50 if (!pubkey || !hex_out)
52 for (
int i = 0; i < 32; i++) {
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;
64 if (strlen(hex_str) != 64) {
65 SET_ERRNO(ERROR_INVALID_PARAM,
"Hex string must be exactly 64 characters");
66 return ERROR_INVALID_PARAM;
69 for (
int i = 0; i < 32; i++) {
71 hex_byte[0] = hex_str[i * 2];
72 hex_byte[1] = hex_str[i * 2 + 1];
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;
80 pubkey_out[i] = (uint8_t)strtol(hex_byte, NULL, 16);
111static void discovery_mdns_callback(
const asciichat_mdns_discovery_t *discovery,
void *user_data) {
113 if (!state || !discovery) {
119 log_warn(
"mDNS: Reached maximum server capacity (%d)", state->
capacity);
131 if (strstr(discovery->type,
"_ascii-chat._tcp") == NULL) {
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) {
139 if (discovery->ttl > state->
servers[i].ttl) {
140 state->
servers[i].ttl = discovery->ttl;
147 discovery_tui_server_t *server = &state->
servers[state->
count];
148 memset(server, 0,
sizeof(discovery_tui_server_t));
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;
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));
167 log_debug(
"mDNS: Found server '%s' at %s:%u", discovery->name, server->address, discovery->port);
181 SET_ERRNO(ERROR_INVALID_PARAM,
"out_count pointer is NULL");
188 if (timeout_ms <= 0) {
189 timeout_ms = 2 * MS_PER_SEC_INT;
191 if (max_servers <= 0) {
197 memset(&state, 0,
sizeof(state));
203 state.
servers = SAFE_MALLOC((
size_t)state.
capacity *
sizeof(discovery_tui_server_t), discovery_tui_server_t *);
205 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate mDNS discovery server array");
208 memset(state.
servers, 0, state.
capacity *
sizeof(discovery_tui_server_t));
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");
218 log_warn(
"mDNS: Failed to initialize mDNS - discovery unavailable");
224 asciichat_error_t query_result =
227 if (query_result != ASCIICHAT_OK) {
228 log_info(
"mDNS: Query failed - no servers found via service discovery");
237 int poll_timeout = (int)(deadline - (int64_t)time_ns_to_ms(
time_get_ns()));
238 if (poll_timeout < 0) {
241 if (poll_timeout > 100) {
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);
255 printf(
"❌ No ascii-chat servers found on LAN\n");
256 log_info(
"mDNS: No servers found");
260 *out_count = state.
count;
286static void *mdns_thread_fn(
void *arg) {
292 int discovered_count = 0;
293 discovery_tui_server_t *discovered_servers =
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) {
310 for (
int i = 0; i < discovered_count; i++) {
311 discovery_tui_server_t *server = &discovered_servers[i];
320 ctx->
state->
result->source = DISCOVERY_SOURCE_MDNS;
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);
327 SAFE_STRNCPY(ctx->
state->
result->mdns_service_name, server->name,
328 sizeof(ctx->
state->
result->mdns_service_name) - 1);
365static void *acds_thread_fn(
void *arg) {
370 acds_client_t client;
371 memset(&client, 0,
sizeof(client));
373 acds_client_config_t client_config;
374 memset(&client_config, 0,
sizeof(client_config));
376 SAFE_STRNCPY(client_config.server_address, ctx->
config->acds_server,
sizeof(client_config.server_address) - 1);
378 client_config.timeout_ms = ctx->
config->acds_timeout_ms;
382 if (conn_err != ASCIICHAT_OK) {
393 acds_session_lookup_result_t lookup_result;
394 memset(&lookup_result, 0,
sizeof(lookup_result));
397 if (lookup_err != ASCIICHAT_OK) {
398 log_debug(
"ACDS: Session lookup failed for '%s'", ctx->
session_string);
408 if (!lookup_result.found) {
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?)");
434 acds_session_join_params_t join_params;
435 memset(&join_params, 0,
sizeof(join_params));
438 if (ctx->
config->client_pubkey) {
439 memcpy(join_params.identity_pubkey, ctx->
config->client_pubkey, 32);
441 if (ctx->
config->client_seckey) {
442 memcpy(join_params.identity_seckey, ctx->
config->client_seckey, 64);
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);
449 acds_session_join_result_t join_result;
450 memset(&join_result, 0,
sizeof(join_result));
452 asciichat_error_t join_err =
acds_session_join(&client, &join_params, &join_result);
455 if (join_err != ASCIICHAT_OK || !join_result.success) {
456 log_debug(
"ACDS: Session join failed: %s", join_result.error_message);
471 ctx->
state->
result->source = DISCOVERY_SOURCE_ACDS;
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);
477 SAFE_STRNCPY(ctx->
state->
result->server_address, join_result.server_address,
479 ctx->
state->
result->server_port = join_result.server_port;
500 memset(config, 0,
sizeof(*config));
505 SAFE_STRNCPY(config->acds_server,
"discovery.ascii-chat.com",
sizeof(config->acds_server) - 1);
508 SAFE_STRNCPY(config->acds_server,
"127.0.0.1",
sizeof(config->acds_server) - 1);
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;
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;
527 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid session string format");
528 return ERROR_INVALID_PARAM;
531 memset(result, 0,
sizeof(*result));
532 log_info(
"Discovery: Looking up session '%s'", session_string);
536 memset(&state, 0,
sizeof(state));
542 bool use_mdns =
true;
543 bool use_acds = config->expected_pubkey != NULL || config->insecure_mode;
546 log_debug(
"Discovery: mDNS-only mode (no --server-key and no --acds-insecure)");
550 asciichat_thread_t mdns_thread;
551 asciichat_thread_init(&mdns_thread);
556 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate mDNS context");
558 cond_destroy(&state.
signal);
563 mdns_ctx->
state = &state;
567 if (thread_err != 0) {
568 log_warn(
"Discovery: Failed to spawn mDNS thread");
575 asciichat_thread_t acds_thread;
576 asciichat_thread_init(&acds_thread);
581 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate ACDS context");
582 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
586 cond_destroy(&state.
signal);
591 acds_ctx->
state = &state;
592 acds_ctx->
config = config;
595 if (thread_err != 0) {
596 log_warn(
"Discovery: Failed to spawn ACDS thread");
603 uint32_t wait_timeout_ms = config->acds_timeout_ms + 1000;
604 mutex_lock(&state.
lock);
606 uint32_t elapsed_ms = 0;
607 while (!state.
found && elapsed_ms < wait_timeout_ms) {
614 cond_timedwait(&state.
signal, &state.
lock, 500 * NS_PER_MS_INT);
618 mutex_unlock(&state.
lock);
621 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
624 if (use_acds && asciichat_thread_is_initialized(&acds_thread)) {
630 cond_destroy(&state.
signal);
633 if (!result->success) {
634 SET_ERRNO(ERROR_NOT_FOUND,
"Session '%s' not found (mDNS/ACDS timeout)", session_string);
635 return ERROR_NOT_FOUND;
638 log_info(
"Discovery: Session '%s' discovered via %s", session_string,
639 result->source == DISCOVERY_SOURCE_MDNS ?
"mDNS" :
"ACDS");
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)
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])
asciichat_error_t hex_to_pubkey(const char *hex_str, uint8_t pubkey_out[32])
void discovery_config_init_defaults(discovery_config_t *config)
void discovery_mdns_destroy(discovery_tui_server_t *servers)
Free memory from mDNS discovery results.
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.
asciichat_error_t discover_session_parallel(const char *session_string, const discovery_config_t *config, discovery_result_t *result)
asciichat_error_t asciichat_mdns_update(asciichat_mdns_t *mdns, int timeout_ms)
void asciichat_mdns_destroy(asciichat_mdns_t *mdns)
asciichat_mdns_t * asciichat_mdns_init(void)
asciichat_error_t asciichat_mdns_query(asciichat_mdns_t *mdns, const char *service_type, asciichat_mdns_discovery_callback_fn callback, void *user_data)
Context for ACDS discovery thread.
discovery_thread_state_t * state
Shared discovery state.
const discovery_config_t * config
Discovery configuration.
const char * session_string
Session string to look up.
Internal mDNS context structure.
Configuration for discovery session.
uint16_t acds_port
ACDS port (default: 27225)
Thread-safe result sharing between discovery threads.
bool found
Whether a session was found.
discovery_result_t * result
Shared discovery result.
cond_t signal
Condition variable for signaling.
bool mdns_done
Whether mDNS discovery completed.
mutex_t lock
Mutex for thread-safe access.
bool acds_done
Whether ACDS discovery completed.
Internal state for collecting discovered services.
int count
Number of servers discovered so far.
int64_t start_time_ms
When discovery started (for timeout)
bool query_complete
Set when discovery completes.
int capacity
Allocated capacity.
int timeout_ms
Discovery timeout in milliseconds.
discovery_tui_server_t * servers
Array of discovered servers.
Context for mDNS discovery thread.
discovery_thread_state_t * state
Shared discovery state.
uint32_t timeout_ms
Discovery timeout in milliseconds.
const uint8_t * expected_pubkey
Expected server public key (optional)
const char * session_string
Session string to discover.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
int mutex_init(mutex_t *mutex)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
int mutex_destroy(mutex_t *mutex)
uint64_t time_get_ns(void)