ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
session.c
Go to the documentation of this file.
1
15#include "acds/session.h"
16#include "acds/main.h"
17#include "acds/strings.h"
18#include "log/logging.h"
20#include "util/fnv1a.h"
21#include <string.h>
22#include <time.h>
23#include <sodium.h>
24
25// ============================================================================
26// Helper Functions
27// ============================================================================
28
33static void generate_uuid(uint8_t uuid_out[16]) {
34 randombytes_buf(uuid_out, 16);
35
36 // Set version to 4 (random UUID)
37 uuid_out[6] = (uuid_out[6] & 0x0F) | 0x40;
38
39 // Set variant to RFC4122
40 uuid_out[8] = (uuid_out[8] & 0x3F) | 0x80;
41}
42
47static uint64_t get_current_time_ms(void) {
48 struct timespec ts;
49 clock_gettime(CLOCK_REALTIME, &ts);
50 return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
51}
52
59static bool verify_password(const char *password, const char *hash) {
60 return crypto_pwhash_str_verify(hash, password, strlen(password)) == 0;
61}
62
63// ============================================================================
64// RCU Hash Table Helpers
65// ============================================================================
66
76static unsigned long session_string_hash(const char *session_string) {
77 // Use FNV-1a hash (sanitizer-safe implementation)
78 return (unsigned long)fnv1a_hash_string(session_string);
79}
80
87static int session_string_match(struct cds_lfht_node *node, const void *key) {
88 const session_entry_t *entry = caa_container_of(node, session_entry_t, hash_node);
89 const char *session_string = (const char *)key;
90 return strcmp(entry->session_string, session_string) == 0;
91}
92
98static void session_free_rcu(struct rcu_head *head) {
99 session_entry_t *entry = caa_container_of(head, session_entry_t, rcu_head);
100
101 // Free all participants
102 for (size_t i = 0; i < MAX_PARTICIPANTS; i++) {
103 if (entry->participants[i]) {
104 SAFE_FREE(entry->participants[i]);
105 }
106 }
107
108 // Destroy per-entry mutex
110
111 // Free the entry itself
112 SAFE_FREE(entry);
113}
114
123static session_entry_t *find_session_by_id_rcu(session_registry_t *registry, const uint8_t session_id[16]) {
124 session_entry_t *entry;
125 struct cds_lfht_iter iter;
126
127 /* Iterate through all entries in RCU hash table */
128 cds_lfht_for_each_entry(registry->sessions, &iter, entry, hash_node) {
129 if (memcmp(entry->session_id, session_id, 16) == 0) {
130 return entry;
131 }
132 }
133 return NULL;
134}
135
142static participant_t *find_participant_locked(session_entry_t *session, const uint8_t participant_id[16]) {
143 for (size_t i = 0; i < MAX_PARTICIPANTS; i++) {
144 if (session->participants[i] && memcmp(session->participants[i]->participant_id, participant_id, 16) == 0) {
145 return session->participants[i];
146 }
147 }
148 return NULL;
149}
150
156static int find_empty_slot_locked(session_entry_t *session) {
157 for (size_t i = 0; i < MAX_PARTICIPANTS; i++) {
158 if (session->participants[i] == NULL) {
159 return (int)i;
160 }
161 }
162 return -1;
163}
164
165// ============================================================================
166// Registry Lifecycle
167// ============================================================================
168
170 if (!registry) {
171 return SET_ERRNO(ERROR_INVALID_PARAM, "registry is NULL");
172 }
173
174 memset(registry, 0, sizeof(*registry));
175
176 /* Create RCU lock-free hash table
177 - Initial size: 256 buckets
178 - Auto-resize enabled
179 - No flags needed for simplicity
180 */
181 registry->sessions = cds_lfht_new(256, 256, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
182 if (!registry->sessions) {
183 return SET_ERRNO(ERROR_MEMORY, "Failed to create RCU hash table");
184 }
185
186 log_info("Session registry initialized (RCU lock-free hash table)");
187 return ASCIICHAT_OK;
188}
189
191 if (!registry || !registry->sessions) {
192 return;
193 }
194
195 /*
196 * Destroy is called during shutdown when no other threads are accessing
197 * the registry. Still use RCU context for consistency with the rest of
198 * the codebase.
199 */
200 rcu_read_lock();
201
202 session_entry_t *entry;
203 struct cds_lfht_iter iter;
204 int deleted_count = 0;
205
206 /* Iterate and delete all entries
207 NOTE: Deleting during iteration is safe in shutdown context */
208 cds_lfht_for_each_entry(registry->sessions, &iter, entry, hash_node) {
209 int ret = cds_lfht_del(registry->sessions, &entry->hash_node);
210 if (ret == 0) {
211 /* Successfully deleted from hash table
212 Schedule deferred freeing via call_rcu() to ensure RCU readers
213 can't access the entry after we return from this function */
214 call_rcu(&entry->rcu_head, session_free_rcu);
215 deleted_count++;
216 }
217 }
218
219 rcu_read_unlock();
220
221 log_debug("Deleted %d sessions during registry shutdown", deleted_count);
222
223 /* Force RCU grace period to ensure all entries are freed before exit
224 This blocks until all RCU readers complete and all deferred callbacks execute */
225 synchronize_rcu();
226
227 /* Destroy the RCU hash table
228 It should be empty now after synchronize_rcu() completes */
229 int ret = cds_lfht_destroy(registry->sessions, NULL);
230 if (ret < 0) {
231 log_warn("Failed to destroy RCU hash table (returned %d)", ret);
232 }
233
234 log_info("Session registry destroyed");
235}
236
237// ============================================================================
238// Session Operations
239// ============================================================================
240
242 const acds_config_t *config, acip_session_created_t *resp) {
243 if (!registry || !req || !config || !resp) {
244 return SET_ERRNO(ERROR_INVALID_PARAM, "registry, req, config, or resp is NULL");
245 }
246
247 memset(resp, 0, sizeof(*resp));
248
249 // Generate or use reserved session string
250 char session_string[ACIP_MAX_SESSION_STRING_LEN] = {0};
251 if (req->reserved_string_len > 0) {
252 // Use provided string (copy from variable part after struct)
253 const char *reserved_str = (const char *)(req + 1);
254 size_t len = req->reserved_string_len < (ACIP_MAX_SESSION_STRING_LEN - 1) ? req->reserved_string_len
256 memcpy(session_string, reserved_str, len);
257 session_string[len] = '\0';
258
259 // Validate format
260 if (!acds_string_validate(session_string)) {
261 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid session string format: %s", session_string);
262 }
263 } else {
264 // Auto-generate session string
265 asciichat_error_t result = acds_string_generate(session_string, sizeof(session_string));
266 if (result != ASCIICHAT_OK) {
267 return result;
268 }
269 }
270
271 // Allocate new session entry
273 if (!session) {
274 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate session entry");
275 }
276
277 memset(session, 0, sizeof(*session));
278
279 // Initialize per-entry mutex for participant list
280 asciichat_error_t mutex_result = mutex_init(&session->participant_mutex);
281 if (mutex_result != ASCIICHAT_OK) {
282 SAFE_FREE(session);
283 return SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to initialize participant mutex");
284 }
285
286 // Initialize RCU node
287 cds_lfht_node_init(&session->hash_node);
288
289 // Fill session data
290 SAFE_STRNCPY(session->session_string, session_string, sizeof(session->session_string));
291 generate_uuid(session->session_id);
292 memcpy(session->host_pubkey, req->identity_pubkey, 32);
293 session->capabilities = req->capabilities;
294 session->max_participants =
295 req->max_participants > 0 && req->max_participants <= MAX_PARTICIPANTS ? req->max_participants : MAX_PARTICIPANTS;
296 session->has_password = req->has_password != 0;
297 session->current_participants = 0;
298
299 // Server connection information
300 SAFE_STRNCPY(session->server_address, req->server_address, sizeof(session->server_address));
301 session->server_port = req->server_port;
302
303 // Hash password if provided
304 if (session->has_password) {
305 memcpy(session->password_hash, req->password_hash, sizeof(session->password_hash));
306 }
307
308 // IP disclosure policy (explicit opt-in)
309 session->expose_ip_publicly = req->expose_ip_publicly != 0;
310
311 // Session type (Direct TCP or WebRTC)
312 session->session_type = req->session_type;
313
314 // Set timestamps
315 uint64_t now = get_current_time_ms();
316 session->created_at = now;
317 session->expires_at = now + ACIP_SESSION_EXPIRATION_MS;
318
319 /* Insert into RCU hash table
320 Returns duplicate if already exists
321 */
322 unsigned long hash = session_string_hash(session_string);
323 struct cds_lfht_node *ret_node =
324 cds_lfht_add_unique(registry->sessions, hash, session_string_match, session_string, &session->hash_node);
325
326 if (ret_node != &session->hash_node) {
327 // Duplicate exists - cleanup and return error
329 SAFE_FREE(session);
330 return SET_ERRNO(ERROR_INVALID_STATE, "Session string already exists: %s", session_string);
331 }
332
333 // Fill response
334 resp->session_string_len = (uint8_t)strlen(session_string);
335 SAFE_STRNCPY(resp->session_string, session_string, sizeof(resp->session_string));
336 memcpy(resp->session_id, session->session_id, 16);
337 resp->expires_at = session->expires_at;
338
339 // Populate STUN/TURN server counts from config
340 resp->stun_count = config->stun_count;
341 resp->turn_count = config->turn_count;
342
343 log_info("Session created: %s (max_participants=%d, has_password=%d)", session_string, session->max_participants,
344 session->has_password);
345
346 return ASCIICHAT_OK;
347}
348
349asciichat_error_t session_lookup(session_registry_t *registry, const char *session_string, const acds_config_t *config,
350 acip_session_info_t *resp) {
351 if (!registry || !session_string || !config || !resp) {
352 return SET_ERRNO(ERROR_INVALID_PARAM, "registry, session_string, config, or resp is NULL");
353 }
354
355 memset(resp, 0, sizeof(*resp));
356
357 /* RCU read-side critical section - NO LOCK ACQUIRED!
358 This is the key performance improvement: lookups never contend
359 */
360 rcu_read_lock();
361
362 // Find session by string using RCU hash table
363 unsigned long hash = session_string_hash(session_string);
364 struct cds_lfht_iter iter;
365 cds_lfht_lookup(registry->sessions, hash, session_string_match, session_string, &iter);
366 struct cds_lfht_node *node = cds_lfht_iter_get_node(&iter);
367
368 if (!node) {
369 rcu_read_unlock();
370 resp->found = 0;
371 log_debug("Session lookup failed: %s (not found)", session_string);
372 return ASCIICHAT_OK;
373 }
374
375 session_entry_t *session = caa_container_of(node, session_entry_t, hash_node);
376
377 // Fill response - session data
378 resp->found = 1;
379 memcpy(resp->session_id, session->session_id, 16);
380 memcpy(resp->host_pubkey, session->host_pubkey, 32);
381 resp->capabilities = session->capabilities;
382 resp->max_participants = session->max_participants;
383 resp->current_participants = session->current_participants;
384 resp->has_password = session->has_password;
385 resp->created_at = session->created_at;
386 resp->expires_at = session->expires_at;
387
388 // Fill response - ACDS policy flags
389 resp->require_server_verify = config->require_server_verify ? 1 : 0;
390 resp->require_client_verify = config->require_client_verify ? 1 : 0;
391
392 // Session type (Direct TCP or WebRTC)
393 resp->session_type = session->session_type;
394
395 // NOTE: Server connection information (IP/port) is NOT included in SESSION_INFO.
396 // It is only revealed after successful authentication via SESSION_JOIN to prevent
397 // IP address leakage to unauthenticated clients.
398
399 rcu_read_unlock();
400
401 log_debug("Session lookup: %s (found, participants=%d/%d)", session_string, resp->current_participants,
402 resp->max_participants);
403
404 return ASCIICHAT_OK;
405}
406
408 const acds_config_t *config, acip_session_joined_t *resp) {
409 if (!registry || !req || !config || !resp) {
410 return SET_ERRNO(ERROR_INVALID_PARAM, "registry, req, config, or resp is NULL");
411 }
412
413 memset(resp, 0, sizeof(*resp));
414 resp->success = 0;
415
416 // Extract session string (null-terminate)
417 char session_string[ACIP_MAX_SESSION_STRING_LEN] = {0};
418 size_t len = req->session_string_len < (ACIP_MAX_SESSION_STRING_LEN - 1) ? req->session_string_len
420 memcpy(session_string, req->session_string, len);
421 session_string[len] = '\0';
422
423 /* RCU read-side critical section for lookup
424 (Read-side critical section, not write lock!)
425 */
426 rcu_read_lock();
427
428 // Find session
429 unsigned long hash = session_string_hash(session_string);
430 struct cds_lfht_iter iter;
431 cds_lfht_lookup(registry->sessions, hash, session_string_match, session_string, &iter);
432 struct cds_lfht_node *node = cds_lfht_iter_get_node(&iter);
433
434 if (!node) {
435 rcu_read_unlock();
436 resp->error_code = ACIP_ERROR_SESSION_NOT_FOUND;
437 SAFE_STRNCPY(resp->error_message, "Session not found", sizeof(resp->error_message));
438 log_warn("Session join failed: %s (not found)", session_string);
439 return ASCIICHAT_OK;
440 }
441
442 session_entry_t *session = caa_container_of(node, session_entry_t, hash_node);
443
444 // Acquire fine-grained per-entry mutex for participant modifications
445 mutex_lock(&session->participant_mutex);
446
447 // Check if session full
448 if (session->current_participants >= session->max_participants) {
450 rcu_read_unlock();
451 resp->error_code = ACIP_ERROR_SESSION_FULL;
452 SAFE_STRNCPY(resp->error_message, "Session is full", sizeof(resp->error_message));
453 log_warn("Session join failed: %s (full)", session_string);
454 return ASCIICHAT_OK;
455 }
456
457 // Verify password if required
458 if (session->has_password && req->has_password) {
459 if (!verify_password(req->password, session->password_hash)) {
461 rcu_read_unlock();
462 resp->error_code = ACIP_ERROR_INVALID_PASSWORD;
463 SAFE_STRNCPY(resp->error_message, "Invalid password", sizeof(resp->error_message));
464 log_warn("Session join failed: %s (invalid password)", session_string);
465 return ASCIICHAT_OK;
466 }
467 } else if (session->has_password && !req->has_password) {
469 rcu_read_unlock();
470 resp->error_code = ACIP_ERROR_INVALID_PASSWORD;
471 SAFE_STRNCPY(resp->error_message, "Password required", sizeof(resp->error_message));
472 log_warn("Session join failed: %s (password required)", session_string);
473 return ASCIICHAT_OK;
474 }
475
476 // Find empty participant slot
477 int slot = find_empty_slot_locked(session);
478 if (slot < 0) {
480 rcu_read_unlock();
481 resp->error_code = ACIP_ERROR_SESSION_FULL;
482 SAFE_STRNCPY(resp->error_message, "No participant slots available", sizeof(resp->error_message));
483 log_error("Session join failed: %s (no slots, but count was not full)", session_string);
484 return ASCIICHAT_OK;
485 }
486
487 // Allocate participant
488 participant_t *participant = SAFE_MALLOC(sizeof(participant_t), participant_t *);
489 if (!participant) {
491 rcu_read_unlock();
492 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate participant");
493 }
494
495 memset(participant, 0, sizeof(*participant));
496
497 // Fill participant data
498 generate_uuid(participant->participant_id);
499 memcpy(participant->identity_pubkey, req->identity_pubkey, 32);
500 participant->joined_at = get_current_time_ms();
501
502 // Add participant to session
503 session->participants[slot] = participant;
504 session->current_participants++;
505
506 // Fill response
507 resp->success = 1;
508 resp->error_code = ACIP_ERROR_NONE;
509 memcpy(resp->participant_id, participant->participant_id, 16);
510 memcpy(resp->session_id, session->session_id, 16);
511
512 // Server connection information (CRITICAL SECURITY: Conditional IP disclosure)
513 bool reveal_ip = false;
514
515 if (session->has_password) {
516 // Password was already verified
517 reveal_ip = true;
518 } else if (session->expose_ip_publicly) {
519 // No password, but explicit opt-in via --acds-expose-ip
520 reveal_ip = true;
521 } else {
522 log_warn("Session join: %s has no password and expose_ip_publicly=false - IP NOT REVEALED", session_string);
523 reveal_ip = false;
524 }
525
526 if (reveal_ip) {
527 SAFE_STRNCPY(resp->server_address, session->server_address, sizeof(resp->server_address));
528 resp->server_port = session->server_port;
529 resp->session_type = session->session_type;
530
531 // Generate TURN credentials for WebRTC sessions
532 if (session->session_type == SESSION_TYPE_WEBRTC && config->turn_secret[0] != '\0') {
533 turn_credentials_t turn_creds;
534 asciichat_error_t turn_result = turn_generate_credentials(session_string, config->turn_secret, 86400, // 24 hours
535 &turn_creds);
536 if (turn_result == ASCIICHAT_OK) {
537 SAFE_STRNCPY(resp->turn_username, turn_creds.username, sizeof(resp->turn_username));
538 SAFE_STRNCPY(resp->turn_password, turn_creds.password, sizeof(resp->turn_password));
539 log_debug("Generated TURN credentials for session %s", session_string);
540 } else {
541 log_warn("Failed to generate TURN credentials for session %s", session_string);
542 }
543 }
544
545 log_info("Participant joined session %s (participants=%d/%d, server=%s:%d, type=%s)", session_string,
546 session->current_participants, session->max_participants, resp->server_address, resp->server_port,
547 session->session_type == SESSION_TYPE_WEBRTC ? "WebRTC" : "DirectTCP");
548 } else {
549 log_info("Participant joined session %s (participants=%d/%d, IP WITHHELD - auth required)", session_string,
550 session->current_participants, session->max_participants);
551 }
552
554 rcu_read_unlock();
555
556 return ASCIICHAT_OK;
557}
558
560 const uint8_t participant_id[16]) {
561 if (!registry || !session_id || !participant_id) {
562 return SET_ERRNO(ERROR_INVALID_PARAM, "registry, session_id, or participant_id is NULL");
563 }
564
565 rcu_read_lock();
566
567 // Find session by ID
568 session_entry_t *session = find_session_by_id_rcu(registry, session_id);
569 if (!session) {
570 rcu_read_unlock();
571 return SET_ERRNO(ERROR_INVALID_STATE, "Session not found");
572 }
573
574 // Acquire per-entry mutex for participant modifications
575 mutex_lock(&session->participant_mutex);
576
577 // Find and remove participant
578 participant_t *participant = find_participant_locked(session, participant_id);
579 if (!participant) {
581 rcu_read_unlock();
582 return SET_ERRNO(ERROR_INVALID_STATE, "Participant not in session");
583 }
584
585 // Remove participant
586 for (size_t i = 0; i < MAX_PARTICIPANTS; i++) {
587 if (session->participants[i] == participant) {
588 SAFE_FREE(session->participants[i]);
589 session->current_participants--;
590 break;
591 }
592 }
593
594 log_info("Participant left session %s (participants=%d/%d)", session->session_string, session->current_participants,
595 session->max_participants);
596
597 // If no participants left, mark session for deletion
598 bool should_delete = (session->current_participants == 0);
599
601
602 if (should_delete) {
603 log_info("Session %s has no participants, deleting", session->session_string);
604
605 // Delete from RCU hash table and schedule deferred freeing
606 cds_lfht_del(registry->sessions, &session->hash_node);
607 call_rcu(&session->rcu_head, session_free_rcu);
608 }
609
610 rcu_read_unlock();
611
612 return ASCIICHAT_OK;
613}
614
616 if (!registry || !registry->sessions) {
617 return;
618 }
619
620 uint64_t now = get_current_time_ms();
621 size_t removed_count = 0;
622
623 rcu_read_lock();
624
625 session_entry_t *entry;
626 struct cds_lfht_iter iter;
627
628 // Iterate through hash table and collect expired sessions
629 cds_lfht_for_each_entry(registry->sessions, &iter, entry, hash_node) {
630 if (now > entry->expires_at) {
631 log_info("Session %s expired (created_at=%llu, expires_at=%llu, now=%llu)", entry->session_string,
632 (unsigned long long)entry->created_at, (unsigned long long)entry->expires_at, (unsigned long long)now);
633
634 // Delete from hash table
635 cds_lfht_del(registry->sessions, &entry->hash_node);
636
637 // Schedule deferred freeing via RCU callback
638 call_rcu(&entry->rcu_head, session_free_rcu);
639 removed_count++;
640 }
641 }
642
643 rcu_read_unlock();
644
645 if (removed_count > 0) {
646 log_info("Cleaned up %zu expired sessions", removed_count);
647
648 // Optionally synchronize RCU if many deletions to avoid callback backlog
649 if (removed_count > 100) {
650 log_debug("Synchronizing RCU after bulk session cleanup");
651 synchronize_rcu();
652 }
653 }
654}
🔍 ASCII-Chat Discovery Service (acds) main entry point
#️⃣ FNV-1a Hash Function Implementation
acip_session_join_t
acip_session_joined_t
#define ACIP_MAX_SESSION_STRING_LEN
Maximum session string length (e.g., "swift-river-mountain" = 20 chars)
acip_session_created_t
acip_session_create_t
acip_session_info_t
#define ACIP_SESSION_EXPIRATION_MS
Session expiration time (24 hours in milliseconds)
@ ACIP_ERROR_INVALID_PASSWORD
Password verification failed.
@ ACIP_ERROR_SESSION_NOT_FOUND
Session does not exist.
@ ACIP_ERROR_SESSION_FULL
Session has reached max participants.
@ ACIP_ERROR_NONE
No error (success)
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#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)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_PLATFORM_INIT
Definition error_codes.h:57
@ 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_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
📝 Logging API with multiple log levels and terminal output control
void session_registry_destroy(session_registry_t *registry)
Destroy session registry.
Definition session.c:190
asciichat_error_t session_leave(session_registry_t *registry, const uint8_t session_id[16], const uint8_t participant_id[16])
Leave session.
Definition session.c:559
asciichat_error_t session_registry_init(session_registry_t *registry)
Initialize session registry.
Definition session.c:169
asciichat_error_t session_create(session_registry_t *registry, const acip_session_create_t *req, const acds_config_t *config, acip_session_created_t *resp)
Create new session.
Definition session.c:241
asciichat_error_t session_join(session_registry_t *registry, const acip_session_join_t *req, const acds_config_t *config, acip_session_joined_t *resp)
Join existing session.
Definition session.c:407
asciichat_error_t session_lookup(session_registry_t *registry, const char *session_string, const acds_config_t *config, acip_session_info_t *resp)
Lookup session by string.
Definition session.c:349
void session_cleanup_expired(session_registry_t *registry)
Clean up expired sessions.
Definition session.c:615
🎯 Session registry for discovery service (lock-free RCU implementation)
#define MAX_PARTICIPANTS
Maximum participants per session.
Definition session.h:36
uint8_t session_id[16]
uint8_t participant_id[16]
bool acds_string_validate(const char *str)
Validate session string format.
Definition strings.c:80
asciichat_error_t acds_string_generate(char *output, size_t output_size)
Generate random session string.
Definition strings.c:55
Session string generation for discovery service.
Discovery server configuration.
Definition acds/main.h:71
bool require_server_verify
ACDS policy: require servers to verify client identity during handshake.
Definition acds/main.h:81
uint8_t turn_count
Number of configured TURN servers (0-4)
Definition acds/main.h:87
char turn_secret[256]
Shared secret for TURN credential generation (HMAC-SHA1)
Definition acds/main.h:89
uint8_t stun_count
Number of configured STUN servers (0-4)
Definition acds/main.h:85
bool require_client_verify
ACDS policy: require clients to verify server identity during handshake.
Definition acds/main.h:82
Participant in a session.
Definition session.h:41
uint8_t participant_id[16]
UUID.
Definition session.h:42
uint8_t identity_pubkey[32]
Ed25519 public key.
Definition session.h:43
uint64_t joined_at
Unix timestamp (ms)
Definition session.h:44
Session entry (RCU hash table node)
Definition session.h:55
bool expose_ip_publicly
Allow IP disclosure without verification (explicit opt-in via –acds-expose-ip)
Definition session.h:66
mutex_t participant_mutex
Fine-grained lock for participant list.
Definition session.h:77
bool has_password
Password protection flag.
Definition session.h:65
char server_address[64]
IPv4/IPv6 address or hostname.
Definition session.h:73
uint8_t max_participants
1-8
Definition session.h:61
uint8_t host_pubkey[32]
Host's Ed25519 key.
Definition session.h:59
struct cds_lfht_node hash_node
RCU lock-free hash table node (keyed by session_string)
Definition session.h:80
uint8_t session_id[16]
UUID.
Definition session.h:57
char password_hash[128]
Argon2id hash (if has_password)
Definition session.h:64
uint64_t created_at
Unix timestamp (ms)
Definition session.h:69
char session_string[48]
e.g., "swift-river-mountain" (lookup key)
Definition session.h:56
struct rcu_head rcu_head
For deferred freeing via call_rcu()
Definition session.h:81
uint64_t expires_at
Unix timestamp (ms) - created_at + 24h.
Definition session.h:70
uint8_t capabilities
bit 0: video, bit 1: audio
Definition session.h:60
uint8_t session_type
acds_session_type_t: 0=DIRECT_TCP, 1=WEBRTC
Definition session.h:67
participant_t * participants[8]
Participant array.
Definition session.h:76
uint16_t server_port
Port number for client connection.
Definition session.h:74
uint8_t current_participants
Active participant count.
Definition session.h:62
Session registry (lock-free RCU)
Definition session.h:92
struct cds_lfht * sessions
RCU lock-free hash table.
Definition session.h:93
TURN server credentials (username + password)
⏱️ High-precision timing utilities using sokol_time.h and uthash
asciichat_error_t turn_generate_credentials(const char *session_id, const char *secret, uint32_t validity_seconds, turn_credentials_t *out_credentials)
Generate time-limited TURN credentials.
TURN server credential generation for WebRTC.