ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
discovery.h File Reference

Parallel mDNS and ACDS session discovery. More...

Go to the source code of this file.

Data Structures

struct  discovery_result_t
 Result from session discovery. More...
 
struct  discovery_config_t
 Session discovery configuration. 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.
 
bool is_session_string (const char *str)
 Check if a string matches session string pattern.
 
discovery_tui_server_tdiscovery_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_free (discovery_tui_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.
 

Detailed Description

Parallel mDNS and ACDS session discovery.

Implements concurrent lookup on both mDNS (local LAN) and ACDS (internet discovery) with "race to success" semantics - whichever discovery method finds the session first is used.

Three Usage Modes:

  1. mDNS-only (safest, no ACDS)
    • Input: session string only
    • Searches mDNS for TXT record containing session_string
    • No ACDS lookup, no network calls
    • Use case: Local LAN connections where server is on same network
    • Command: ascii-chat swift-river-mountain
  2. Verified ACDS (parallel with pubkey check)
    • Input: session string + expected server pubkey
    • Searches mDNS (timeout 2s) AND ACDS (timeout 5s) in parallel threads
    • Verifies discovered server pubkey matches expected key in BOTH sources
    • Requires explicit --server-key flag with Ed25519 public key
    • Command: ascii-chat --server-key $pubkey swift-river-mountain
  3. Insecure ACDS (parallel without verification)
    • Input: session string only
    • Searches mDNS (timeout 2s) AND ACDS (timeout 5s) in parallel threads
    • No pubkey verification (MITM-vulnerable, requires explicit --acds-insecure flag)
    • Use case: Test/development environments only
    • Command: ascii-chat --acds-insecure swift-river-mountain

Thread Architecture:

Both lookups run concurrently:

├─ Thread A (mDNS):
│ └─ Query TXT records, parse session_string
│ └─ Extract host_pubkey from TXT
│ └─ Return: IPv4/IPv6, port, pubkey (2s timeout)
├─ Thread B (ACDS):
│ └─ Connect to ACDS server
│ └─ SESSION_LOOKUP request
│ └─ Return: host_pubkey, session_info (5-8s timeout)
│ └─ SESSION_JOIN to get server_address/port
└─ Whichever completes first wins, other is cancelled
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.
Definition discovery.c:655

Security Model:

  • mDNS verification: TXT record contains host_pubkey=<hex>, client checks against --server-key
  • ACDS verification: ACDS stores host_pubkey, SESSION_LOOKUP returns it, client verifies
  • Cross-verification: If both succeed, MUST have same pubkey (prevents split-brain)
  • TOFU on first use: When no --server-key specified, mDNS-only mode (trust first TXT record seen)
Author
Claude claud.nosp@m.e@an.nosp@m.throp.nosp@m.ic.c.nosp@m.om
Date
January 2026

Definition in 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 655 of file discovery.c.

656 {
657 if (!session_string || !config || !result) {
658 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to discover_session_parallel");
659 return ERROR_INVALID_PARAM;
660 }
661
662 // Validate session string format
663 if (!is_session_string(session_string)) {
664 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid session string format");
665 return ERROR_INVALID_PARAM;
666 }
667
668 memset(result, 0, sizeof(*result));
669 log_info("Discovery: Looking up session '%s'", session_string);
670
671 // Initialize thread state
673 memset(&state, 0, sizeof(state));
674 state.result = result;
675 mutex_init(&state.lock);
676 cond_init(&state.signal);
677
678 // Determine which discovery methods to use
679 bool use_mdns = true;
680 bool use_acds = config->expected_pubkey != NULL || config->insecure_mode;
681
682 if (!use_acds) {
683 log_debug("Discovery: mDNS-only mode (no --server-key and no --acds-insecure)");
684 }
685
686 // Spawn mDNS thread
687 asciichat_thread_t mdns_thread;
688 asciichat_thread_init(&mdns_thread);
689
690 if (use_mdns) {
691 mdns_thread_context_t *mdns_ctx = SAFE_MALLOC(sizeof(*mdns_ctx), mdns_thread_context_t *);
692 if (!mdns_ctx) {
693 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS context");
694 mutex_destroy(&state.lock);
695 cond_destroy(&state.signal);
696 return ERROR_MEMORY;
697 }
698
699 mdns_ctx->session_string = session_string;
700 mdns_ctx->state = &state;
701 mdns_ctx->expected_pubkey = config->expected_pubkey;
702
703 int thread_err = asciichat_thread_create(&mdns_thread, mdns_thread_fn, mdns_ctx);
704 if (thread_err != 0) {
705 log_warn("Discovery: Failed to spawn mDNS thread");
706 SAFE_FREE(mdns_ctx);
707 use_mdns = false;
708 }
709 }
710
711 // Spawn ACDS thread
712 asciichat_thread_t acds_thread;
713 asciichat_thread_init(&acds_thread);
714
715 if (use_acds) {
716 acds_thread_context_t *acds_ctx = SAFE_MALLOC(sizeof(*acds_ctx), acds_thread_context_t *);
717 if (!acds_ctx) {
718 SET_ERRNO(ERROR_MEMORY, "Failed to allocate ACDS context");
719 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
720 asciichat_thread_join(&mdns_thread, NULL);
721 }
722 mutex_destroy(&state.lock);
723 cond_destroy(&state.signal);
724 return ERROR_MEMORY;
725 }
726
727 acds_ctx->session_string = session_string;
728 acds_ctx->state = &state;
729 acds_ctx->config = config;
730
731 int thread_err = asciichat_thread_create(&acds_thread, acds_thread_fn, acds_ctx);
732 if (thread_err != 0) {
733 log_warn("Discovery: Failed to spawn ACDS thread");
734 SAFE_FREE(acds_ctx);
735 use_acds = false;
736 }
737 }
738
739 // Wait for result (mDNS timeout 2s + ACDS timeout 5s max)
740 uint32_t wait_timeout_ms = config->acds_timeout_ms + 1000;
741 mutex_lock(&state.lock);
742 {
743 uint32_t elapsed_ms = 0;
744 while (!state.found && elapsed_ms < wait_timeout_ms) {
745 // Check if both threads are done
746 if (state.mdns_done && state.acds_done) {
747 break;
748 }
749
750 // Wait with timeout
751 cond_timedwait(&state.signal, &state.lock, 500);
752 elapsed_ms += 500;
753 }
754 }
755 mutex_unlock(&state.lock);
756
757 // Join threads
758 if (use_mdns && asciichat_thread_is_initialized(&mdns_thread)) {
759 asciichat_thread_join(&mdns_thread, NULL);
760 }
761 if (use_acds && asciichat_thread_is_initialized(&acds_thread)) {
762 asciichat_thread_join(&acds_thread, NULL);
763 }
764
765 // Cleanup
766 mutex_destroy(&state.lock);
767 cond_destroy(&state.signal);
768
769 // Check result
770 if (!result->success) {
771 SET_ERRNO(ERROR_NOT_FOUND, "Session '%s' not found (mDNS/ACDS timeout)", session_string);
772 return ERROR_NOT_FOUND;
773 }
774
775 log_info("Discovery: Session '%s' discovered via %s", session_string,
776 result->source == DISCOVERY_SOURCE_MDNS ? "mDNS" : "ACDS");
777
778 return ASCIICHAT_OK;
779}
bool is_session_string(const char *str)
Check if a string matches session string pattern.
Definition discovery.c:83
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_NOT_FOUND
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
int cond_init(cond_t *cond)
Initialize a condition variable.
int cond_timedwait(cond_t *cond, mutex_t *mutex, int timeout_ms)
Wait on a condition variable with timeout.
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)
Definition mutex.h:140
bool asciichat_thread_is_initialized(asciichat_thread_t *thread)
Check if a thread handle has been initialized.
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Wait for a thread to complete (blocking)
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
pthread_t asciichat_thread_t
Thread handle type (POSIX: pthread_t)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*func)(void *), void *arg)
Create a new thread.
int cond_destroy(cond_t *cond)
Destroy a condition variable.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
discovery_thread_state_t * state
Definition discovery.c:497
const discovery_config_t * config
Definition discovery.c:498
const char * session_string
Definition discovery.c:496
uint32_t acds_timeout_ms
ACDS lookup timeout (default: 5000ms)
Definition discovery.h:122
bool insecure_mode
Allow no verification (–acds-insecure flag)
Definition discovery.h:114
const uint8_t * expected_pubkey
Expected server pubkey (NULL = no verification)
Definition discovery.h:113
bool success
Discovery succeeded.
Definition discovery.h:86
enum discovery_result_t::@3 source
Which discovery method found the server.
discovery_result_t * result
Definition discovery.c:36
discovery_thread_state_t * state
Definition discovery.c:419
const uint8_t * expected_pubkey
Definition discovery.c:420
const char * session_string
Definition discovery.c:418

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, mdns_thread_context_t::expected_pubkey, discovery_config_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, 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.

