47 if (!pubkey || !hex_out)
49 for (
int i = 0; i < 32; i++) {
50 sprintf(hex_out + (i * 2),
"%02x", pubkey[i]);
56 if (!hex_str || !pubkey_out) {
61 if (strlen(hex_str) != 64) {
66 for (
int i = 0; i < 32; i++) {
68 hex_byte[0] = hex_str[i * 2];
69 hex_byte[1] = hex_str[i * 2 + 1];
72 if (!isxdigit(hex_byte[0]) || !isxdigit(hex_byte[1])) {
77 pubkey_out[i] = (
uint8_t)strtol(hex_byte, NULL, 16);
84 if (!str || strlen(str) == 0)
90 size_t len = strlen(str);
93 if (len < 5 || len > 194) {
98 if (str[0] ==
'-' || str[len - 1] ==
'-') {
104 int current_word_len = 0;
105 bool last_was_hyphen =
false;
107 for (
size_t i = 0; i < len; i++) {
114 if (current_word_len > 64) {
117 last_was_hyphen =
false;
118 }
else if (c ==
'-') {
120 if (last_was_hyphen) {
126 if (current_word_len < 3) {
131 current_word_len = 0;
132 last_was_hyphen =
true;
140 if (current_word_len < 3 || current_word_len > 64) {
145 return word_count == 3;
164static asciichat_error_t parse_txt_records(
const char *txt,
char *session_string_out,
char *host_pubkey_out) {
165 if (!txt || !session_string_out || !host_pubkey_out) {
169 memset(session_string_out, 0, 49);
170 memset(host_pubkey_out, 0, 65);
172 const char *ptr = txt;
173 while (*ptr && ptr < txt + 512) {
175 while (*ptr && isspace(*ptr))
181 const char *key_start = ptr;
182 while (*ptr && *ptr !=
'=' && !isspace(*ptr))
184 size_t key_len = ptr - key_start;
194 const char *value_start = ptr;
195 while (*ptr && !isspace(*ptr))
197 size_t value_len = ptr - value_start;
200 if (key_len == strlen(
"session_string") && strncmp(key_start,
"session_string", key_len) == 0) {
201 if (value_len < 49) {
202 strncpy(session_string_out, value_start, value_len);
203 session_string_out[value_len] =
'\0';
205 }
else if (key_len == strlen(
"host_pubkey") && strncmp(key_start,
"host_pubkey", key_len) == 0) {
206 if (value_len == 64) {
207 strncpy(host_pubkey_out, value_start, 64);
208 host_pubkey_out[64] =
'\0';
216 if (session_string_out[0] && host_pubkey_out[0]) {
242static int64_t discovery_get_time_ms(
void) {
244 clock_gettime(CLOCK_MONOTONIC, &ts);
245 return (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
254 if (!state || !discovery) {
265 int64_t elapsed = discovery_get_time_ms() - state->
start_time_ms;
272 if (strstr(discovery->
type,
"_ascii-chat._tcp") == NULL) {
277 for (
int i = 0; i < state->
count; i++) {
296 server->
ttl = discovery->
ttl;
299 if (discovery->
ipv4[0] !=
'\0') {
301 }
else if (discovery->
host[0] !=
'\0') {
303 }
else if (discovery->
ipv6[0] !=
'\0') {
329 if (timeout_ms <= 0) {
332 if (max_servers <= 0) {
338 memset(&state, 0,
sizeof(state));
352 log_info(
"mDNS: Searching for ASCII-Chat servers on local network (timeout: %dms)", state.
timeout_ms);
353 printf(
"🔍 Searching for ASCII-Chat servers on LAN...\n");
359 log_warn(
"mDNS: Failed to initialize mDNS - discovery unavailable");
369 log_info(
"mDNS: Query failed - no servers found via service discovery");
377 while (!state.
query_complete && discovery_get_time_ms() < deadline) {
378 int poll_timeout = (int)(deadline - discovery_get_time_ms());
379 if (poll_timeout < 0) {
382 if (poll_timeout > 100) {
392 if (state.
count > 0) {
393 printf(
"✅ Found %d ASCII-Chat server%s on LAN\n", state.
count, state.
count == 1 ?
"" :
"s");
396 printf(
"❌ No ASCII-Chat servers found on LAN\n");
401 *out_count = state.
count;
425static void *mdns_thread_fn(
void *arg) {
431 int discovered_count = 0;
435 if (!discovered_servers || discovered_count == 0) {
436 log_debug(
"mDNS: No servers found (this is normal if no servers are on LAN)");
437 if (discovered_servers) {
449 for (
int i = 0; i < discovered_count; i++) {
462 const char *best_addr = (server->
ipv4[0] !=
'\0') ? server->
ipv4 : server->ipv6;
502static void *acds_thread_fn(
void *arg) {
508 memset(&client, 0,
sizeof(client));
511 memset(&client_config, 0,
sizeof(client_config));
531 memset(&lookup_result, 0,
sizeof(lookup_result));
545 if (!lookup_result.
found) {
559 log_warn(
"ACDS: Session found but pubkey mismatch (MITM?)");
572 memset(&join_params, 0,
sizeof(join_params));
587 memset(&join_result, 0,
sizeof(join_result));
637 memset(config, 0,
sizeof(*config));
657 if (!session_string || !config || !result) {
668 memset(result, 0,
sizeof(*result));
669 log_info(
"Discovery: Looking up session '%s'", session_string);
673 memset(&state, 0,
sizeof(state));
679 bool use_mdns =
true;
683 log_debug(
"Discovery: mDNS-only mode (no --server-key and no --acds-insecure)");
700 mdns_ctx->
state = &state;
704 if (thread_err != 0) {
705 log_warn(
"Discovery: Failed to spawn mDNS thread");
728 acds_ctx->
state = &state;
729 acds_ctx->
config = config;
732 if (thread_err != 0) {
733 log_warn(
"Discovery: Failed to spawn ACDS thread");
744 while (!state.
found && elapsed_ms < wait_timeout_ms) {
775 log_info(
"Discovery: Session '%s' discovered via %s", session_string,
776 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)
Look up session by string.
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
Join an existing session.
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Connect to ACDS server.
void acds_client_disconnect(acds_client_t *client)
Disconnect from ACDS server.
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.
bool is_session_string(const char *str)
Check if a string matches session string pattern.
void discovery_mdns_free(discovery_tui_server_t *servers)
Free memory from mDNS discovery results.
void discovery_config_init_defaults(discovery_config_t *config)
Initialize discovery config with defaults.
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)
Look up session in parallel on mDNS and ACDS.
Parallel mDNS and ACDS session discovery.
TUI-based service discovery for ascii-chat client.
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
📝 Logging API with multiple log levels and terminal output control
asciichat_error_t asciichat_mdns_update(asciichat_mdns_t *mdns, int timeout_ms)
Process pending mDNS events (must be called regularly)
void asciichat_mdns_shutdown(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.
Cross-platform mutex interface for ascii-chat.
ACDS client connection configuration.
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
uint32_t timeout_ms
Connection timeout in milliseconds.
uint16_t server_port
ACDS server port (default: 27225)
ACDS client connection handle.
uint8_t identity_pubkey[32]
Participant's Ed25519 public key.
bool has_password
Password provided.
char password[128]
Password (if has_password)
const char * session_string
Session to join.
uint8_t identity_seckey[64]
Ed25519 secret key (for signing)
char error_message[129]
Error message (if !success, null-terminated)
uint8_t participant_id[16]
Participant UUID (if success)
bool success
Join succeeded.
char server_address[65]
Server IP/hostname (if success, null-terminated)
uint16_t server_port
Server port (if success)
uint8_t session_id[16]
Session UUID (if success)
bool found
Session exists.
uint8_t host_pubkey[32]
Host's Ed25519 public key.
discovery_thread_state_t * state
const discovery_config_t * config
const char * session_string
Discovered service information.
Internal mDNS context structure.
Session discovery configuration.
const uint8_t * client_seckey
Client's Ed25519 secret key (can be NULL)
uint32_t acds_timeout_ms
ACDS lookup timeout (default: 5000ms)
uint32_t mdns_timeout_ms
mDNS search timeout (default: 2000ms)
const char * password
Optional session password (NULL if none)
uint16_t acds_port
ACDS server port (default: 27225)
char acds_server[256]
ACDS server address (e.g., "localhost" or "acds.ascii-chat.com")
bool insecure_mode
Allow no verification (–acds-insecure flag)
const uint8_t * expected_pubkey
Expected server pubkey (NULL = no verification)
const uint8_t * client_pubkey
Client's Ed25519 public key (can be NULL)
Result from session discovery.
char mdns_service_name[256]
mDNS service instance name (e.g., "swift-river-mountain")
uint16_t server_port
Server port (typically 27224)
uint8_t host_pubkey[32]
Ed25519 public key of discovered server.
uint8_t participant_id[16]
Assigned participant ID (from SESSION_JOIN)
uint8_t session_id[16]
ACDS session UUID.
char server_address[256]
Server IP or hostname.
bool success
Discovery succeeded.
enum discovery_result_t::@3 source
Which discovery method found the server.
discovery_result_t * result
Discovered server information from mDNS.
char name[256]
Service instance name (e.g., "swift-river-canyon")
uint16_t port
Server port number.
char ipv6[46]
IPv6 address (if available)
uint32_t ttl
TTL remaining (seconds)
char ipv4[16]
IPv4 address (if available)
char address[256]
Server address (IPv4, IPv6, or hostname)
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.
discovery_thread_state_t * state
const uint8_t * expected_pubkey
const char * session_string
⏱️ High-precision timing utilities using sokol_time.h and uthash