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

🎯 Session registry implementation (lock-free RCU) More...

Go to the source code of this file.

Functions

asciichat_error_t session_registry_init (session_registry_t *registry)
 Initialize session registry.
 
void session_registry_destroy (session_registry_t *registry)
 Destroy session registry.
 
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.
 
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.
 
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.
 
asciichat_error_t session_leave (session_registry_t *registry, const uint8_t session_id[16], const uint8_t participant_id[16])
 Leave session.
 
void session_cleanup_expired (session_registry_t *registry)
 Clean up expired sessions.
 

Detailed Description

🎯 Session registry implementation (lock-free RCU)

High-performance session management using liburcu (Read-Copy-Update):

  • Lock-free read-side operations (no locks acquired on lookups)
  • RCU hash table (cds_lfht) replacing uthash + rwlock
  • Fine-grained per-entry locking for participant modifications
  • Deferred memory freeing via call_rcu() for thread safety

Sessions are ephemeral (24-hour expiration) and stored in memory. Expected 5-10x performance improvement under high concurrency.

Definition in file session.c.

Function Documentation

◆ session_cleanup_expired()

void session_cleanup_expired ( session_registry_t registry)

Clean up expired sessions.

Removes sessions that have exceeded their 24-hour lifetime. Called periodically by background cleanup thread.

Parameters
registrySession registry

Definition at line 615 of file session.c.

615 {
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}
unsigned long long uint64_t
Definition common.h:59
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
Session entry (RCU hash table node)
Definition session.h:55
struct cds_lfht_node hash_node
RCU lock-free hash table node (keyed by session_string)
Definition session.h:80
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
struct cds_lfht * sessions
RCU lock-free hash table.
Definition session.h:93

References session_entry::created_at, session_entry::expires_at, session_entry::hash_node, log_debug, log_info, session_entry::rcu_head, session_entry::session_string, and session_registry_t::sessions.

◆ session_create()

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.

Parameters
registrySession registry
reqSession creation request
respSession creation response (output)
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 241 of file session.c.

242 {
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}
#define ACIP_MAX_SESSION_STRING_LEN
Maximum session string length (e.g., "swift-river-mountain" = 20 chars)
#define ACIP_SESSION_EXPIRATION_MS
Session expiration time (24 hours in milliseconds)
#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 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
int mutex_init(mutex_t *mutex)
Initialize a mutex.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
#define MAX_PARTICIPANTS
Maximum participants per session.
Definition session.h:36
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
uint8_t turn_count
Number of configured TURN servers (0-4)
Definition acds/main.h:87
uint8_t stun_count
Number of configured STUN servers (0-4)
Definition acds/main.h:85
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
uint8_t session_id[16]
UUID.
Definition session.h:57
char password_hash[128]
Argon2id hash (if has_password)
Definition session.h:64
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
uint16_t server_port
Port number for client connection.
Definition session.h:74
uint8_t current_participants
Active participant count.
Definition session.h:62

References acds_string_generate(), acds_string_validate(), ACIP_MAX_SESSION_STRING_LEN, ACIP_SESSION_EXPIRATION_MS, ASCIICHAT_OK, session_entry::capabilities, session_entry::created_at, session_entry::current_participants, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_PLATFORM_INIT, session_entry::expires_at, session_entry::expose_ip_publicly, session_entry::has_password, session_entry::hash_node, session_entry::host_pubkey, log_info, MAX_PARTICIPANTS, session_entry::max_participants, mutex_destroy(), mutex_init(), session_entry::participant_mutex, session_entry::password_hash, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, session_entry::server_address, session_entry::server_port, session_entry::session_id, session_entry::session_string, session_entry::session_type, session_registry_t::sessions, SET_ERRNO, acds_config_t::stun_count, and acds_config_t::turn_count.

◆ session_join()

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.

Parameters
registrySession registry
reqJoin request
configACDS server configuration (for TURN credentials)
respJoin response (output)
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 407 of file session.c.

408 {
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}
@ 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 log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#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
char turn_secret[256]
Shared secret for TURN credential generation (HMAC-SHA1)
Definition acds/main.h:89
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
participant_t * participants[8]
Participant array.
Definition session.h:76
TURN server credentials (username + password)
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.

References ACIP_ERROR_INVALID_PASSWORD, ACIP_ERROR_NONE, ACIP_ERROR_SESSION_FULL, ACIP_ERROR_SESSION_NOT_FOUND, ACIP_MAX_SESSION_STRING_LEN, ASCIICHAT_OK, session_entry::current_participants, ERROR_INVALID_PARAM, ERROR_MEMORY, session_entry::expose_ip_publicly, session_entry::has_password, participant_t::identity_pubkey, participant_t::joined_at, log_debug, log_error, log_info, log_warn, session_entry::max_participants, mutex_lock, mutex_unlock, participant_t::participant_id, session_entry::participant_mutex, session_entry::participants, turn_credentials_t::password, session_entry::password_hash, SAFE_MALLOC, SAFE_STRNCPY, session_entry::server_address, session_entry::server_port, session_entry::session_id, session_entry::session_type, SESSION_TYPE_WEBRTC, session_registry_t::sessions, SET_ERRNO, turn_generate_credentials(), acds_config_t::turn_secret, and turn_credentials_t::username.

◆ session_leave()

asciichat_error_t session_leave ( session_registry_t registry,
const uint8_t  session_id[16],
const uint8_t  participant_id[16] 
)

Leave session.

Parameters
registrySession registry
session_idSession UUID
participant_idParticipant UUID
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 559 of file session.c.

560 {
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}
uint8_t session_id[16]
uint8_t participant_id[16]

References ASCIICHAT_OK, session_entry::current_participants, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, session_entry::hash_node, log_info, MAX_PARTICIPANTS, session_entry::max_participants, mutex_lock, mutex_unlock, participant_id, session_entry::participant_mutex, session_entry::participants, session_entry::rcu_head, SAFE_FREE, session_id, session_entry::session_string, session_registry_t::sessions, and SET_ERRNO.

◆ session_lookup()

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.

Parameters
registrySession registry
session_stringSession string to look up
configACDS server configuration (for policy flags)
respSession info response (output)
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 349 of file session.c.

350 {
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}
bool require_server_verify
ACDS policy: require servers to verify client identity during handshake.
Definition acds/main.h:81
bool require_client_verify
ACDS policy: require clients to verify server identity during handshake.
Definition acds/main.h:82

References ASCIICHAT_OK, session_entry::capabilities, session_entry::created_at, session_entry::current_participants, ERROR_INVALID_PARAM, session_entry::expires_at, session_entry::has_password, session_entry::host_pubkey, log_debug, session_entry::max_participants, acds_config_t::require_client_verify, acds_config_t::require_server_verify, session_entry::session_id, session_entry::session_type, session_registry_t::sessions, and SET_ERRNO.

◆ session_registry_destroy()

void session_registry_destroy ( session_registry_t registry)

Destroy session registry.

Parameters
registryRegistry to destroy

Definition at line 190 of file session.c.

190 {
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}

References session_entry::hash_node, log_debug, log_info, log_warn, session_entry::rcu_head, and session_registry_t::sessions.

Referenced by acds_server_init(), and acds_server_shutdown().

◆ session_registry_init()

asciichat_error_t session_registry_init ( session_registry_t registry)

Initialize session registry.

Parameters
registryRegistry structure to initialize
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 169 of file session.c.

169 {
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}

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_MEMORY, log_info, session_registry_t::sessions, and SET_ERRNO.

Referenced by acds_server_init().