Referenced by client_main().

◆ 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), acds.ascii-chat.com (release)
  • Timeouts: mdns=2000ms, acds=5000ms
  • Verification: None (expected_pubkey=NULL)
  • Insecure mode: false
Parameters
configConfiguration to initialize

Definition at line 633 of file discovery.c.

633 {
634 if (!config)
635 return;
636
637 memset(config, 0, sizeof(*config));
638
639 // Check if debug or release build
640#ifdef NDEBUG
641 // Release: use internet ACDS
642 strncpy(config->acds_server, "discovery.ascii-chat.com", sizeof(config->acds_server) - 1);
643#else
644 // Debug: use local ACDS
645 strncpy(config->acds_server, "127.0.0.1", sizeof(config->acds_server) - 1);
646#endif
647
648 config->acds_port = 27225;
649 config->mdns_timeout_ms = 2000;
650 config->acds_timeout_ms = 5000;
651 config->insecure_mode = false;
652 config->expected_pubkey = NULL;
653}
uint32_t mdns_timeout_ms
mDNS search timeout (default: 2000ms)
Definition discovery.h:121
uint16_t acds_port
ACDS server port (default: 27225)
Definition discovery.h:118
char acds_server[256]
ACDS server address (e.g., "localhost" or "acds.ascii-chat.com")
Definition discovery.h:117

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, and discovery_config_t::mdns_timeout_ms.

