ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
options/common.c
Go to the documentation of this file.
1
11
13#include <ascii-chat/common.h>
14#include <ascii-chat/log/log.h>
24
25#include <limits.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <sys/stat.h>
30
31// ============================================================================
32// Helper Functions
33// ============================================================================
34
35// Find the most similar option name to an unknown option
36// Returns the best matching option name, or NULL if no good match found
37// Note: Uses LEVENSHTEIN_SUGGESTION_THRESHOLD from levenshtein.h
38const char *find_similar_option(const char *unknown_opt, const struct option *options) {
39 if (!unknown_opt || !options) {
40 return NULL;
41 }
42
43 const char *best_match = NULL;
44 size_t best_distance = SIZE_MAX;
45
46 for (int i = 0; options[i].name != NULL; i++) {
47 size_t dist = levenshtein(unknown_opt, options[i].name);
48 if (dist < best_distance) {
49 best_distance = dist;
50 best_match = options[i].name;
51 }
52 }
53
54 // Only suggest if the distance is within our threshold
55 if (best_distance <= LEVENSHTEIN_SUGGESTION_THRESHOLD) {
56 return best_match;
57 }
58
59 return NULL;
60}
61
62// Helper to format all available modes for an option (used in error messages)
63// Returns comma-separated list like "server, client, mirror" or "global options"
65 static char buffer[256];
66 buffer[0] = '\0';
67
68 bool first = true;
69
70 // Check if it's a global/binary option
71 if (mode_bitmask & OPTION_MODE_BINARY) {
72 SAFE_SNPRINTF(buffer, sizeof(buffer), "global options");
73 return buffer;
74 }
75
76 // Build comma-separated list of modes (ordered: default, client, server, mirror, discovery-service)
77 if (mode_bitmask & OPTION_MODE_DISCOVERY) {
78 safe_snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), "%sdefault", first ? "" : ", ");
79 first = false;
80 }
81 if (mode_bitmask & OPTION_MODE_CLIENT) {
82 safe_snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), "%sclient", first ? "" : ", ");
83 first = false;
84 }
85 if (mode_bitmask & OPTION_MODE_SERVER) {
86 safe_snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), "%sserver", first ? "" : ", ");
87 first = false;
88 }
89 if (mode_bitmask & OPTION_MODE_MIRROR) {
90 safe_snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), "%smirror", first ? "" : ", ");
91 first = false;
92 }
93 if (mode_bitmask & OPTION_MODE_DISCOVERY_SVC) {
94 safe_snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), "%sdiscovery-service", first ? "" : ", ");
95 first = false;
96 }
97
98 // Fallback if no modes matched
99 if (buffer[0] == '\0') {
100 SAFE_SNPRINTF(buffer, sizeof(buffer), "unknown mode");
101 }
102
103 return buffer;
104}
105
106// Find similar option across all modes and suggest it with mode information
107// Returns formatted suggestion string with mode info, or NULL if no good match
108const char *find_similar_option_with_mode(const char *unknown_opt, const options_config_t *config,
109 option_mode_bitmask_t current_mode_bitmask) {
110 if (!unknown_opt || !config) {
111 return NULL;
112 }
113
114 // Extract the option name without dashes
115 const char *opt_name = unknown_opt;
116 if (strncmp(opt_name, "--", 2) == 0) {
117 opt_name += 2;
118 } else if (strncmp(opt_name, "-", 1) == 0) {
119 opt_name += 1;
120 } else {
121 return NULL; // Not an option format
122 }
123
124 const option_descriptor_t *best_match = NULL;
125 size_t best_distance = SIZE_MAX;
126
127 // Search through all descriptors
128 for (size_t i = 0; i < config->num_descriptors; i++) {
129 const option_descriptor_t *desc = &config->descriptors[i];
130 if (!desc->long_name)
131 continue;
132
133 // Calculate distance to the long name
134 size_t dist = levenshtein(opt_name, desc->long_name);
135 if (dist < best_distance) {
136 best_distance = dist;
137 best_match = desc;
138 }
139 }
140
141 // Only suggest if the distance is within our threshold
142 if (best_distance > LEVENSHTEIN_SUGGESTION_THRESHOLD || !best_match) {
143 return NULL;
144 }
145
146 // Check if the option is not available in current mode
147 bool available_in_current_mode = (best_match->mode_bitmask & current_mode_bitmask) != 0;
148
149 static char suggestion[256];
150 if (available_in_current_mode) {
151 // Option exists but user typed it wrong - just suggest the correct spelling
152 safe_snprintf(suggestion, sizeof(suggestion), "Did you mean '--%s'?", best_match->long_name);
153 } else {
154 // Option exists but in a different mode - show all available modes
155 const char *modes_str = format_available_modes(best_match->mode_bitmask);
156 safe_snprintf(suggestion, sizeof(suggestion), "Did you mean '--%s' (available in modes: %s)?",
157 best_match->long_name, modes_str);
158 }
159
160 return suggestion;
161}
162
163// Safely parse string to integer with validation
164int strtoint_safe(const char *str) {
165 if (!str || *str == '\0') {
166 return INT_MIN; // Error: NULL or empty string
167 }
168
169 int32_t result = 0;
170 // Use safe parsing utility with full int32 range validation
171 if (parse_int32(str, &result, INT_MIN, INT_MAX) != ASCIICHAT_OK) {
172 return INT_MIN; // Error: invalid input or out of range
173 }
174
175 return (int)result;
176}
177
178// Forward declaration for get_required_argument (defined later in file)
179char *get_required_argument(const char *opt_value, char *buffer, size_t buffer_size, const char *option_name,
180 asciichat_mode_t mode);
181
182// Validate and retrieve required argument for an option
183char *validate_required_argument(const char *optarg, char *argbuf, size_t argbuf_size, const char *option_name,
184 asciichat_mode_t mode) {
185 char *value = get_required_argument(optarg, argbuf, argbuf_size, option_name, mode);
186 if (!value) {
187 (void)option_error_invalid();
188 }
189 return value;
190}
191
192// Validate a positive integer value (internal option parsing helper)
193bool validate_positive_int_opt(const char *value_str, int *out_value, const char *param_name) {
194 if (!value_str || !out_value) {
195 return false;
196 }
197
198 int val = strtoint_safe(value_str);
199 if (val == INT_MIN || val <= 0) {
200 log_error("Invalid %s value '%s'. %s must be a positive integer.", param_name, value_str, param_name);
201 return false;
202 }
203
204 *out_value = val;
205 return true;
206}
207
208// Validate port number (1-65535) (internal option parsing helper)
209bool validate_port_opt(const char *value_str, uint16_t *out_port) {
210 if (!value_str || !out_port) {
211 return false;
212 }
213
214 // Use safe integer parsing with range validation
215 if (parse_port(value_str, out_port) != ASCIICHAT_OK) {
216 log_error("Invalid port value '%s'. Port must be a number between 1 and 65535.", value_str);
217 return false;
218 }
219
220 return true;
221}
222
223// Validate FPS value (1-144) (internal option parsing helper)
224bool validate_fps_opt(const char *value_str, int *out_fps) {
225 if (!value_str || !out_fps) {
226 return false;
227 }
228
229 int fps_val = strtoint_safe(value_str);
230 if (fps_val == INT_MIN || fps_val < 1 || fps_val > 144) {
231 log_error("Invalid FPS value '%s'. FPS must be between 1 and 144.", value_str);
232 return false;
233 }
234
235 *out_fps = fps_val;
236 return true;
237}
238
239// Validate webcam index using the common device index validator
240bool validate_webcam_index(const char *value_str, unsigned short int *out_index) {
241 if (!value_str || !out_index) {
242 return false;
243 }
244
245 char error_msg[BUFFER_SIZE_SMALL];
246 int parsed_index = validate_opt_device_index(value_str, error_msg, sizeof(error_msg));
247 if (parsed_index == INT_MIN) {
248 log_error("Invalid webcam index: %s", error_msg);
249 return false;
250 }
251 // Webcam index doesn't support -1 (default), must be >= 0
252 if (parsed_index < 0) {
253 log_error("Invalid webcam index '%s'. Webcam index must be a non-negative integer.", value_str);
254 return false;
255 }
256
257 *out_index = (unsigned short int)parsed_index;
258 return true;
259}
260
261// Detect default SSH key path for the current user
262asciichat_error_t detect_default_ssh_key(char *key_path, size_t path_size) {
263 // Use expand_path utility to resolve ~/.ssh/id_ed25519
264 char *full_path = expand_path("~/.ssh/id_ed25519");
265 if (!full_path) {
266 return SET_ERRNO(ERROR_CONFIG, "Could not expand SSH key path");
267 }
268
269 // Check if the Ed25519 private key file exists
270 struct stat st;
271 bool found = (stat(full_path, &st) == 0 && S_ISREG(st.st_mode));
272
273 if (found) {
274 SAFE_SNPRINTF(key_path, path_size, "%s", full_path);
275 log_debug("Found default SSH key: %s", full_path);
276 SAFE_FREE(full_path);
277 return ASCIICHAT_OK;
278 }
279
280 log_error("No Ed25519 SSH key found at %s", full_path);
281 SAFE_FREE(full_path);
282 return SET_ERRNO(
284 "Only Ed25519 keys are supported (modern, secure, fast). Generate a new key with: ssh-keygen -t ed25519");
285}
286
287// ============================================================================
288// Argument Processing Helpers
289// ============================================================================
290
291// Helper function to strip equals sign from optarg if present
292char *strip_equals_prefix(const char *opt_value, char *buffer, size_t buffer_size) {
293 if (!opt_value)
294 return NULL;
295
296 SAFE_SNPRINTF(buffer, buffer_size, "%s", opt_value);
297 char *value_str = buffer;
298 if (value_str[0] == '=') {
299 value_str++; // Skip the equals sign
300 }
301
302 // Return NULL for empty strings (treat as missing argument)
303 if (strlen(value_str) == 0) {
304 return NULL;
305 }
306
307 return value_str;
308}
309
310// Helper function to handle required arguments with consistent error messages
311// Returns NULL on error (caller should check and return error code)
312char *get_required_argument(const char *opt_value, char *buffer, size_t buffer_size, const char *option_name,
313 asciichat_mode_t mode) {
314 // Check if opt_value is NULL or empty
315 if (!opt_value || strlen(opt_value) == 0) {
316 goto error;
317 }
318
319 // Check if getopt_long returned the option name itself as the argument
320 // This happens when a long option requiring an argument is at the end of argv
321 if (opt_value && option_name && strcmp(opt_value, option_name) == 0) {
322 goto error;
323 }
324
325 // Process the argument normally
326 char *value_str = strip_equals_prefix(opt_value, buffer, buffer_size);
327 if (!value_str) {
328 goto error;
329 }
330
331 return value_str;
332
333error:
334 (void)0;
335 const char *mode_name = (mode == MODE_SERVER ? "server" : (mode == MODE_MIRROR ? "mirror" : "client"));
336 log_error("%s: option '--%s' requires an argument", mode_name, option_name);
337 return NULL; // Signal error to caller
338}
339
340// Read password from stdin with prompt (returns allocated string or NULL on error)
341char *read_password_from_stdin(const char *prompt) {
342 char *password_buf = SAFE_MALLOC(PASSWORD_MAX_LEN, char *);
343 if (!password_buf) {
344 return NULL;
345 }
346
347 if (prompt_password_simple(prompt, password_buf, PASSWORD_MAX_LEN) != 0) {
348 SAFE_FREE(password_buf);
349 return NULL;
350 }
351
352 return password_buf; // Caller must free
353}
354
355// ============================================================================
356// Global Variable Definitions
357// ============================================================================
358// Removed: All opt_* global variables moved to RCU options_t struct
359// Access these via GET_OPTION(field_name) for thread-safe lock-free reads
360// Modify via options_set_*() functions for thread-safe updates
361// ============================================================================
362
363// These flags are used during parsing, not part of RCU options
365
366// Track if --port was explicitly set via command-line flag (for mutual exclusion validation)
368
369// Default weights; must add up to 1.0
370const float weight_red = 0.2989f;
371const float weight_green = 0.5866f;
372const float weight_blue = 0.1145f;
373
374// Color channel lookup tables (used by image.c for palette precomputation)
375// Size must match ASCII_LUMINANCE_LEVELS (256) from ascii.h
376unsigned short int RED[256];
377unsigned short int GREEN[256];
378unsigned short int BLUE[256];
379unsigned short int GRAY[256];
380
381// ============================================================================
382// Shared Option Parsers (Client + Mirror Common Options)
383// ============================================================================
384
386 if (!value_str || !opts) {
387 return ERROR_INVALID_PARAM;
388 }
389
390 if (strcmp(value_str, "auto") == 0 || strcmp(value_str, "a") == 0) {
392 } else if (strcmp(value_str, "none") == 0 || strcmp(value_str, "mono") == 0) {
394 } else if (strcmp(value_str, "16") == 0 || strcmp(value_str, "16color") == 0 || strcmp(value_str, "ansi") == 0) {
396 } else if (strcmp(value_str, "256") == 0 || strcmp(value_str, "256color") == 0) {
398 } else if (strcmp(value_str, "truecolor") == 0 || strcmp(value_str, "24bit") == 0 || strcmp(value_str, "tc") == 0 ||
399 strcmp(value_str, "rgb") == 0 || strcmp(value_str, "true") == 0) {
401 } else {
402 log_error("Invalid color mode '%s'. Valid modes: auto, none, 16, 256, truecolor", value_str);
403 return ERROR_INVALID_PARAM;
404 }
405
406 return ASCIICHAT_OK;
407}
408
410 if (!value_str || !opts) {
411 return ERROR_INVALID_PARAM;
412 }
413
414 if (strcmp(value_str, "foreground") == 0 || strcmp(value_str, "fg") == 0) {
416 } else if (strcmp(value_str, "background") == 0 || strcmp(value_str, "bg") == 0) {
418 } else if (strcmp(value_str, "half-block") == 0 || strcmp(value_str, "halfblock") == 0) {
420 } else {
421 log_error("Invalid render mode '%s'. Valid modes: foreground, background, half-block", value_str);
422 return ERROR_INVALID_PARAM;
423 }
424
425 return ASCIICHAT_OK;
426}
427
428asciichat_error_t parse_palette_option(const char *value_str, options_t *opts) {
429 if (!value_str || !opts) {
430 return ERROR_INVALID_PARAM;
431 }
432
433 if (strcmp(value_str, "standard") == 0) {
435 } else if (strcmp(value_str, "blocks") == 0) {
437 } else if (strcmp(value_str, "digital") == 0) {
439 } else if (strcmp(value_str, "minimal") == 0) {
441 } else if (strcmp(value_str, "cool") == 0) {
443 } else if (strcmp(value_str, "custom") == 0) {
445 } else {
446 log_error("Invalid palette '%s'. Valid palettes: standard, blocks, digital, minimal, cool, custom", value_str);
447 return ERROR_INVALID_PARAM;
448 }
449
450 return ASCIICHAT_OK;
451}
452
454 if (!value_str || !opts) {
455 return ERROR_INVALID_PARAM;
456 }
457
458 if (strlen(value_str) >= sizeof(opts->palette_custom)) {
459 log_error("Invalid palette-chars: too long (%zu chars, max %zu)", strlen(value_str),
460 sizeof(opts->palette_custom) - 1);
461 return ERROR_INVALID_PARAM;
462 }
463
464 SAFE_STRNCPY(opts->palette_custom, value_str, sizeof(opts->palette_custom));
465 opts->palette_custom[sizeof(opts->palette_custom) - 1] = '\0';
466 opts->palette_custom_set = true;
468
469 return ASCIICHAT_OK;
470}
471
472asciichat_error_t parse_width_option(const char *value_str, options_t *opts) {
473 if (!opts) {
474 return ERROR_INVALID_PARAM;
475 }
476
477 int width_val;
478 if (!validate_positive_int_opt(value_str, &width_val, "width")) {
479 return ERROR_INVALID_PARAM;
480 }
481
482 opts->width = width_val;
483 opts->auto_width = false;
484
485 return ASCIICHAT_OK;
486}
487
488asciichat_error_t parse_height_option(const char *value_str, options_t *opts) {
489 if (!opts) {
490 return ERROR_INVALID_PARAM;
491 }
492
493 int height_val;
494 if (!validate_positive_int_opt(value_str, &height_val, "height")) {
495 return ERROR_INVALID_PARAM;
496 }
497
498 opts->height = height_val;
499 opts->auto_height = false;
500
501 return ASCIICHAT_OK;
502}
503
505 if (!opts) {
506 return ERROR_INVALID_PARAM;
507 }
508
509 unsigned short int index_val;
510 if (!validate_webcam_index(value_str, &index_val)) {
511 return ERROR_INVALID_PARAM;
512 }
513
514 opts->webcam_index = index_val;
515
516 return ASCIICHAT_OK;
517}
518
520 if (!value_str || !opts) {
521 return ERROR_INVALID_PARAM;
522 }
523
524 char *endptr;
525 float delay = strtof(value_str, &endptr);
526 if (endptr == value_str || *endptr != '\0' || delay < 0.0f) {
527 log_error("Invalid snapshot delay '%s'. Must be a non-negative number.", value_str);
528 return ERROR_INVALID_PARAM;
529 }
530
531 opts->snapshot_delay = delay;
532
533 return ASCIICHAT_OK;
534}
535
537 if (!opts) {
538 return ERROR_INVALID_PARAM;
539 }
540
541 char error_msg[BUFFER_SIZE_SMALL];
542 int log_level = validate_opt_log_level(value_str, error_msg, sizeof(error_msg));
543
544 if (log_level == -1) {
545 log_error("%s", error_msg);
546 return ERROR_INVALID_PARAM;
547 }
548
549 opts->log_level = (log_level_t)log_level;
550
551 return ASCIICHAT_OK;
552}
553
554// ============================================================================
555// Option Formatting Utilities
556// ============================================================================
557
559 switch (type) {
560 case OPTION_TYPE_INT:
561 return "INTEGER";
563 return "NUMBER";
565 return "STRING";
567 return "VALUE";
568 case OPTION_TYPE_BOOL:
569 return "BOOLEAN";
571 default:
572 return "";
573 }
574}
575
576int options_format_default_value(option_type_t type, const void *default_value, char *buf, size_t bufsize) {
577 if (!default_value || !buf || bufsize == 0) {
578 return 0;
579 }
580
581 switch (type) {
582 case OPTION_TYPE_BOOL:
583 return safe_snprintf(buf, bufsize, "%s", *(const bool *)default_value ? "true" : "false");
584 case OPTION_TYPE_INT: {
585 int int_val = 0;
586 memcpy(&int_val, default_value, sizeof(int));
587 return safe_snprintf(buf, bufsize, "%d", int_val);
588 }
590 return safe_snprintf(buf, bufsize, "%s", *(const char *const *)default_value);
591 case OPTION_TYPE_DOUBLE: {
592 double double_val = 0.0;
593 memcpy(&double_val, default_value, sizeof(double));
594 return safe_snprintf(buf, bufsize, "%.2f", double_val);
595 }
596 default:
597 // OPTION_TYPE_CALLBACK and OPTION_TYPE_ACTION don't have defaults to display
598 return 0;
599 }
600}
601
602// ============================================================================
603// Terminal Dimension Utilities
604// ============================================================================
605
607 if (!opts) {
608 return;
609 }
610
611 unsigned short int term_width, term_height;
612
613 // Note: Logging is not available during options_init, so we can't use log_debug here
614 asciichat_error_t result = get_terminal_size(&term_width, &term_height);
615
616 if (result == ASCIICHAT_OK) {
617 // If both dimensions are auto, set height to terminal height and let
618 // aspect_ratio calculate width
619 if (opts->auto_height && opts->auto_width) {
620 opts->height = term_height;
621 opts->width = term_width; // Also set width when both are auto
622 }
623 // If only height is auto, use full terminal height
624 else if (opts->auto_height) {
625 opts->height = term_height;
626 }
627 // If only width is auto, use full terminal width
628 else if (opts->auto_width) {
629 opts->width = term_width;
630 }
631 } else {
632 // Terminal size detection failed, but we can still continue with defaults
633 }
634}
635
637 if (!opts) {
638 return;
639 }
640
641 unsigned short int term_width, term_height;
642 // Get current terminal size (get_terminal_size already handles ioctl first, then $COLUMNS/$LINES fallback)
643 asciichat_error_t terminal_result = get_terminal_size(&term_width, &term_height);
644 if (terminal_result == ASCIICHAT_OK) {
645 // Use INFO level so this is visible without -v flag (important for debugging dimension issues)
646 log_dev("Terminal size detected: %ux%u (auto_width=%d, auto_height=%d)", term_width, term_height, opts->auto_width,
647 opts->auto_height);
648 if (opts->auto_width) {
649 opts->width = term_width;
650 log_debug("Auto-width: set width to %u", opts->width);
651 }
652 if (opts->auto_height) {
653 opts->height = term_height;
654 log_debug("Auto-height: set height to %u", opts->height);
655 }
656 log_debug("Final dimensions: %ux%u", opts->width, opts->height);
657 } else {
658 // Terminal detection failed - keep the default values set in options_init()
659 log_warn("TERMINAL_DETECT_FAIL: Could not detect terminal size, using defaults: %ux%u", opts->width, opts->height);
660 }
661}
662
663// ============================================================================
664// Generic Usage Function (Unified Implementation)
665// ============================================================================
666
670typedef struct {
672 const char *program_name;
673 const char *description;
675
676static const mode_metadata_t mode_info[] = {
677 {MODE_SERVER, "ascii-chat server", "host a server mixing video and audio for ascii-chat clients"},
678 {MODE_CLIENT, "ascii-chat client", "connect to an ascii-chat server"},
679 {MODE_MIRROR, "ascii-chat mirror", "use the webcam or files or urls without network connections"},
680 {MODE_DISCOVERY_SERVICE, "ascii-chat discovery-service", "secure p2p session signalling"},
681 {MODE_DISCOVERY, "💻📸 ascii-chat 🔡💬", "Video chat in your terminal"},
682};
683
684void usage(FILE *desc, asciichat_mode_t mode) {
685 if (!desc) {
686 return;
687 }
688
689 // Find mode metadata
690 const mode_metadata_t *metadata = NULL;
691 for (size_t i = 0; i < sizeof(mode_info) / sizeof(mode_info[0]); i++) {
692 if (mode_info[i].mode == mode) {
693 metadata = &mode_info[i];
694 break;
695 }
696 }
697
698 if (!metadata) {
699 (void)fprintf(desc, "error: Unknown mode\n");
700 return;
701 }
702
703 // Get unified config
704 const options_config_t *config = options_preset_unified(metadata->program_name, metadata->description);
705 if (!config) {
706 (void)fprintf(desc, "Error: Failed to create options config\n");
707 return;
708 }
709
710 options_print_help_for_mode(config, mode, metadata->program_name, metadata->description, desc);
712}
713
714// ============================================================================
715// Options Validation Helper
716// ============================================================================
717
718asciichat_error_t validate_options_and_report(const void *config, const void *opts) {
719 if (!config || !opts) {
720 return SET_ERRNO(ERROR_INVALID_PARAM, "Config or options is NULL");
721 }
722
723 char *error_message = NULL;
724 // Cast opaque config pointer to actual type
725 const options_config_t *config_typed = (const options_config_t *)config;
726 const options_t *options = (const options_t *)opts;
727 if (options->webrtc_relay_only && options->detected_mode == MODE_CLIENT) {
728 return SET_ERRNO(ERROR_INVALID_PARAM, "Relay only requires discovery mode; connect using a session name");
729 }
730 if (options->webrtc_relay_only && options->detected_mode == MODE_SERVER && !options->discovery) {
731 return SET_ERRNO(ERROR_INVALID_PARAM, "Server relay only requires --discovery");
732 }
733 if (options->webrtc_relay_only && (options->no_webrtc || options->webrtc_disable_turn)) {
734 return SET_ERRNO(ERROR_INVALID_PARAM, "--webrtc-relay-only conflicts with --no-webrtc or --webrtc-disable-turn");
735 }
736 if ((options->turn_username[0] != '\0') != (options->turn_credential[0] != '\0')) {
737 return SET_ERRNO(ERROR_INVALID_PARAM, "Provide both --turn-username and --turn-credential, or leave both blank");
738 }
739 if (strlen(options->turn_username) > 127 || strlen(options->turn_credential) > 127) {
740 return SET_ERRNO(ERROR_INVALID_PARAM, "TURN username and credential must each fit 127 bytes");
741 }
742 asciichat_error_t result = options_config_validate(config_typed, opts, &error_message);
743 if (result != ASCIICHAT_OK) {
744 if (error_message) {
745 log_error("%s", error_message);
746 free(error_message);
747 }
748 }
749 return result;
750}
751
752// ============================================================================
753// Print Project Links
754// ============================================================================
755
756void print_project_links(FILE *desc) {
757 if (!desc) {
758 return;
759 }
760
761 (void)fprintf(desc, "🔗 %s\n", colored_string(LOG_COLOR_GREY, "https://ascii-chat.com"));
762 (void)fprintf(desc, "🔗 %s\n", colored_string(LOG_COLOR_GREY, "https://github.com/zfogg/ascii-chat"));
763}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
asciichat_error_t options_config_validate(const options_config_t *config, const void *options_struct, char **error_message)
Validate options struct.
Definition builder.c:1840
void options_config_destroy(options_config_t *config)
Free options config.
Definition builder.c:631
Options builder API for flexible command-line option configuration.
option_type_t
Option value types.
Definition builder.h:161
@ OPTION_TYPE_INT
Integer value (–count 42)
Definition builder.h:163
@ OPTION_TYPE_DOUBLE
Floating point (–ratio 1.5)
Definition builder.h:165
@ OPTION_TYPE_STRING
String value (–name foo)
Definition builder.h:164
@ OPTION_TYPE_ACTION
Action that executes and may exit (–list-webcams, etc.)
Definition builder.h:167
@ OPTION_TYPE_CALLBACK
Custom parser function.
Definition builder.h:166
@ OPTION_TYPE_BOOL
Boolean flag (–flag, no value)
Definition builder.h:162
⚙️ Common definitions, error codes, macros, and types shared throughout the application
int buffer_size
Size of circular buffer.
Definition grep.c:90
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
unsigned short uint16_t
Definition common.h:57
#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 SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
#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_CRYPTO_KEY
Definition error_codes.h:97
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CONFIG
Definition error_codes.h:57
@ ERROR_INVALID_PARAM
#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
@ LOG_COLOR_GREY
Definition log/log.h:137
unsigned short int GRAY[256]
Grayscale lookup table.
#define COLOR_MODE_16_COLOR
16-color mode (full name)
unsigned short int BLUE[256]
Blue channel lookup table.
#define COLOR_MODE_256_COLOR
256-color mode (full name)
#define COLOR_MODE_TRUECOLOR
24-bit truecolor mode
asciichat_mode_t
Mode type for options parsing.
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
int strtoint_safe(const char *str)
Safely parse string to integer with validation.
void update_dimensions_to_terminal_size(options_t *opts)
Update dimensions to current terminal size.
const float weight_blue
Blue weight for luminance calculation.
const float weight_red
Red weight for luminance calculation.
int validate_opt_device_index(const char *value_str, char *error_msg, size_t error_msg_size)
Validate device index (-1 for default, 0+ for specific device)
Definition validation.c:456
option_mode_bitmask_t
Option mode bitmask.
size_t levenshtein(const char *a, const char *b)
Calculate Levenshtein distance between two strings.
Definition levenshtein.c:74
#define COLOR_MODE_NONE
Monochrome mode.
#define LEVENSHTEIN_SUGGESTION_THRESHOLD
Maximum edit distance to suggest an option.
Definition levenshtein.h:32
void usage(FILE *desc, asciichat_mode_t mode)
Print usage information for a specific mode.
unsigned short int GREEN[256]
Green channel lookup table.
const float weight_green
Green weight for luminance calculation.
unsigned short int RED[256]
Red channel lookup table.
int validate_opt_log_level(const char *value_str, char *error_msg, size_t error_msg_size)
Validate log level string.
Definition validation.c:234
void update_dimensions_for_full_height(options_t *opts)
Update dimensions for full-height mode.
@ MODE_CLIENT
Client mode - network client options.
@ MODE_DISCOVERY_SERVICE
Discovery server mode - session management and WebRTC signaling.
@ MODE_SERVER
Server mode - network server options.
@ MODE_MIRROR
Mirror mode - local webcam viewing (no network)
@ MODE_DISCOVERY
Discovery mode - participant that can dynamically become host.
@ OPTION_MODE_BINARY
Binary-level options (parsed before mode detection)
@ OPTION_MODE_CLIENT
Client mode (bit 1)
@ OPTION_MODE_DISCOVERY
Discovery mode (bit 4)
@ OPTION_MODE_SERVER
Server mode (bit 0)
@ OPTION_MODE_MIRROR
Mirror mode (bit 2)
@ OPTION_MODE_DISCOVERY_SVC
Discovery server mode (bit 3)
@ PALETTE_BLOCKS
Unicode block characters: " ░░▒▒▓▓██".
Definition palette.h:90
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:98
@ PALETTE_COOL
Ascending blocks: " ▁▂▃▄▅▆▇█".
Definition palette.h:96
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
Definition palette.h:88
@ PALETTE_DIGITAL
Digital/glitch aesthetic: " -=≡≣▰▱◼".
Definition palette.h:92
@ PALETTE_MINIMAL
Simple ASCII: " .-+*#".
Definition palette.h:94
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
#define ASCIICHAT_API
Export symbols on Unix platforms (Linux, macOS)
Definition api.h:82
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
char * expand_path(const char *path)
Expand path with tilde (~) support.
Definition path.c:505
#define PASSWORD_MAX_LEN
Buffer size for password input.
Definition password.h:30
int prompt_password_simple(const char *prompt, char *password, size_t max_len)
Prompt the user for a password with simple formatting.
Definition password.c:70
void options_print_help_for_mode(const options_config_t *config, asciichat_mode_t mode, const char *program_name, const char *description, FILE *desc)
Print help for a specific mode or binary level.
Definition help.c:1105
Levenshtein distance algorithm for fuzzy string matching.
Application limits and constraints.
📝 Logging API with multiple log levels and terminal output control
bool auto_height
char * strip_equals_prefix(const char *opt_value, char *buffer, size_t buffer_size)
Strip equals sign prefix from option argument.
int options_format_default_value(option_type_t type, const void *default_value, char *buf, size_t bufsize)
Format option default value to string.
void print_project_links(FILE *desc)
Print project links with link emoji and colored styling.
const char * format_available_modes(option_mode_bitmask_t mode_bitmask)
Format all available modes for an option as comma-separated list.
char * validate_required_argument(const char *optarg, char *argbuf, size_t argbuf_size, const char *option_name, asciichat_mode_t mode)
Validate and retrieve required argument for an option.
const char * find_similar_option(const char *unknown_opt, const struct option *options)
Find a similar option name for typo suggestions.
asciichat_error_t parse_render_mode_option(const char *value_str, options_t *opts)
Parse –render-mode option and set opts->render_mode.
asciichat_error_t parse_width_option(const char *value_str, options_t *opts)
Parse –width option and set opts->width.
asciichat_error_t detect_default_ssh_key(char *key_path, size_t path_size)
Detect default SSH key path for the current user.
char * read_password_from_stdin(const char *prompt)
Read password from stdin with prompt.
bool validate_port_opt(const char *value_str, uint16_t *out_port)
Validate port number (1-65535)
asciichat_error_t parse_height_option(const char *value_str, options_t *opts)
Parse –height option and set opts->height.
bool validate_fps_opt(const char *value_str, int *out_fps)
Validate FPS value (1-144)
asciichat_error_t parse_palette_chars_option(const char *value_str, options_t *opts)
Parse –palette-chars option and set opt_palette_custom.
asciichat_error_t parse_palette_option(const char *value_str, options_t *opts)
Parse –palette option and set opt_palette_type.
bool auto_width
asciichat_error_t parse_log_level_option(const char *value_str, options_t *opts)
Parse –log-level option and set opt_log_level.
const char * find_similar_option_with_mode(const char *unknown_opt, const options_config_t *config, option_mode_bitmask_t current_mode_bitmask)
Find similar option across all modes and suggest with mode information.
asciichat_error_t validate_options_and_report(const void *config, const void *opts)
Validate options and report errors to stderr.
bool validate_positive_int_opt(const char *value_str, int *out_value, const char *param_name)
Validate a positive integer value.
const char * options_get_type_placeholder(option_type_t type)
Get placeholder string for option type.
asciichat_error_t parse_color_mode_option(const char *value_str, options_t *opts)
Parse –color-mode option and set opts->color_mode.
asciichat_error_t parse_snapshot_delay_option(const char *value_str, options_t *opts)
Parse –snapshot-delay option and set opts->snapshot_delay.
char * get_required_argument(const char *opt_value, char *buffer, size_t buffer_size, const char *option_name, asciichat_mode_t mode)
Handle required arguments with consistent error messages.
asciichat_error_t parse_webcam_index_option(const char *value_str, options_t *opts)
Parse –webcam-index option and set opts->webcam_index.
bool port_explicitly_set_via_flag
bool validate_webcam_index(const char *value_str, unsigned short int *out_index)
Validate webcam index using the common device index validator.
Common utilities and helpers for option parsing across all modes.
Validation functions for options parsing.
Password prompting utilities with secure input and formatting.
📂 Path Manipulation Utilities
asciichat_error_t get_terminal_size(unsigned short int *width, unsigned short int *height)
Get terminal size with multiple fallback methods.
options_config_t * options_preset_unified(const char *program_name, const char *description)
Build unified options config with ALL options (binary + all modes)
Definition presets.c:51
Cross-platform stat and file type checking macros.
Mode metadata for usage display.
const char * program_name
Program name shown in usage.
asciichat_mode_t mode
Application mode.
const char * description
One-line description of mode.
Option descriptor.
Definition builder.h:241
option_mode_bitmask_t mode_bitmask
Which modes this option applies to.
Definition builder.h:278
const char * long_name
Long option name (e.g., "port")
Definition builder.h:243
Options configuration.
Definition builder.h:401
option_descriptor_t * descriptors
Array of option descriptors.
Definition builder.h:402
size_t num_descriptors
Number of descriptors.
Definition builder.h:403
Consolidated options structure.
terminal_color_mode_t color_mode
Color mode (auto/none/16/256/truecolor)
bool discovery
Enable discovery session registration (default: false)
bool webrtc_relay_only
–webrtc-relay-only: Require TURN relay candidates
asciichat_mode_t detected_mode
Mode detected from command-line arguments.
int height
Terminal height in characters (int for OPTION_TYPE_INT compat)
bool no_webrtc
–no-webrtc: Disable WebRTC, use Direct TCP only
bool webrtc_disable_turn
–webrtc-disable-turn: Disable Stage 3 (TURN), use STUN only
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.
char palette_custom[256]
Custom palette characters.
bool palette_custom_set
True if custom palette was set.
render_mode_t render_mode
Render mode (foreground/background/half-block)
int webcam_index
Webcam device index (0 = first)
double snapshot_delay
Snapshot delay in seconds.
int width
Terminal width in characters (int for OPTION_TYPE_INT compat)
palette_type_t palette_type
Selected palette type.
char turn_username[256]
ACDS: Username for TURN authentication.
bool auto_width
Auto-detect width from terminal.
🖥️ Cross-platform terminal interface for ascii-chat
@ RENDER_MODE_FOREGROUND
Foreground colors only (text color)
Definition terminal.h:664
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:666
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:668
asciichat_error_t parse_int32(const char *str, int32_t *out_value, int32_t min_value, int32_t max_value)
Parse signed 32-bit integer with range validation.
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Parse port number (1-65535) from string.
🔍 Safe Parsing Utilities
🔤 String Manipulation and Shell Escaping Utilities