ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
rcu.c
Go to the documentation of this file.
1
12#include <ascii-chat/common.h>
13#include <ascii-chat/log/log.h>
18
19#include <ascii-chat/atomic.h>
20#include <stdlib.h>
21#include <string.h>
22#ifndef _WIN32
23#include <pthread.h>
24#endif
25
26// ============================================================================
27// RCU State: Global Atomic Pointer
28// ============================================================================
29
38static atomic_ptr_t g_options = {0};
39
51static mutex_t g_options_write_mutex;
52
56static lifecycle_t g_options_lifecycle = LIFECYCLE_INIT;
57// Track the PID to detect fork() - after fork, child needs to reinitialize
58static pid_t g_init_pid = -1;
59
83static const options_t g_default_options = (options_t){
84 // ========================================================================
85 // Mode Detection (auto-detected, default here is uninitialized)
86 // ========================================================================
88
89 // ========================================================================
90 // Binary-Level Options (parsed before mode selection)
91 // ========================================================================
92 .help = OPT_HELP_DEFAULT,
93 .version = OPT_VERSION_DEFAULT,
94 .config_file = OPT_STRING_EMPTY_DEFAULT,
95
96 // ========================================================================
97 // Terminal Dimensions
98 // ========================================================================
99 .width = OPT_WIDTH_DEFAULT,
100 .height = OPT_HEIGHT_DEFAULT,
101 .auto_width = OPT_AUTO_WIDTH_DEFAULT,
102 .auto_height = OPT_AUTO_HEIGHT_DEFAULT,
103
104 // ========================================================================
105 // Network Options
106 // ========================================================================
107 .address = OPT_ADDRESS_DEFAULT,
108 .address6 = OPT_ADDRESS6_DEFAULT,
109 .port = OPT_PORT_INT_DEFAULT,
110 .websocket_port = OPT_WEBSOCKET_PORT_SERVER_DEFAULT,
111 .max_clients = OPT_MAX_CLIENTS_DEFAULT,
112 .session_string = OPT_STRING_EMPTY_DEFAULT,
113
114 // ========================================================================
115 // Discovery Service (ACDS) Options
116 // ========================================================================
117 .discovery = OPT_ACDS_DEFAULT,
118 .discovery_expose_ip = OPT_ACDS_EXPOSE_IP_DEFAULT,
119 .discovery_insecure = OPT_ACDS_INSECURE_DEFAULT,
120 .discovery_port = OPT_ACDS_PORT_INT_DEFAULT,
121 .discovery_server = OPT_STRING_EMPTY_DEFAULT,
122 .discovery_service_key = OPT_STRING_EMPTY_DEFAULT,
123 .discovery_database_path = OPT_STRING_EMPTY_DEFAULT,
124 .webrtc = OPT_WEBRTC_DEFAULT,
125
126 // ========================================================================
127 // LAN Discovery & WebRTC Options
128 // ========================================================================
129 .lan_discovery = OPT_LAN_DISCOVERY_DEFAULT,
130 .no_mdns_advertise = OPT_NO_MDNS_ADVERTISE_DEFAULT,
131 .enable_upnp = OPT_ENABLE_UPNP_DEFAULT,
132
133 // ========================================================================
134 // WebRTC Connection Strategy Options
135 // ========================================================================
136 .prefer_webrtc = OPT_PREFER_WEBRTC_DEFAULT,
137 .no_webrtc = OPT_NO_WEBRTC_DEFAULT,
138 .webrtc_skip_stun = OPT_WEBRTC_SKIP_STUN_DEFAULT,
139 .webrtc_relay_only = false,
140 .webrtc_disable_turn = OPT_WEBRTC_DISABLE_TURN_DEFAULT,
141 .webrtc_skip_host = OPT_WEBRTC_SKIP_HOST_DEFAULT,
142 .webrtc_ice_timeout_ms = OPT_WEBRTC_ICE_TIMEOUT_MS_DEFAULT,
143 .webrtc_reconnect_attempts = OPT_WEBRTC_RECONNECT_ATTEMPTS_DEFAULT,
144
145 // ========================================================================
146 // WebRTC Connectivity Options (ACDS mode)
147 // ========================================================================
148 .stun_servers = OPT_STUN_SERVERS_DEFAULT,
149 .turn_servers = OPT_TURN_SERVERS_DEFAULT,
150 .turn_username = OPT_TURN_USERNAME_DEFAULT,
151 .turn_credential = OPT_TURN_CREDENTIAL_DEFAULT,
152 .turn_secret = OPT_STRING_EMPTY_DEFAULT,
153
154 // ========================================================================
155 // Encryption & Security Options
156 // ========================================================================
157 .encrypt_enabled = OPT_ENCRYPT_ENABLED_DEFAULT,
158 .no_encrypt = OPT_NO_ENCRYPT_DEFAULT,
159 .no_auth = OPT_NO_AUTH_DEFAULT,
160 .encrypt_key = OPT_STRING_EMPTY_DEFAULT,
161 .encrypt_keyfile = OPT_STRING_EMPTY_DEFAULT,
162 .password = OPT_STRING_EMPTY_DEFAULT,
163 .server_key = OPT_STRING_EMPTY_DEFAULT,
164 .client_keys = OPT_STRING_EMPTY_DEFAULT,
165 .require_server_identity = OPT_REQUIRE_SERVER_IDENTITY_DEFAULT,
166 .require_client_identity = OPT_REQUIRE_CLIENT_IDENTITY_DEFAULT,
167 .require_server_verify = OPT_REQUIRE_SERVER_VERIFY_DEFAULT,
168 .require_client_verify = OPT_REQUIRE_CLIENT_VERIFY_DEFAULT,
169
170 // ========================================================================
171 // Media Options
172 // ========================================================================
173 .media_file = OPT_STRING_EMPTY_DEFAULT,
174 .media_url = OPT_STRING_EMPTY_DEFAULT,
175 .media_loop = OPT_MEDIA_LOOP_DEFAULT,
176 .media_from_stdin = OPT_MEDIA_FROM_STDIN_DEFAULT,
177 .media_seek_timestamp = OPT_MEDIA_SEEK_TIMESTAMP_DEFAULT,
178 .pause = OPT_PAUSE_DEFAULT,
179 .yt_dlp_options = OPT_STRING_EMPTY_DEFAULT,
180
181 // ========================================================================
182 // Webcam Options
183 // ========================================================================
184 .webcam_index = OPT_WEBCAM_INDEX_DEFAULT,
185 .test_pattern = OPT_TEST_PATTERN_DEFAULT,
186 .no_audio_mixer = OPT_NO_AUDIO_MIXER_DEFAULT,
187 .stretch = OPT_STRETCH_DEFAULT,
188 .flip_x = OPT_FLIP_X_DEFAULT,
189 .flip_y = OPT_FLIP_Y_DEFAULT,
190
191 // ========================================================================
192 // Display Options
193 // ========================================================================
194 .color = OPT_COLOR_DEFAULT,
195 .color_mode = OPT_COLOR_MODE_DEFAULT,
196 .color_filter = OPT_COLOR_FILTER_DEFAULT,
197 .color_scheme_name = OPT_COLOR_SCHEME_NAME_DEFAULT,
198 .render_mode = OPT_RENDER_MODE_DEFAULT,
199 .show_capabilities = OPT_SHOW_CAPABILITIES_DEFAULT,
200 .fps = OPT_FPS_DEFAULT,
201 .fps_explicitly_set = false,
202 .palette_type = OPT_PALETTE_TYPE_DEFAULT,
203 .palette_custom = OPT_STRING_EMPTY_DEFAULT,
204 .palette_custom_set = OPT_PALETTE_CUSTOM_SET_DEFAULT,
205
206 // ========================================================================
207 // Compression Options
208 // ========================================================================
209 .compression_level = OPT_COMPRESSION_LEVEL_DEFAULT,
210 .no_compress = OPT_NO_COMPRESS_DEFAULT,
211
212 // ========================================================================
213 // Audio Options
214 // ========================================================================
215 .audio_enabled = OPT_AUDIO_ENABLED_DEFAULT,
216 .audio_source = OPT_AUDIO_SOURCE_DEFAULT,
217 .audio_capture_source = OPT_AUDIO_CAPTURE_SOURCE_DEFAULT,
218 .waveform = false,
219 .fft = false,
220 .microphone_index = OPT_MICROPHONE_INDEX_DEFAULT,
221 .speakers_index = OPT_SPEAKERS_INDEX_DEFAULT,
222 .microphone_sensitivity = OPT_MICROPHONE_SENSITIVITY_DEFAULT,
223 .speakers_volume = OPT_SPEAKERS_VOLUME_DEFAULT,
224 .audio_analysis_enabled = OPT_AUDIO_ANALYSIS_ENABLED_DEFAULT,
225 .audio_no_playback = OPT_AUDIO_NO_PLAYBACK_DEFAULT,
226 .encode_audio = OPT_ENCODE_AUDIO_DEFAULT,
227
228 // ========================================================================
229 // Terminal & Output Options (consolidated)
230 // ========================================================================
231 .force_utf8 = OPT_FORCE_UTF8_DEFAULT,
232 .quiet = OPT_QUIET_DEFAULT,
233 .verbose_level = OPT_VERBOSE_LEVEL_DEFAULT,
234 .snapshot_mode = OPT_SNAPSHOT_MODE_DEFAULT,
235 .snapshot_delay = OPT_SNAPSHOT_DELAY_DEFAULT,
236 .matrix_rain = OPT_MATRIX_RAIN_DEFAULT,
237 .strip_ansi = OPT_STRIP_ANSI_DEFAULT,
238 .log_file = OPT_STRING_EMPTY_DEFAULT,
239 .log_level = OPT_LOG_LEVEL_DEFAULT,
240 .grep_pattern = OPT_GREP_PATTERN_DEFAULT,
241 .json = OPT_JSON_DEFAULT,
242 .log_template = OPT_LOG_TEMPLATE_DEFAULT,
243 .log_format_console_only = OPT_LOG_FORMAT_CONSOLE_DEFAULT,
244 .enable_keepawake = OPT_ENABLE_KEEPAWAKE_DEFAULT,
245 .disable_keepawake = OPT_DISABLE_KEEPAWAKE_DEFAULT,
246
247 // ========================================================================
248 // UI Screen Options
249 // ========================================================================
250 .splash_screen = OPT_SPLASH_DEFAULT,
251 .status_screen = OPT_STATUS_SCREEN_DEFAULT,
252
253 // ========================================================================
254 // Remaining Options (zero-initialized by default)
255 // ========================================================================
256 .reconnect_attempts = OPT_RECONNECT_ATTEMPTS_DEFAULT,
257 .splash_screen_explicitly_set = OPT_SPLASH_SCREEN_EXPLICITLY_SET_DEFAULT,
258 .status_screen_explicitly_set = OPT_STATUS_SCREEN_EXPLICITLY_SET_DEFAULT,
259 .no_check_update = OPT_NO_CHECK_UPDATE_DEFAULT,
260
261 // All remaining array fields (identity_keys, num_identity_keys, etc.)
262 // are zero-initialized (empty strings, NULL pointers, 0 values, etc.)
263 // which is appropriate for their types.
264};
265
266// ============================================================================
267// Memory Reclamation Strategy
268// ============================================================================
269
283#define MAX_DEFERRED_FREES 64
284static options_t *g_deferred_frees[MAX_DEFERRED_FREES];
285static size_t g_deferred_free_count = 0;
286static mutex_t g_deferred_free_mutex;
287
293static void deferred_free_add(options_t *old_opts) {
294 if (!old_opts) {
295 return;
296 }
297
298 mutex_lock(&g_deferred_free_mutex);
299
300 if (g_deferred_free_count < MAX_DEFERRED_FREES) {
301 g_deferred_frees[g_deferred_free_count++] = old_opts;
302 log_debug("Added options struct %p to deferred free list (count=%zu)", (void *)old_opts, g_deferred_free_count);
303 } else {
304 // List full - free immediately (risky but unlikely with infrequent updates)
305 log_warn("Deferred free list full (%d entries), freeing options struct %p immediately", MAX_DEFERRED_FREES,
306 (void *)old_opts);
307 SAFE_FREE(old_opts);
308 }
309
310 mutex_unlock(&g_deferred_free_mutex);
311}
312
318static void deferred_free_all(void) {
319 mutex_lock(&g_deferred_free_mutex);
320
321 log_debug("Freeing %zu deferred options structs", g_deferred_free_count);
322
323 for (size_t i = 0; i < g_deferred_free_count; i++) {
324 SAFE_FREE(g_deferred_frees[i]);
325 }
326
327 g_deferred_free_count = 0;
328 mutex_unlock(&g_deferred_free_mutex);
329}
330
331// ============================================================================
332// Fork Handler Registration
333// ============================================================================
334
335#ifndef _WIN32
342static void options_rcu_atfork_child(void) {
343 /* Reset the lifecycle state to allow reinitialization after fork */
344 /* This is safe because lifecycle_t is just an atomic int, can be reset directly */
345 atomic_store_int(&g_options_lifecycle.state, LIFECYCLE_UNINITIALIZED);
346
347 /* Reset the atomic options pointer to avoid dangling references */
348 /* The parent's allocated memory is not accessible/valid in the child */
349 atomic_ptr_store(&g_options, NULL);
350
351 /* Don't reset g_init_pid - we'll detect the fork in options_state_init */
352}
353
360__attribute__((constructor)) static void register_fork_handlers_constructor(void) {
361 // Register the child handler that will be called after fork() in the child process
362 pthread_atfork(NULL, NULL, options_rcu_atfork_child);
363}
364#endif
365
366// ============================================================================
367// Public API Implementation
368// ============================================================================
369
371 // RCU setters validate against the option registry, including when callers
372 // use the state API directly in tests or embedded integrations. Initialize
373 // the registry here so those callers observe the same metadata as the CLI.
375
376 // Detect fork: after fork(), child process must reinitialize
377 pid_t current_pid = platform_get_pid();
378 if ((lifecycle_is_initialized(&g_options_lifecycle) || g_init_pid != -1) && g_init_pid != current_pid) {
379 log_debug("Detected fork in options_state_init (parent PID %d, child PID %d) - resetting lifecycle", g_init_pid,
380 current_pid);
381 /* Reset lifecycle to allow reinitialization after fork */
382 lifecycle_shutdown(&g_options_lifecycle);
383 g_init_pid = current_pid;
384 }
385
386 // Check if already initialized in this process using lifecycle
387 if (lifecycle_is_initialized(&g_options_lifecycle) && g_init_pid == current_pid &&
388 atomic_ptr_load(&g_options) != NULL) {
389 log_warn("Options state already initialized");
390 return ASCIICHAT_OK;
391 }
392 if (lifecycle_is_initialized(&g_options_lifecycle) && atomic_ptr_load(&g_options) == NULL) {
393 lifecycle_shutdown(&g_options_lifecycle);
394 }
395
396 /* Use lifecycle to serialize initialization */
397 if (!lifecycle_init(&g_options_lifecycle, "options")) {
398 /* Another thread is initializing or already initialized */
399 if (lifecycle_is_initialized(&g_options_lifecycle)) {
400 return ASCIICHAT_OK;
401 }
402 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to acquire options initialization lock");
403 }
404
405 // Initialize write mutex
406 if (mutex_init(&g_options_write_mutex, "options_write") != 0) {
407 lifecycle_shutdown(&g_options_lifecycle);
408 return SET_ERRNO(ERROR_THREAD, "Failed to initialize options write mutex");
409 }
410
411 // Initialize deferred free mutex
412 if (mutex_init(&g_deferred_free_mutex, "deferred_free") != 0) {
413 mutex_destroy(&g_options_write_mutex);
414 lifecycle_shutdown(&g_options_lifecycle);
415 return SET_ERRNO(ERROR_THREAD, "Failed to initialize deferred free mutex");
416 }
417
418 // Allocate initial options struct (will be populated later by options_state_populate_from_globals)
419 options_t *initial_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
420 if (!initial_opts) {
421 mutex_destroy(&g_options_write_mutex);
422 mutex_destroy(&g_deferred_free_mutex);
423 lifecycle_shutdown(&g_options_lifecycle);
424 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate initial options struct");
425 }
426
427 // Initialize all defaults using options_t_new()
428 *initial_opts = options_t_new();
429
430 // Publish initial struct (release semantics - make all fields visible to readers)
431 atomic_ptr_store(&g_options, initial_opts);
432
433 g_init_pid = current_pid; // Record PID to detect fork later
434 log_dev("Options state initialized with RCU pattern (PID %d)", current_pid);
435
436 return ASCIICHAT_OK;
437}
438
440 if (!opts) {
441 return SET_ERRNO(ERROR_INVALID_PARAM, "opts is NULL");
442 }
443
444 // Check initialization state using lifecycle
445 if (!lifecycle_is_initialized(&g_options_lifecycle)) {
446 return SET_ERRNO(ERROR_INVALID_STATE, "Options state not initialized (call options_state_init first)");
447 }
448
449 // Get current struct (should be the initial zero-initialized one during startup)
450 options_t *current = atomic_ptr_load(&g_options);
451
452 // Copy provided options into current struct
453 // Note: No need for RCU update here since this is called during initialization
454 // before any reader threads exist
455 memcpy(current, opts, sizeof(options_t));
456
457 log_debug("Options state set from parsed struct");
458 return ASCIICHAT_OK;
459}
460
462 // Check if initialized using lifecycle
463 if (!lifecycle_is_initialized(&g_options_lifecycle)) {
464 return;
465 }
466
467 /* Shutdown lifecycle to mark as no longer initialized */
468 lifecycle_shutdown(&g_options_lifecycle);
469
470 // Get current options pointer
471 options_t *current = atomic_ptr_load(&g_options);
472
473 // Clear the atomic pointer (prevent further reads)
474 atomic_ptr_store(&g_options, NULL);
475
476 // Free current struct
477 SAFE_FREE(current);
478
479 // Free all deferred structs
480 deferred_free_all();
481
482 // Cleanup schema (frees all dynamically allocated config strings)
484
485 // Destroy dynamically created mutexes
486 mutex_destroy(&g_options_write_mutex);
487 mutex_destroy(&g_deferred_free_mutex);
488
489 log_debug("Options state shutdown complete");
490}
491
495
496const options_t *options_get(void) {
497 // Lock-free read with acquire semantics
498 // Guarantees we see all writes made before the pointer was published
499 options_t *current = atomic_ptr_load(&g_options);
500
501 // If options not yet published or after destruction, return safe static default.
502 // This is a critical fallback that:
503 // 1. Allows early startup code (before options_state_init) to work safely
504 // 2. Allows atexit handlers and cleanup code to call GET_OPTION() safely
505 // 3. Never crashes due to NULL pointer access
506 // 4. Provides sensible defaults for all option fields
507 //
508 // The static default options are:
509 // - Allocated once at startup (static storage)
510 // - Never freed (outlives all dynamically allocated options)
511 // - Thread-safe to read (immutable const data)
512 // - Complete with all OPT_*_DEFAULT values matching options_t_new()
513 if (!current) {
514 return (const options_t *)&g_default_options;
515 }
516
517 return current;
518}
519
520static asciichat_error_t options_update(void (*updater)(options_t *, void *), void *context) {
521 if (!updater) {
522 return SET_ERRNO(ERROR_INVALID_PARAM, "updater function is NULL");
523 }
524
525 // The state pointer may remain valid in a forked child even though the
526 // lifecycle marker was reset. Recreate a standalone state for that case.
527 bool lock_writers = lifecycle_is_initialized(&g_options_lifecycle);
528
529 // 1. Load current options (acquire semantics)
530 options_t *old_opts = atomic_ptr_load(&g_options);
531 if (!old_opts) {
532 old_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
533 if (!old_opts) {
534 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate initial options struct");
535 }
536 *old_opts = options_t_new();
537 atomic_ptr_store(&g_options, old_opts);
538 }
539
540 // Serialize writers with mutex when the lifecycle owns an initialized one.
541 if (lock_writers) {
542 mutex_lock(&g_options_write_mutex);
543 }
544
545 // 2. Allocate new options struct
546 options_t *new_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
547 if (!new_opts) {
548 if (lock_writers) {
549 mutex_unlock(&g_options_write_mutex);
550 }
551 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate new options struct");
552 }
553
554 // 3. Copy current values to new struct
555 memcpy(new_opts, old_opts, sizeof(options_t));
556
557 // 4. Call user updater to modify the new struct
558 updater(new_opts, context);
559
560 // 5. Atomically swap global pointer (release semantics)
561 // This makes the new struct visible to all readers
562 atomic_ptr_store(&g_options, new_opts);
563
564 // 6. Add old struct to deferred free list
565 deferred_free_add(old_opts);
566
567 if (lock_writers) {
568 mutex_unlock(&g_options_write_mutex);
569 }
570
571 log_debug("Options updated via RCU (old=%p, new=%p)", (void *)old_opts, (void *)new_opts);
572 return ASCIICHAT_OK;
573}
574
575// ============================================================================
576// Generic Option Setters
577// ============================================================================
578
588typedef struct {
589 const char *field;
590 const char *option;
592
593static const field_option_map_t g_field_to_option[] = { // INT fields
594 {"width", "width"},
595 {"height", "height"},
596 {"max_clients", "max-clients"},
597 {"compression_level", "compression-level"},
598 {"reconnect_attempts", "reconnect"},
599 {"microphone_index", "microphone-index"},
600 {"speakers_index", "speakers-index"},
601 {"discovery_port", "discovery-service-port"},
602 {"port", "port"},
603 {"fps", "fps"},
604 {"color_mode", "color-mode"},
605 {"color_filter", "color-filter"},
606 {"render_mode", "render-mode"},
607 {"log_level", "log-level"},
608 {"palette_type", "palette"},
609 // DOUBLE fields
610 {"snapshot_delay", "snapshot-delay"},
611 {"microphone_sensitivity", "microphone-volume"},
612 {"speakers_volume", "speakers-volume"},
613 // STRING fields with registry entries
614 {"address", "address"},
615 {"log_file", "log-file"},
616 {"password", "password"},
617 {NULL, NULL}};
618
632static asciichat_error_t rcu_validate_field(const char *field_name, const options_t *new_opts) {
633 // Look up option name from field name
634 const char *opt_name = NULL;
635 for (int i = 0; g_field_to_option[i].field; i++) {
636 if (strcmp(g_field_to_option[i].field, field_name) == 0) {
637 opt_name = g_field_to_option[i].option;
638 break;
639 }
640 }
641 if (!opt_name)
642 return ASCIICHAT_OK; // No mapping → skip validation
643
644 const registry_entry_t *entry = registry_find_entry_by_name(opt_name);
645 if (!entry) {
646 return ASCIICHAT_OK; // Not in registry → skip
647 }
648
649 // Cross-field validate_fn (future-proofing)
650 if (entry->validate_fn) {
651 char *error_msg = NULL;
652 if (!entry->validate_fn(new_opts, &error_msg)) {
653 log_error("Option '%s' rejected by validator: %s", opt_name, error_msg ? error_msg : "unknown error");
654 SAFE_FREE(error_msg);
655 return SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' failed validation", opt_name);
656 }
657 SAFE_FREE(error_msg);
658 }
659
660 // Numeric range check. Float-valued callback options store their value as a
661 // float; reading those bytes as an int produces nondeterministic validation
662 // failures (and can read beyond the field on some layouts).
663 int min = entry->metadata.numeric_range.min;
664 int max = entry->metadata.numeric_range.max;
665 if (min != 0 || max != 0) {
666 if (entry->type == OPTION_TYPE_CALLBACK && entry->default_value_size == sizeof(float)) {
667 float val = *(const float *)((const char *)new_opts + entry->offset);
668 if (val < (float)min || val > (float)max) {
669 log_error("Option '%s' value %f out of range [%d, %d]", opt_name, val, min, max);
670 return SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' value %f out of range [%d, %d]", opt_name, val, min, max);
671 }
672 } else {
673 int val = *(const int *)((const char *)new_opts + entry->offset);
674 if (val < min || val > max) {
675 log_error("Option '%s' value %d out of range [%d, %d]", opt_name, val, min, max);
676 return SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' value %d out of range [%d, %d]", opt_name, val, min, max);
677 }
678 }
679 }
680
681 // Enum validation: check value against known enum values
682 size_t enum_count = 0;
683 if (entry->metadata.enum_values) {
684 for (size_t i = 0; entry->metadata.enum_values[i]; i++) {
685 enum_count++;
686 }
687 }
688
689 if (enum_count > 0 && entry->metadata.input_type == OPTION_INPUT_ENUM) {
690 int val = *(const int *)((const char *)new_opts + entry->offset);
691 if (entry->metadata.enum_integer_values) {
692 // Validate against actual enum integer values (handles non-sequential enums like color_mode with AUTO=-1)
693 bool found = false;
694 for (size_t i = 0; i < enum_count; i++) {
695 if (strcmp(field_name, "color_mode") == 0 && entry->metadata.enum_integer_values[i] == TERM_COLOR_AUTO) {
696 continue;
697 }
698 if (val == entry->metadata.enum_integer_values[i]) {
699 found = true;
700 break;
701 }
702 }
703 if (!found) {
704 log_error("Option '%s' value %d is not a valid enum value", opt_name, val);
705 return SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' value %d is not a valid enum", opt_name, val);
706 }
707 } else {
708 // Fallback: assume 0-based sequential enum values
709 if (val < 0 || (size_t)val >= enum_count) {
710 log_error("Option '%s' value %d is not a valid enum (0..%zu)", opt_name, val, enum_count - 1);
711 return SET_ERRNO(ERROR_INVALID_PARAM, "Option '%s' value %d is not a valid enum", opt_name, val);
712 }
713 }
714 }
715
716 return ASCIICHAT_OK;
717}
718
722typedef struct {
723 const char *field_name;
724 int value;
726
727static void int_field_updater(options_t *opts, void *context) {
728 int_field_ctx_t *ctx = (int_field_ctx_t *)context;
729 if (strcmp(ctx->field_name, "width") == 0)
730 opts->width = ctx->value;
731 else if (strcmp(ctx->field_name, "height") == 0)
732 opts->height = ctx->value;
733 else if (strcmp(ctx->field_name, "max_clients") == 0)
734 opts->max_clients = ctx->value;
735 else if (strcmp(ctx->field_name, "compression_level") == 0)
736 opts->compression_level = ctx->value;
737 else if (strcmp(ctx->field_name, "reconnect_attempts") == 0)
738 opts->reconnect_attempts = ctx->value;
739 else if (strcmp(ctx->field_name, "microphone_index") == 0)
740 opts->microphone_index = ctx->value;
741 else if (strcmp(ctx->field_name, "speakers_index") == 0)
742 opts->speakers_index = ctx->value;
743 else if (strcmp(ctx->field_name, "discovery_port") == 0)
744 opts->discovery_port = ctx->value;
745 else if (strcmp(ctx->field_name, "port") == 0)
746 opts->port = ctx->value;
747 else if (strcmp(ctx->field_name, "fps") == 0)
748 opts->fps = ctx->value;
749 else if (strcmp(ctx->field_name, "color_mode") == 0)
751 else if (strcmp(ctx->field_name, "color_filter") == 0)
752 opts->color_filter = (color_filter_t)ctx->value;
753 else if (strcmp(ctx->field_name, "render_mode") == 0)
754 opts->render_mode = (render_mode_t)ctx->value;
755 else if (strcmp(ctx->field_name, "log_level") == 0)
756 opts->log_level = (log_level_t)ctx->value;
757 else if (strcmp(ctx->field_name, "palette_type") == 0)
758 opts->palette_type = (palette_type_t)ctx->value;
759}
760
761asciichat_error_t options_set_int(const char *field_name, int value) {
762 if (!field_name) {
763 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
764 return ERROR_INVALID_PARAM;
765 }
766
767 if (strcmp(field_name, "width") != 0 && strcmp(field_name, "height") != 0 && strcmp(field_name, "max_clients") != 0 &&
768 strcmp(field_name, "compression_level") != 0 && strcmp(field_name, "reconnect_attempts") != 0 &&
769 strcmp(field_name, "microphone_index") != 0 && strcmp(field_name, "speakers_index") != 0 &&
770 strcmp(field_name, "discovery_port") != 0 && strcmp(field_name, "port") != 0 && strcmp(field_name, "fps") != 0 &&
771 strcmp(field_name, "color_mode") != 0 && strcmp(field_name, "color_filter") != 0 &&
772 strcmp(field_name, "render_mode") != 0 && strcmp(field_name, "log_level") != 0 &&
773 strcmp(field_name, "palette_type") != 0) {
774 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown integer field: %s", field_name);
775 return ERROR_INVALID_PARAM;
776 }
777
778 // Keep the state API's public invariants explicit even when the registry
779 // has not yet been populated by the command-line parser.
780 if (strcmp(field_name, "port") == 0 && (value < 1 || value > 65535)) {
781 return SET_ERRNO(ERROR_INVALID_PARAM, "Option 'port' value %d out of range [1, 65535]", value);
782 }
783 if (strcmp(field_name, "color_mode") == 0 && value == TERM_COLOR_AUTO) {
784 return SET_ERRNO(ERROR_INVALID_PARAM, "Option 'color_mode' value %d is not valid for the state API", value);
785 }
786
787 // Pre-validate against registry metadata
788 {
789 const options_t *cur = options_get();
790
791 // Skip RCU update if value is already set (avoids unnecessary alloc+deferred-free)
792 int cur_value = 0;
793 if (strcmp(field_name, "width") == 0)
794 cur_value = cur->width;
795 else if (strcmp(field_name, "height") == 0)
796 cur_value = cur->height;
797 else if (strcmp(field_name, "max_clients") == 0)
798 cur_value = cur->max_clients;
799 else if (strcmp(field_name, "compression_level") == 0)
800 cur_value = cur->compression_level;
801 else if (strcmp(field_name, "reconnect_attempts") == 0)
802 cur_value = cur->reconnect_attempts;
803 else if (strcmp(field_name, "microphone_index") == 0)
804 cur_value = cur->microphone_index;
805 else if (strcmp(field_name, "speakers_index") == 0)
806 cur_value = cur->speakers_index;
807 else if (strcmp(field_name, "discovery_port") == 0)
808 cur_value = cur->discovery_port;
809 else if (strcmp(field_name, "port") == 0)
810 cur_value = cur->port;
811 else if (strcmp(field_name, "fps") == 0)
812 cur_value = cur->fps;
813 else if (strcmp(field_name, "color_mode") == 0)
814 cur_value = (int)cur->color_mode;
815 else if (strcmp(field_name, "color_filter") == 0)
816 cur_value = (int)cur->color_filter;
817 else if (strcmp(field_name, "render_mode") == 0)
818 cur_value = (int)cur->render_mode;
819 else if (strcmp(field_name, "log_level") == 0)
820 cur_value = (int)cur->log_level;
821 else if (strcmp(field_name, "palette_type") == 0)
822 cur_value = (int)cur->palette_type;
823 if (cur_value == value)
824 return ASCIICHAT_OK;
825
826 options_t temp = *cur;
827 // Manually set the field in temp to validate
828 if (strcmp(field_name, "width") == 0)
829 temp.width = value;
830 else if (strcmp(field_name, "height") == 0)
831 temp.height = value;
832 else if (strcmp(field_name, "max_clients") == 0)
833 temp.max_clients = value;
834 else if (strcmp(field_name, "compression_level") == 0)
835 temp.compression_level = value;
836 else if (strcmp(field_name, "reconnect_attempts") == 0)
837 temp.reconnect_attempts = value;
838 else if (strcmp(field_name, "microphone_index") == 0)
839 temp.microphone_index = value;
840 else if (strcmp(field_name, "speakers_index") == 0)
841 temp.speakers_index = value;
842 else if (strcmp(field_name, "discovery_port") == 0)
843 temp.discovery_port = value;
844 else if (strcmp(field_name, "port") == 0)
845 temp.port = value;
846 else if (strcmp(field_name, "fps") == 0)
847 temp.fps = value;
848 else if (strcmp(field_name, "color_mode") == 0)
849 temp.color_mode = (terminal_color_mode_t)value;
850 else if (strcmp(field_name, "color_filter") == 0)
851 temp.color_filter = (color_filter_t)value;
852 else if (strcmp(field_name, "render_mode") == 0)
853 temp.render_mode = (render_mode_t)value;
854 else if (strcmp(field_name, "log_level") == 0)
855 temp.log_level = (log_level_t)value;
856 else if (strcmp(field_name, "palette_type") == 0)
857 temp.palette_type = (palette_type_t)value;
858
859 asciichat_error_t err = rcu_validate_field(field_name, &temp);
860 if (err != ASCIICHAT_OK)
861 return err;
862 }
863
864 int_field_ctx_t ctx = {.field_name = field_name, .value = value};
865 asciichat_error_t update_result = options_update(int_field_updater, &ctx);
866 return update_result;
867}
868
872typedef struct {
873 const char *field_name;
874 bool value;
876
877static void bool_field_updater(options_t *opts, void *context) {
878 bool_field_ctx_t *ctx = (bool_field_ctx_t *)context;
879 if (strcmp(ctx->field_name, "no_compress") == 0)
880 opts->no_compress = ctx->value;
881 else if (strcmp(ctx->field_name, "encode_audio") == 0)
882 opts->encode_audio = ctx->value;
883 else if (strcmp(ctx->field_name, "flip_x") == 0)
884 opts->flip_x = ctx->value;
885 else if (strcmp(ctx->field_name, "flip_y") == 0)
886 opts->flip_y = ctx->value;
887 else if (strcmp(ctx->field_name, "test_pattern") == 0)
888 opts->test_pattern = ctx->value;
889 else if (strcmp(ctx->field_name, "no_audio_mixer") == 0)
890 opts->no_audio_mixer = ctx->value;
891 else if (strcmp(ctx->field_name, "show_capabilities") == 0)
892 opts->show_capabilities = ctx->value;
893 else if (strcmp(ctx->field_name, "force_utf8") == 0)
894 opts->force_utf8 = ctx->value;
895 else if (strcmp(ctx->field_name, "audio_enabled") == 0)
896 opts->audio_enabled = ctx->value;
897 else if (strcmp(ctx->field_name, "audio_analysis_enabled") == 0)
898 opts->audio_analysis_enabled = ctx->value;
899 else if (strcmp(ctx->field_name, "audio_no_playback") == 0)
900 opts->audio_no_playback = ctx->value;
901 else if (strcmp(ctx->field_name, "stretch") == 0)
902 opts->stretch = ctx->value;
903 else if (strcmp(ctx->field_name, "snapshot_mode") == 0)
904 opts->snapshot_mode = ctx->value;
905 else if (strcmp(ctx->field_name, "strip_ansi") == 0)
906 opts->strip_ansi = ctx->value;
907 else if (strcmp(ctx->field_name, "quiet") == 0)
908 opts->quiet = ctx->value;
909 else if (strcmp(ctx->field_name, "encrypt_enabled") == 0)
910 opts->encrypt_enabled = ctx->value;
911 else if (strcmp(ctx->field_name, "no_encrypt") == 0)
912 opts->no_encrypt = ctx->value;
913 else if (strcmp(ctx->field_name, "discovery") == 0)
914 opts->discovery = ctx->value;
915 else if (strcmp(ctx->field_name, "discovery_expose_ip") == 0)
916 opts->discovery_expose_ip = ctx->value;
917 else if (strcmp(ctx->field_name, "discovery_insecure") == 0)
918 opts->discovery_insecure = ctx->value;
919 else if (strcmp(ctx->field_name, "webrtc") == 0)
920 opts->webrtc = ctx->value;
921 else if (strcmp(ctx->field_name, "lan_discovery") == 0)
922 opts->lan_discovery = ctx->value;
923 else if (strcmp(ctx->field_name, "no_mdns_advertise") == 0)
924 opts->no_mdns_advertise = ctx->value;
925 else if (strcmp(ctx->field_name, "prefer_webrtc") == 0)
926 opts->prefer_webrtc = ctx->value;
927 else if (strcmp(ctx->field_name, "no_webrtc") == 0)
928 opts->no_webrtc = ctx->value;
929 else if (strcmp(ctx->field_name, "webrtc_skip_stun") == 0)
930 opts->webrtc_skip_stun = ctx->value;
931 else if (strcmp(ctx->field_name, "webrtc_relay_only") == 0)
932 opts->webrtc_relay_only = ctx->value;
933 else if (strcmp(ctx->field_name, "webrtc_disable_turn") == 0)
934 opts->webrtc_disable_turn = ctx->value;
935 else if (strcmp(ctx->field_name, "enable_upnp") == 0)
936 opts->enable_upnp = ctx->value;
937 else if (strcmp(ctx->field_name, "require_server_identity") == 0)
938 opts->require_server_identity = ctx->value;
939 else if (strcmp(ctx->field_name, "require_client_identity") == 0)
940 opts->require_client_identity = ctx->value;
941 else if (strcmp(ctx->field_name, "require_server_verify") == 0)
942 opts->require_server_verify = ctx->value;
943 else if (strcmp(ctx->field_name, "require_client_verify") == 0)
944 opts->require_client_verify = ctx->value;
945 else if (strcmp(ctx->field_name, "palette_custom_set") == 0)
946 opts->palette_custom_set = ctx->value;
947 else if (strcmp(ctx->field_name, "media_loop") == 0)
948 opts->media_loop = ctx->value;
949 else if (strcmp(ctx->field_name, "media_from_stdin") == 0)
950 opts->media_from_stdin = ctx->value;
951 else if (strcmp(ctx->field_name, "auto_width") == 0)
952 opts->auto_width = ctx->value;
953 else if (strcmp(ctx->field_name, "auto_height") == 0)
954 opts->auto_height = ctx->value;
955 else if (strcmp(ctx->field_name, "splash_screen") == 0)
956 opts->splash_screen = ctx->value;
957 else if (strcmp(ctx->field_name, "status_screen") == 0)
958 opts->status_screen = ctx->value;
959 else if (strcmp(ctx->field_name, "matrix_rain") == 0)
960 opts->matrix_rain = ctx->value;
961 else if (strcmp(ctx->field_name, "waveform") == 0)
962 opts->waveform = ctx->value;
963 else if (strcmp(ctx->field_name, "fft") == 0)
964 opts->fft = ctx->value;
965 else if (strcmp(ctx->field_name, "fps_counter") == 0)
966 opts->fps_counter = ctx->value;
967}
968
969asciichat_error_t options_set_bool(const char *field_name, bool value) {
970 if (!field_name) {
971 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
972 return ERROR_INVALID_PARAM;
973 }
974
975 // Validate field exists
976 if (strcmp(field_name, "no_compress") != 0 && strcmp(field_name, "encode_audio") != 0 &&
977 strcmp(field_name, "flip_x") != 0 && strcmp(field_name, "flip_y") != 0 &&
978 strcmp(field_name, "test_pattern") != 0 && strcmp(field_name, "no_audio_mixer") != 0 &&
979 strcmp(field_name, "show_capabilities") != 0 && strcmp(field_name, "force_utf8") != 0 &&
980 strcmp(field_name, "audio_enabled") != 0 && strcmp(field_name, "audio_analysis_enabled") != 0 &&
981 strcmp(field_name, "audio_no_playback") != 0 && strcmp(field_name, "stretch") != 0 &&
982 strcmp(field_name, "snapshot_mode") != 0 && strcmp(field_name, "strip_ansi") != 0 &&
983 strcmp(field_name, "quiet") != 0 && strcmp(field_name, "encrypt_enabled") != 0 &&
984 strcmp(field_name, "no_encrypt") != 0 && strcmp(field_name, "discovery") != 0 &&
985 strcmp(field_name, "discovery_expose_ip") != 0 && strcmp(field_name, "discovery_insecure") != 0 &&
986 strcmp(field_name, "webrtc") != 0 && strcmp(field_name, "lan_discovery") != 0 &&
987 strcmp(field_name, "no_mdns_advertise") != 0 && strcmp(field_name, "prefer_webrtc") != 0 &&
988 strcmp(field_name, "no_webrtc") != 0 && strcmp(field_name, "webrtc_skip_stun") != 0 &&
989 strcmp(field_name, "webrtc_relay_only") != 0 && strcmp(field_name, "webrtc_disable_turn") != 0 &&
990 strcmp(field_name, "enable_upnp") != 0 && strcmp(field_name, "require_server_identity") != 0 &&
991 strcmp(field_name, "require_client_identity") != 0 && strcmp(field_name, "require_server_verify") != 0 &&
992 strcmp(field_name, "require_client_verify") != 0 && strcmp(field_name, "palette_custom_set") != 0 &&
993 strcmp(field_name, "media_loop") != 0 && strcmp(field_name, "media_from_stdin") != 0 &&
994 strcmp(field_name, "auto_width") != 0 && strcmp(field_name, "auto_height") != 0 &&
995 strcmp(field_name, "splash_screen") != 0 && strcmp(field_name, "status_screen") != 0 &&
996 strcmp(field_name, "matrix_rain") != 0 && strcmp(field_name, "fps_counter") != 0 &&
997 strcmp(field_name, "waveform") != 0 && strcmp(field_name, "fft") != 0) {
998 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown boolean field: %s", field_name);
999 return ERROR_INVALID_PARAM;
1000 }
1001
1002 // Pre-validate against registry metadata (for cross-field validators)
1003 {
1004 const options_t *cur = options_get();
1005
1006 // Skip RCU update if value is already set
1007 bool cur_value = false;
1008 if (strcmp(field_name, "no_compress") == 0)
1009 cur_value = cur->no_compress;
1010 else if (strcmp(field_name, "encode_audio") == 0)
1011 cur_value = cur->encode_audio;
1012 else if (strcmp(field_name, "flip_x") == 0)
1013 cur_value = cur->flip_x;
1014 else if (strcmp(field_name, "flip_y") == 0)
1015 cur_value = cur->flip_y;
1016 else if (strcmp(field_name, "test_pattern") == 0)
1017 cur_value = cur->test_pattern;
1018 else if (strcmp(field_name, "matrix_rain") == 0)
1019 cur_value = cur->matrix_rain;
1020 else if (strcmp(field_name, "waveform") == 0)
1021 cur_value = cur->waveform;
1022 else if (strcmp(field_name, "fft") == 0)
1023 cur_value = cur->fft;
1024 else if (strcmp(field_name, "fps_counter") == 0)
1025 cur_value = cur->fps_counter;
1026 else if (strcmp(field_name, "splash_screen") == 0)
1027 cur_value = cur->splash_screen;
1028 else if (strcmp(field_name, "status_screen") == 0)
1029 cur_value = cur->status_screen;
1030 // For less common fields, always update (safe default)
1031 else
1032 cur_value = !value;
1033 if (cur_value == value)
1034 return ASCIICHAT_OK;
1035
1036 options_t temp = *cur;
1037 // Manually set the field in temp to validate
1038 if (strcmp(field_name, "no_compress") == 0)
1039 temp.no_compress = value;
1040 else if (strcmp(field_name, "encode_audio") == 0)
1041 temp.encode_audio = value;
1042 else if (strcmp(field_name, "flip_x") == 0)
1043 temp.flip_x = value;
1044 else if (strcmp(field_name, "flip_y") == 0)
1045 temp.flip_y = value;
1046 else if (strcmp(field_name, "test_pattern") == 0)
1047 temp.test_pattern = value;
1048 else if (strcmp(field_name, "no_audio_mixer") == 0)
1049 temp.no_audio_mixer = value;
1050 else if (strcmp(field_name, "show_capabilities") == 0)
1051 temp.show_capabilities = value;
1052 else if (strcmp(field_name, "force_utf8") == 0)
1053 temp.force_utf8 = value;
1054 else if (strcmp(field_name, "audio_enabled") == 0)
1055 temp.audio_enabled = value;
1056 else if (strcmp(field_name, "audio_analysis_enabled") == 0)
1057 temp.audio_analysis_enabled = value;
1058 else if (strcmp(field_name, "audio_no_playback") == 0)
1059 temp.audio_no_playback = value;
1060 else if (strcmp(field_name, "stretch") == 0)
1061 temp.stretch = value;
1062 else if (strcmp(field_name, "snapshot_mode") == 0)
1063 temp.snapshot_mode = value;
1064 else if (strcmp(field_name, "strip_ansi") == 0)
1065 temp.strip_ansi = value;
1066 else if (strcmp(field_name, "quiet") == 0)
1067 temp.quiet = value;
1068 else if (strcmp(field_name, "encrypt_enabled") == 0)
1069 temp.encrypt_enabled = value;
1070 else if (strcmp(field_name, "no_encrypt") == 0)
1071 temp.no_encrypt = value;
1072 else if (strcmp(field_name, "discovery") == 0)
1073 temp.discovery = value;
1074 else if (strcmp(field_name, "discovery_expose_ip") == 0)
1075 temp.discovery_expose_ip = value;
1076 else if (strcmp(field_name, "discovery_insecure") == 0)
1077 temp.discovery_insecure = value;
1078 else if (strcmp(field_name, "webrtc") == 0)
1079 temp.webrtc = value;
1080 else if (strcmp(field_name, "lan_discovery") == 0)
1081 temp.lan_discovery = value;
1082 else if (strcmp(field_name, "no_mdns_advertise") == 0)
1083 temp.no_mdns_advertise = value;
1084 else if (strcmp(field_name, "prefer_webrtc") == 0)
1085 temp.prefer_webrtc = value;
1086 else if (strcmp(field_name, "no_webrtc") == 0)
1087 temp.no_webrtc = value;
1088 else if (strcmp(field_name, "webrtc_skip_stun") == 0)
1089 temp.webrtc_skip_stun = value;
1090 else if (strcmp(field_name, "webrtc_relay_only") == 0)
1091 temp.webrtc_relay_only = value;
1092 else if (strcmp(field_name, "webrtc_disable_turn") == 0)
1093 temp.webrtc_disable_turn = value;
1094 else if (strcmp(field_name, "enable_upnp") == 0)
1095 temp.enable_upnp = value;
1096 else if (strcmp(field_name, "require_server_identity") == 0)
1097 temp.require_server_identity = value;
1098 else if (strcmp(field_name, "require_client_identity") == 0)
1099 temp.require_client_identity = value;
1100 else if (strcmp(field_name, "require_server_verify") == 0)
1101 temp.require_server_verify = value;
1102 else if (strcmp(field_name, "require_client_verify") == 0)
1103 temp.require_client_verify = value;
1104 else if (strcmp(field_name, "palette_custom_set") == 0)
1105 temp.palette_custom_set = value;
1106 else if (strcmp(field_name, "media_loop") == 0)
1107 temp.media_loop = value;
1108 else if (strcmp(field_name, "media_from_stdin") == 0)
1109 temp.media_from_stdin = value;
1110 else if (strcmp(field_name, "auto_width") == 0)
1111 temp.auto_width = value;
1112 else if (strcmp(field_name, "auto_height") == 0)
1113 temp.auto_height = value;
1114 else if (strcmp(field_name, "splash_screen") == 0)
1115 temp.splash_screen = value;
1116 else if (strcmp(field_name, "status_screen") == 0)
1117 temp.status_screen = value;
1118 else if (strcmp(field_name, "matrix_rain") == 0)
1119 temp.matrix_rain = value;
1120 else if (strcmp(field_name, "waveform") == 0)
1121 temp.waveform = value;
1122 else if (strcmp(field_name, "fft") == 0)
1123 temp.fft = value;
1124 else if (strcmp(field_name, "fps_counter") == 0)
1125 temp.fps_counter = value;
1126
1127 asciichat_error_t err = rcu_validate_field(field_name, &temp);
1128 if (err != ASCIICHAT_OK)
1129 return err;
1130 }
1131
1132 bool_field_ctx_t ctx = {.field_name = field_name, .value = value};
1133 return options_update(bool_field_updater, &ctx);
1134}
1135
1139typedef struct {
1140 const char *field_name;
1141 const char *value;
1143
1144static void string_field_updater(options_t *opts, void *context) {
1145 string_field_ctx_t *ctx = (string_field_ctx_t *)context;
1146 if (strcmp(ctx->field_name, "address") == 0)
1147 SAFE_STRNCPY(opts->address, ctx->value, sizeof(opts->address));
1148 else if (strcmp(ctx->field_name, "address6") == 0)
1149 SAFE_STRNCPY(opts->address6, ctx->value, sizeof(opts->address6));
1150 else if (strcmp(ctx->field_name, "encrypt_key") == 0)
1151 SAFE_STRNCPY(opts->encrypt_key, ctx->value, sizeof(opts->encrypt_key));
1152 else if (strcmp(ctx->field_name, "password") == 0)
1153 SAFE_STRNCPY(opts->password, ctx->value, sizeof(opts->password));
1154 else if (strcmp(ctx->field_name, "encrypt_keyfile") == 0)
1155 SAFE_STRNCPY(opts->encrypt_keyfile, ctx->value, sizeof(opts->encrypt_keyfile));
1156 else if (strcmp(ctx->field_name, "server_key") == 0)
1157 SAFE_STRNCPY(opts->server_key, ctx->value, sizeof(opts->server_key));
1158 else if (strcmp(ctx->field_name, "client_keys") == 0)
1159 SAFE_STRNCPY(opts->client_keys, ctx->value, sizeof(opts->client_keys));
1160 else if (strcmp(ctx->field_name, "discovery_server") == 0)
1161 SAFE_STRNCPY(opts->discovery_server, ctx->value, sizeof(opts->discovery_server));
1162 else if (strcmp(ctx->field_name, "discovery_service_key") == 0)
1164 else if (strcmp(ctx->field_name, "discovery_database_path") == 0)
1166 else if (strcmp(ctx->field_name, "log_file") == 0)
1167 SAFE_STRNCPY(opts->log_file, ctx->value, sizeof(opts->log_file));
1168 else if (strcmp(ctx->field_name, "media_file") == 0)
1169 SAFE_STRNCPY(opts->media_file, ctx->value, sizeof(opts->media_file));
1170 else if (strcmp(ctx->field_name, "palette_custom") == 0)
1171 SAFE_STRNCPY(opts->palette_custom, ctx->value, sizeof(opts->palette_custom));
1172 else if (strcmp(ctx->field_name, "stun_servers") == 0)
1173 SAFE_STRNCPY(opts->stun_servers, ctx->value, sizeof(opts->stun_servers));
1174 else if (strcmp(ctx->field_name, "turn_servers") == 0)
1175 SAFE_STRNCPY(opts->turn_servers, ctx->value, sizeof(opts->turn_servers));
1176 else if (strcmp(ctx->field_name, "turn_username") == 0)
1177 SAFE_STRNCPY(opts->turn_username, ctx->value, sizeof(opts->turn_username));
1178 else if (strcmp(ctx->field_name, "turn_credential") == 0)
1179 SAFE_STRNCPY(opts->turn_credential, ctx->value, sizeof(opts->turn_credential));
1180 else if (strcmp(ctx->field_name, "turn_secret") == 0)
1181 SAFE_STRNCPY(opts->turn_secret, ctx->value, sizeof(opts->turn_secret));
1182 else if (strcmp(ctx->field_name, "session_string") == 0)
1183 SAFE_STRNCPY(opts->session_string, ctx->value, sizeof(opts->session_string));
1184}
1185
1186asciichat_error_t options_set_string(const char *field_name, const char *value) {
1187 if (!field_name) {
1188 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
1189 return ERROR_INVALID_PARAM;
1190 }
1191
1192 if (!value) {
1193 SET_ERRNO(ERROR_INVALID_PARAM, "value is NULL");
1194 return ERROR_INVALID_PARAM;
1195 }
1196
1197 // Validate field exists
1198 if (strcmp(field_name, "address") != 0 && strcmp(field_name, "address6") != 0 &&
1199 strcmp(field_name, "encrypt_key") != 0 && strcmp(field_name, "password") != 0 &&
1200 strcmp(field_name, "encrypt_keyfile") != 0 && strcmp(field_name, "server_key") != 0 &&
1201 strcmp(field_name, "client_keys") != 0 && strcmp(field_name, "discovery_server") != 0 &&
1202 strcmp(field_name, "discovery_service_key") != 0 && strcmp(field_name, "discovery_database_path") != 0 &&
1203 strcmp(field_name, "log_file") != 0 && strcmp(field_name, "media_file") != 0 &&
1204 strcmp(field_name, "palette_custom") != 0 && strcmp(field_name, "stun_servers") != 0 &&
1205 strcmp(field_name, "turn_servers") != 0 && strcmp(field_name, "turn_username") != 0 &&
1206 strcmp(field_name, "turn_credential") != 0 && strcmp(field_name, "turn_secret") != 0 &&
1207 strcmp(field_name, "session_string") != 0) {
1208 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown string field: %s", field_name);
1209 return ERROR_INVALID_PARAM;
1210 }
1211
1212 // Pre-validate against registry metadata (for cross-field validators)
1213 {
1214 const options_t *cur = options_get();
1215 options_t temp = *cur;
1216 // Manually set the field in temp to validate
1217 if (strcmp(field_name, "address") == 0)
1218 SAFE_STRNCPY(temp.address, value, sizeof(temp.address));
1219 else if (strcmp(field_name, "address6") == 0)
1220 SAFE_STRNCPY(temp.address6, value, sizeof(temp.address6));
1221 else if (strcmp(field_name, "encrypt_key") == 0)
1222 SAFE_STRNCPY(temp.encrypt_key, value, sizeof(temp.encrypt_key));
1223 else if (strcmp(field_name, "password") == 0)
1224 SAFE_STRNCPY(temp.password, value, sizeof(temp.password));
1225 else if (strcmp(field_name, "encrypt_keyfile") == 0)
1226 SAFE_STRNCPY(temp.encrypt_keyfile, value, sizeof(temp.encrypt_keyfile));
1227 else if (strcmp(field_name, "server_key") == 0)
1228 SAFE_STRNCPY(temp.server_key, value, sizeof(temp.server_key));
1229 else if (strcmp(field_name, "client_keys") == 0)
1230 SAFE_STRNCPY(temp.client_keys, value, sizeof(temp.client_keys));
1231 else if (strcmp(field_name, "discovery_server") == 0)
1232 SAFE_STRNCPY(temp.discovery_server, value, sizeof(temp.discovery_server));
1233 else if (strcmp(field_name, "discovery_service_key") == 0)
1234 SAFE_STRNCPY(temp.discovery_service_key, value, sizeof(temp.discovery_service_key));
1235 else if (strcmp(field_name, "discovery_database_path") == 0)
1237 else if (strcmp(field_name, "log_file") == 0)
1238 SAFE_STRNCPY(temp.log_file, value, sizeof(temp.log_file));
1239 else if (strcmp(field_name, "media_file") == 0)
1240 SAFE_STRNCPY(temp.media_file, value, sizeof(temp.media_file));
1241 else if (strcmp(field_name, "palette_custom") == 0)
1242 SAFE_STRNCPY(temp.palette_custom, value, sizeof(temp.palette_custom));
1243 else if (strcmp(field_name, "stun_servers") == 0)
1244 SAFE_STRNCPY(temp.stun_servers, value, sizeof(temp.stun_servers));
1245 else if (strcmp(field_name, "turn_servers") == 0)
1246 SAFE_STRNCPY(temp.turn_servers, value, sizeof(temp.turn_servers));
1247 else if (strcmp(field_name, "turn_username") == 0)
1248 SAFE_STRNCPY(temp.turn_username, value, sizeof(temp.turn_username));
1249 else if (strcmp(field_name, "turn_credential") == 0)
1250 SAFE_STRNCPY(temp.turn_credential, value, sizeof(temp.turn_credential));
1251 else if (strcmp(field_name, "turn_secret") == 0)
1252 SAFE_STRNCPY(temp.turn_secret, value, sizeof(temp.turn_secret));
1253 else if (strcmp(field_name, "session_string") == 0)
1254 SAFE_STRNCPY(temp.session_string, value, sizeof(temp.session_string));
1255
1256 asciichat_error_t err = rcu_validate_field(field_name, &temp);
1257 if (err != ASCIICHAT_OK)
1258 return err;
1259 }
1260
1261 string_field_ctx_t ctx = {.field_name = field_name, .value = value};
1262 return options_update(string_field_updater, &ctx);
1263}
1264
1268typedef struct {
1269 const char *field_name;
1270 double value;
1272
1273static void double_field_updater(options_t *opts, void *context) {
1274 double_field_ctx_t *ctx = (double_field_ctx_t *)context;
1275 if (strcmp(ctx->field_name, "snapshot_delay") == 0)
1276 opts->snapshot_delay = ctx->value;
1277 else if (strcmp(ctx->field_name, "microphone_sensitivity") == 0)
1278 opts->microphone_sensitivity = ctx->value;
1279 else if (strcmp(ctx->field_name, "speakers_volume") == 0)
1280 opts->speakers_volume = ctx->value;
1281}
1282
1283asciichat_error_t options_set_double(const char *field_name, double value) {
1284 if (!field_name) {
1285 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
1286 return ERROR_INVALID_PARAM;
1287 }
1288
1289 // Normalize option names to internal field names
1290 const char *internal_name = field_name;
1291 if (strcmp(field_name, "microphone-volume") == 0 || strcmp(field_name, "ivolume") == 0) {
1292 internal_name = "microphone_sensitivity";
1293 } else if (strcmp(field_name, "speakers-volume") == 0 || strcmp(field_name, "volume") == 0) {
1294 internal_name = "speakers_volume";
1295 }
1296
1297 if (strcmp(internal_name, "snapshot_delay") != 0 && strcmp(internal_name, "microphone_sensitivity") != 0 &&
1298 strcmp(internal_name, "speakers_volume") != 0) {
1299 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown double field: %s", field_name);
1300 return ERROR_INVALID_PARAM;
1301 }
1302
1303 // Pre-validate against registry metadata (if available)
1304 {
1305 const options_t *cur = options_get();
1306 options_t temp = *cur;
1307 // Manually set the field in temp to validate
1308 if (strcmp(internal_name, "snapshot_delay") == 0)
1309 temp.snapshot_delay = value;
1310 else if (strcmp(internal_name, "microphone_sensitivity") == 0)
1311 temp.microphone_sensitivity = value;
1312 else if (strcmp(internal_name, "speakers_volume") == 0)
1313 temp.speakers_volume = value;
1314
1315 asciichat_error_t err = rcu_validate_field(internal_name, &temp);
1316 if (err != ASCIICHAT_OK)
1317 return err;
1318 }
1319
1320 double_field_ctx_t ctx = {.field_name = internal_name, .value = value};
1321 return options_update(double_field_updater, &ctx);
1322}
Platform abstraction layer umbrella header providing unified cross-platform API.
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
void atomic_ptr_store(atomic_ptr_t *a, void *value)
Atomically store a pointer.
Definition atomic.c:280
void * atomic_ptr_load(atomic_ptr_t *a)
Atomically load a pointer.
Definition atomic.c:272
void atomic_store_int(atomic_t *a, int value)
Atomically store an int value.
Definition atomic.c:202
⚛️ Atomic operations abstraction layer with debug tracking
Options builder API for flexible command-line option configuration.
@ OPTION_INPUT_ENUM
Choose from fixed set of enum values.
Definition builder.h:178
@ OPTION_TYPE_CALLBACK
Custom parser function.
Definition builder.h:166
⚙️ Common definitions, error codes, macros, and types shared throughout the application
const registry_entry_t * registry_find_entry_by_name(const char *long_name)
Get a registry entry by long name.
Definition core.c:127
void registry_init_size(void)
Initialize registry size and metadata.
Definition core.c:108
Internal helper functions and data for registry implementation.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
log_level_t
Logging levels enumeration.
Definition types.h:29
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define OPT_STRIP_ANSI_DEFAULT
Default strip ANSI escape sequences flag.
#define OPT_WEBRTC_SKIP_STUN_DEFAULT
Default WebRTC skip STUN flag (false = use STUN)
#define OPT_ACDS_EXPOSE_IP_DEFAULT
Default ACDS expose IP flag (false = private by default)
#define OPT_COLOR_DEFAULT
Default color setting (COLOR_SETTING_AUTO = smart detection)
#define OPT_NO_AUDIO_MIXER_DEFAULT
Default no audio mixer flag (false = enable mixer)
#define OPT_FLIP_Y_DEFAULT
Default vertical flip state (false = no vertical flip)
#define OPT_SHOW_CAPABILITIES_DEFAULT
Default show terminal capabilities flag.
#define OPT_NO_ENCRYPT_DEFAULT
Default no encrypt flag (false = allow encryption)
#define OPT_QUIET_DEFAULT
Default quiet mode flag (false = logging enabled)
asciichat_error_t options_set_int(const char *field_name, int value)
Check if an option was explicitly set via command-line.
Definition rcu.c:761
#define OPT_MICROPHONE_INDEX_DEFAULT
Default microphone device index (-1 means system default)
struct options_state options_t
Consolidated options structure.
#define OPT_TURN_SERVERS_DEFAULT
Default TURN server URLs (comma-separated)
#define OPT_ENCODE_AUDIO_DEFAULT
Default audio encoding state (true = Opus encoding enabled)
#define OPT_COLOR_MODE_DEFAULT
Default color mode (auto-detect terminal capabilities)
#define OPT_PALETTE_TYPE_DEFAULT
Default palette type (standard ASCII art)
#define OPT_RECONNECT_ATTEMPTS_DEFAULT
Default reconnect attempts (-1 means auto/infinite)
#define OPT_SPEAKERS_VOLUME_DEFAULT
Default speakers volume (1.0 = normal volume)
#define OPT_NO_AUTH_DEFAULT
Default no auth flag (false = allow authentication)
#define OPT_TEST_PATTERN_DEFAULT
Default test pattern mode (false = use actual webcam)
#define OPT_WEBRTC_ICE_TIMEOUT_MS_DEFAULT
Default WebRTC ICE gathering timeout in milliseconds (10 seconds)
#define OPT_COMPRESSION_LEVEL_DEFAULT
Default compression level (1-9)
#define OPT_ENCRYPT_ENABLED_DEFAULT
Default encrypt enabled flag (true = encryption required)
#define OPT_REQUIRE_SERVER_IDENTITY_DEFAULT
Default require-server-identity setting for ACDS.
#define OPT_ENABLE_KEEPAWAKE_DEFAULT
Default keep-awake (prevent system sleep) flag (false = normal system sleep)
#define OPT_RENDER_MODE_DEFAULT
Default render mode (foreground characters only)
#define OPT_ACDS_PORT_INT_DEFAULT
Default ACDS discovery service port (integer)
#define OPT_AUDIO_ENABLED_DEFAULT
Default audio enabled flag (true = audio enabled by default)
#define OPT_MATRIX_RAIN_DEFAULT
Default Matrix rain effect flag (false = disabled)
#define OPT_FORCE_UTF8_DEFAULT
Default force UTF-8 support setting (auto-detect)
#define OPT_STRING_EMPTY_DEFAULT
Default empty string for string option fields that have no configured value.
#define OPT_STUN_SERVERS_DEFAULT
Default STUN server URLs (comma-separated)
#define OPT_GREP_PATTERN_DEFAULT
Default grep pattern (empty = no filtering)
#define OPT_HELP_DEFAULT
Default help flag (false = don't show help)
#define OPT_ADDRESS6_DEFAULT
Default IPv6 server address.
#define OPT_WEBRTC_RECONNECT_ATTEMPTS_DEFAULT
Default WebRTC reconnection attempts (3 = try initial + 3 retries)
#define OPT_SPLASH_DEFAULT
Default splash screen flag (true = show splash, false = hide splash)
#define OPT_MEDIA_FROM_STDIN_DEFAULT
Default media from stdin flag (false = not reading from stdin)
#define OPT_AUDIO_ANALYSIS_ENABLED_DEFAULT
Default audio analysis enabled flag.
asciichat_error_t options_set_double(const char *field_name, double value)
Definition rcu.c:1283
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
#define OPT_VERSION_DEFAULT
Default version flag (false = don't show version)
#define OPT_ACDS_DEFAULT
Default ACDS registration flag (false = disabled)
#define OPT_NO_COMPRESS_DEFAULT
Default no compression flag (false = enable compression)
#define OPT_AUTO_HEIGHT_DEFAULT
Default auto-detect height flag (true = auto-detect from terminal)
#define OPT_LOG_TEMPLATE_DEFAULT
Default log template string (selected based on build mode)
#define OPT_REQUIRE_CLIENT_VERIFY_DEFAULT
Default require client verify flag (false = not required)
#define OPT_AUDIO_NO_PLAYBACK_DEFAULT
Default audio playback flag (false = enable playback)
#define OPT_PAUSE_DEFAULT
Default pause media flag (false = play immediately)
#define OPT_REQUIRE_SERVER_VERIFY_DEFAULT
Default require server verify flag (false = not required)
#define OPT_NO_MDNS_ADVERTISE_DEFAULT
Default no mDNS advertise flag (false = advertise enabled)
#define OPT_AUDIO_SOURCE_DEFAULT
Visualize all available sources by default.
#define OPT_STATUS_SCREEN_EXPLICITLY_SET_DEFAULT
Default status screen explicitly set flag (false = use default status screen setting)
asciichat_error_t options_set_string(const char *field_name, const char *value)
Definition rcu.c:1186
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:969
#define OPT_COLOR_FILTER_DEFAULT
Default color filter (none - no filtering)
#define OPT_WEBCAM_INDEX_DEFAULT
Default webcam device index.
#define OPT_HEIGHT_DEFAULT
Default terminal height in characters.
#define OPT_SNAPSHOT_MODE_DEFAULT
Default snapshot mode flag (false = continuous)
#define OPT_COLOR_SCHEME_NAME_DEFAULT
Default color scheme name (pastel)
#define OPT_MEDIA_LOOP_DEFAULT
Default loop media flag (false = play once)
#define OPT_NO_CHECK_UPDATE_DEFAULT
Default no-check-update flag (false = check for updates enabled)
#define OPT_STRETCH_DEFAULT
Default allow aspect ratio distortion flag.
#define OPT_STATUS_SCREEN_DEFAULT
Default status screen flag (true = show status, false = hide status)
#define OPT_LAN_DISCOVERY_DEFAULT
Default LAN discovery flag (false = discovery disabled)
#define OPT_AUTO_WIDTH_DEFAULT
Default auto-detect width flag (true = auto-detect from terminal)
#define OPT_JSON_DEFAULT
Default JSON logging flag (false = text output)
#define OPT_ACDS_INSECURE_DEFAULT
Default ACDS insecure mode flag (false = verify server)
#define OPT_FPS_DEFAULT
Default FPS (frames per second)
#define OPT_WEBRTC_DEFAULT
Default WebRTC mode flag (true = P2P WebRTC, false = direct TCP)
#define OPT_REQUIRE_CLIENT_IDENTITY_DEFAULT
Default require-client-identity setting for ACDS.
#define OPT_DISABLE_KEEPAWAKE_DEFAULT
Default disable keep-awake flag (false = allow system sleep prevention)
#define OPT_ENABLE_UPNP_DEFAULT
Default enable UPnP flag (false = UPnP disabled)
#define OPT_TURN_CREDENTIAL_DEFAULT
Default TURN credential (empty = use ACDS credentials)
#define OPT_WEBRTC_SKIP_HOST_DEFAULT
Default WebRTC skip host candidates flag (false = use host candidates)
#define OPT_VERBOSE_LEVEL_DEFAULT
Default verbose level (0 = not verbose)
#define OPT_LOG_FORMAT_CONSOLE_DEFAULT
Default log format console only flag (false = use default format everywhere)
#define OPT_SNAPSHOT_DELAY_DEFAULT
Default snapshot delay in seconds.
#define OPT_LOG_LEVEL_DEFAULT
Default log level (LOG_INFO)
#define OPT_MICROPHONE_SENSITIVITY_DEFAULT
Default microphone sensitivity (1.0 = normal volume)
#define OPT_FLIP_X_DEFAULT
Default horizontal flip state (true = horizontally flipped) macOS webcams default to flipped (mirrore...
#define OPT_PORT_INT_DEFAULT
Default TCP port for client/server communication (integer)
#define OPT_SPLASH_SCREEN_EXPLICITLY_SET_DEFAULT
Default splash screen explicitly set flag (false = use default splash setting)
#define OPT_MAX_CLIENTS_DEFAULT
Default maximum concurrent clients (server only)
#define OPT_PREFER_WEBRTC_DEFAULT
Default prefer WebRTC flag (false = try direct TCP first)
options_t options_t_new(void)
Initialize options by parsing command-line arguments.
#define OPT_WEBRTC_DISABLE_TURN_DEFAULT
Default WebRTC disable TURN flag (false = use TURN)
#define OPT_NO_WEBRTC_DEFAULT
Default no WebRTC flag (false = WebRTC enabled)
#define OPT_ADDRESS_DEFAULT
Default server address for client connections.
#define OPT_AUDIO_CAPTURE_SOURCE_DEFAULT
Preserve automatic local audio capture selection by default.
#define OPT_PALETTE_CUSTOM_SET_DEFAULT
Default custom palette set flag (false = not set)
#define OPT_TURN_USERNAME_DEFAULT
Default TURN username (empty = use ACDS credentials)
#define OPT_SPEAKERS_INDEX_DEFAULT
Default speakers device index (-1 means system default)
#define OPT_WEBSOCKET_PORT_SERVER_DEFAULT
Default WebSocket port for server mode (integer)
#define OPT_MEDIA_SEEK_TIMESTAMP_DEFAULT
Default media seek timestamp (start from beginning)
#define OPT_WIDTH_DEFAULT
Default terminal width in characters.
@ MODE_INVALID
Invalid mode.
palette_type_t
Built-in palette type enumeration.
Definition palette.h:84
pid_t platform_get_pid(void)
Get the current process ID.
Definition wasm/system.c:61
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
Platform initialization and static synchronization helpers.
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
Lock-free module lifecycle state machine using pure stdatomic.
@ LIFECYCLE_UNINITIALIZED
Not yet initialized (zero = default)
Definition lifecycle.h:41
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
📝 Logging API with multiple log levels and terminal output control
Shared structures and macros for registry implementation.
Cross-platform process types and execution utilities.
void options_state_destroy(void)
Shutdown RCU options system.
Definition rcu.c:461
asciichat_error_t options_state_init(void)
Initialize RCU options system.
Definition rcu.c:370
void options_cleanup_schema(void)
Clean up schema resources.
Definition rcu.c:492
#define MAX_DEFERRED_FREES
Deferred free list for old options structs.
Definition rcu.c:283
asciichat_error_t options_state_set(const options_t *opts)
Set options from a parsed options struct.
Definition rcu.c:439
void config_schema_destroy(void)
Clean up dynamically allocated schema resources.
Definition schema.c:454
Schema metadata for config file options.
Network quality metrics for a single participant.
Definition metrics.h:33
Atomic pointer wrapper.
Definition atomic.h:106
Context for boolean field updates in RCU updater callback.
Definition rcu.c:872
const char * field_name
Name of the field to update.
Definition rcu.c:873
bool value
New value to set.
Definition rcu.c:874
Context for double field updates in RCU updater callback.
Definition rcu.c:1268
double value
New value to set.
Definition rcu.c:1270
const char * field_name
Name of the field to update.
Definition rcu.c:1269
Field name to registry option name mapping.
Definition rcu.c:588
const char * option
Registry option name (e.g., "color-mode")
Definition rcu.c:590
const char * field
C struct field name (e.g., "color_mode")
Definition rcu.c:589
Context for integer field updates in RCU updater callback.
Definition rcu.c:722
int value
New value to set.
Definition rcu.c:724
const char * field_name
Name of the field to update.
Definition rcu.c:723
atomic_t state
lifecycle_state_t enum value
Definition lifecycle.h:59
Mutex type (POSIX: pthread_mutex_t with debug tracking)
int max
Maximum value (or 0 if no limit)
Definition builder.h:203
int min
Minimum value (or 0 if no limit)
Definition builder.h:202
option_input_type_t input_type
What kind of input this option expects.
Definition builder.h:214
const int * enum_integer_values
Actual enum integer values (e.g., {-1, 0, 1, 2, 3} for non-sequential enums)
Definition builder.h:198
const char ** enum_values
Enum value strings (null-terminated, e.g., {"auto", "none", "16", "256", "truecolor",...
Definition builder.h:195
struct option_metadata_t::@19 numeric_range
Consolidated options structure.
bool snapshot_mode
Snapshot mode (one frame and exit)
float speakers_volume
Speaker volume multiplier (0.0-1.0, default 1.0)
int discovery_port
discovery server port (default: 27225)
bool webrtc
Enable WebRTC mode for discovery session (default: true, P2P WebRTC)
bool encrypt_enabled
Enable encryption.
terminal_color_mode_t color_mode
Color mode (auto/none/16/256/truecolor)
bool discovery_expose_ip
ACDS: explicitly allow public IP disclosure without verification (opt-in)
char turn_servers[256]
ACDS: Comma-separated list of TURN server URLs.
int force_utf8
UTF-8 support setting (auto/true/false)
bool discovery
Enable discovery session registration (default: false)
bool webrtc_relay_only
–webrtc-relay-only: Require TURN relay candidates
float microphone_sensitivity
Microphone volume multiplier (0.0-1.0, default 1.0)
int compression_level
zstd compression level (1-9)
bool fft
Replace the video image with a live audio frequency spectrogram.
bool show_capabilities
Show terminal capabilities and exit.
bool no_mdns_advertise
Disable mDNS service advertisement (server only)
bool fps_counter
Show FPS counter overlay in top-right corner.
char password[256]
Password string.
int microphone_index
Microphone device index (-1 = default)
asciichat_mode_t detected_mode
Mode detected from command-line arguments.
bool strip_ansi
Strip ANSI escape sequences.
bool audio_analysis_enabled
Enable audio analysis (debug)
char stun_servers[256]
ACDS: Comma-separated list of STUN server URLs.
int height
Terminal height in characters (int for OPTION_TYPE_INT compat)
bool enable_upnp
Enable UPnP/NAT-PMP port mapping for direct TCP (opt-in via –upnp)
bool webrtc_skip_stun
–webrtc-skip-stun: Skip Stage 2 (STUN), go to TURN
bool lan_discovery
Enable LAN service discovery via mDNS (client only)
bool audio_enabled
Enable audio streaming.
bool no_encrypt
Disable encryption (opt-out)
bool no_webrtc
–no-webrtc: Disable WebRTC, use Direct TCP only
bool matrix_rain
Matrix digital rain effect (false = disabled)
bool webrtc_disable_turn
–webrtc-disable-turn: Disable Stage 3 (TURN), use STUN only
bool audio_no_playback
Disable speaker playback (debug)
bool auto_height
Auto-detect height from terminal.
log_level_t log_level
Log level threshold.
char turn_credential[256]
ACDS: Credential/password for TURN authentication.
bool require_server_identity
ACDS: require servers to provide signed Ed25519 identity.
char palette_custom[256]
Custom palette characters.
bool status_screen
Show status screen (default: true = show, use –no-status-screen to hide)
int max_clients
Maximum concurrent clients (server only)
bool palette_custom_set
True if custom palette was set.
render_mode_t render_mode
Render mode (foreground/background/half-block)
bool splash_screen
Show splash screen (default: true = show, use –no-splash-screen to hide)
char discovery_database_path[256]
~/.ascii-chat/discovery.db)
char server_key[256]
Expected server public key (client)
bool no_audio_mixer
Disable audio mixer (debug)
char encrypt_keyfile[256]
Alternative key file path.
bool no_compress
Disable compression entirely.
char address6[256]
IPv6 bind address (server only)
int speakers_index
Speakers device index (-1 = default)
color_filter_t color_filter
Monochromatic color filter (none/black/white/green/etc)
bool prefer_webrtc
–prefer-webrtc: Try WebRTC before Direct TCP
bool quiet
Quiet mode (suppress logs)
double snapshot_delay
Snapshot delay in seconds.
int fps
Target framerate (1-144, default: 60)
bool stretch
Allow aspect ratio distortion.
bool waveform
Replace the video image with a live audio waveform.
int reconnect_attempts
Number of reconnection attempts (-1=infinite, 0=none)
bool test_pattern
Use test pattern instead of webcam.
bool media_from_stdin
Reading from stdin (detected from "--file -")
bool require_client_verify
Client: only connect to servers whose identity was verified by ACDS.
char discovery_server[256]
discovery server address (default: 127.0.0.1)
int width
Terminal width in characters (int for OPTION_TYPE_INT compat)
bool require_server_verify
Server: only accept clients who verified via ACDS.
palette_type_t palette_type
Selected palette type.
bool flip_y
Flip video vertically (Y-axis)
char turn_secret[256]
ACDS: Shared secret for dynamic TURN credential generation (HMAC-SHA1)
bool media_loop
Loop media file playback.
char discovery_service_key[256]
HTTPS URL)
bool require_client_identity
ACDS: require clients to provide signed Ed25519 identity.
bool discovery_insecure
ACDS: skip server key verification (MITM-vulnerable, requires explicit opt-in)
bool encode_audio
Enable Opus audio encoding.
char encrypt_key[256]
SSH/GPG key file path (first –key flag, kept for compatibility)
char media_file[256]
Media file path or "-" for stdin.
char turn_username[256]
ACDS: Username for TURN authentication.
char log_file[256]
Log file path.
char address[256]
Server address (client) or bind address (server)
bool flip_x
Flip video horizontally (X-axis). Ignored for webcam on macOS.
bool auto_width
Auto-detect width from terminal.
char client_keys[256]
Allowed client keys (server)
Registry entry - stores option definition with mode bitmask and metadata.
option_type_t type
bool(* validate_fn)(const void *options_struct, char **error_msg)
size_t default_value_size
option_metadata_t metadata
Enum values, numeric ranges, examples.
size_t offset
Context for string field updates in RCU updater callback.
Definition rcu.c:1139
const char * value
New value to set.
Definition rcu.c:1141
const char * field_name
Name of the field to update.
Definition rcu.c:1140
color_filter_t
Monochromatic color filter enumeration.
Definition terminal.h:599
render_mode_t
Render mode preferences.
Definition terminal.h:662
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:578
@ TERM_COLOR_AUTO
Auto-detect color support from terminal capabilities.
Definition terminal.h:580