Referenced by client_main().

◆ discovery_mdns_free()

void discovery_mdns_free ( discovery_tui_server_t servers)

Free memory from mDNS discovery results.

Definition at line 408 of file discovery.c.

408 {
409 SAFE_FREE(servers);
410}

References SAFE_FREE.

Referenced by discovery_tui_free_results().

◆ discovery_mdns_query()

discovery_tui_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 (discovery_tui_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_free() 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_free() to free.

Definition at line 320 of file discovery.c.

320 {
321 if (!out_count) {
322 SET_ERRNO(ERROR_INVALID_PARAM, "out_count pointer is NULL");
323 return NULL;
324 }
325
326 *out_count = 0;
327
328 // Apply defaults
329 if (timeout_ms <= 0) {
330 timeout_ms = 2000;
331 }
332 if (max_servers <= 0) {
333 max_servers = 20;
334 }
335
336 // Allocate state for collecting servers
337 mdns_query_state_t state;
338 memset(&state, 0, sizeof(state));
339 state.capacity = max_servers;
340 state.timeout_ms = timeout_ms;
341 state.start_time_ms = discovery_get_time_ms();
342
343 // Allocate server array
345 if (!state.servers) {
346 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS discovery server array");
347 return NULL;
348 }
349 memset(state.servers, 0, state.capacity * sizeof(discovery_tui_server_t));
350
351 if (!quiet) {
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");
354 }
355
356 // Initialize mDNS
358 if (!mdns) {
359 log_warn("mDNS: Failed to initialize mDNS - discovery unavailable");
360 SAFE_FREE(state.servers);
361 return NULL;
362 }
363
364 // Start mDNS query for _ascii-chat._tcp services
365 asciichat_error_t query_result =
366 asciichat_mdns_query(mdns, "_ascii-chat._tcp.local", discovery_mdns_callback, &state);
367
368 if (query_result != ASCIICHAT_OK) {
369 log_info("mDNS: Query failed - no servers found via service discovery");
371 SAFE_FREE(state.servers);
372 return NULL;
373 }
374
375 // Poll for responses until timeout
376 int64_t deadline = state.start_time_ms + state.timeout_ms;
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) {
380 poll_timeout = 0;
381 }
382 if (poll_timeout > 100) {
383 poll_timeout = 100; // Check every 100ms
384 }
385 asciichat_mdns_update(mdns, poll_timeout);
386 }
387
388 // Cleanup mDNS
390
391 if (!quiet) {
392 if (state.count > 0) {
393 printf("✅ Found %d ASCII-Chat server%s on LAN\n", state.count, state.count == 1 ? "" : "s");
394 log_info("mDNS: Found %d server(s)", state.count);
395 } else {
396 printf("❌ No ASCII-Chat servers found on LAN\n");
397 log_info("mDNS: No servers found");
398 }
399 }
400
401 *out_count = state.count;
402 return state.servers;
403}
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
asciichat_error_t asciichat_mdns_update(asciichat_mdns_t *mdns, int timeout_ms)
Process pending mDNS events (must be called regularly)
Definition mdns.c:255
void asciichat_mdns_shutdown(asciichat_mdns_t *mdns)
Shutdown mDNS context and cleanup.
Definition mdns.c:61
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
Definition mdns.c:30
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.
Definition mdns.c:219
Internal mDNS context structure.
Definition mdns.c:18
Discovered server information from mDNS.
Internal state for collecting discovered services.
Definition discovery.c:230
int count
Number of servers discovered so far.
Definition discovery.c:232
int64_t start_time_ms
When discovery started (for timeout)
Definition discovery.c:234
bool query_complete
Set when discovery completes.
Definition discovery.c:236
int capacity
Allocated capacity.
Definition discovery.c:233
int timeout_ms
Discovery timeout in milliseconds.
Definition discovery.c:235
discovery_tui_server_t * servers
Array of discovered servers.
Definition discovery.c:231

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

Referenced by discovery_tui_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 55 of file discovery.c.

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

References ASCIICHAT_OK, ERROR_INVALID_PARAM, and SET_ERRNO.

Referenced by client_main().

◆ is_session_string()

bool is_session_string ( const char *  str)

Check if a string matches session string pattern.

Validates that input matches three-word pattern: word-word-word Each word is 3-64 characters of alphanumerics and hyphens (RFC 952).

Examples:

  • swift-river-mountain - valid
  • cat-dog-bird - valid
  • swift - invalid (only one word)
  • swift-river- - invalid (trailing hyphen)
  • swift_river_mountain - invalid (underscores not allowed)
Parameters
strString to validate
Returns
true if matches session string pattern, false otherwise
Note
Used by src/main.c to detect binary-level session string argument

Definition at line 83 of file discovery.c.

83 {
84 if (!str || strlen(str) == 0)
85 return false;
86
87 // Parse as word-word-word where each word is 3-64 alphanumerics
88 // Hyphens are separators between words
89 // No leading/trailing hyphens, no consecutive hyphens
90 size_t len = strlen(str);
91
92 // Check length bounds (minimum: "a-a-a" = 5, maximum: 64 chars per word * 3 + 2 hyphens = 194)
93 if (len < 5 || len > 194) {
94 return false;
95 }
96
97 // Must not start or end with hyphen
98 if (str[0] == '-' || str[len - 1] == '-') {
99 return false;
100 }
101
102 // Count words separated by hyphens
103 int word_count = 1; // Start with 1 (not 0) to account for first word
104 int current_word_len = 0;
105 bool last_was_hyphen = false;
106
107 for (size_t i = 0; i < len; i++) {
108 char c = str[i];
109
110 if (isalnum(c)) {
111 current_word_len++;
112
113 // Individual word must be 3-64 characters
114 if (current_word_len > 64) {
115 return false;
116 }
117 last_was_hyphen = false;
118 } else if (c == '-') {
119 // Hyphen marks end of current word
120 if (last_was_hyphen) {
121 // Consecutive hyphens
122 return false;
123 }
124
125 // Current word must have been at least 3 chars
126 if (current_word_len < 3) {
127 return false;
128 }
129
130 word_count++;
131 current_word_len = 0;
132 last_was_hyphen = true;
133 } else {
134 // Invalid character
135 return false;
136 }
137 }
138
139 // Final word must be 3-64 characters
140 if (current_word_len < 3 || current_word_len > 64) {
141 return false;
142 }
143
144 // Must have exactly 3 words
145 return word_count == 3;
146}

Referenced by discover_session_parallel(), and parse_client_address().

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

46 {
47 if (!pubkey || !hex_out)
48 return;
49 for (int i = 0; i < 32; i++) {
50 sprintf(hex_out + (i * 2), "%02x", pubkey[i]);
51 }
52 hex_out[64] = '\0';
53}