ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
validation.c
Go to the documentation of this file.
1
8
9#include <limits.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13
14#include <ascii-chat/common.h>
15#include <ascii-chat/log/log.h>
17#include <ascii-chat/util/ip.h>
21
22// ============================================================================
23// Generic Integer Range Validator
24// ============================================================================
25
40static int validate_int_range(const char *value_str, int min, int max, const char *param_name, char *error_msg,
41 size_t error_msg_size) {
42 if (!value_str || strlen(value_str) == 0) {
43 if (error_msg) {
44 SAFE_SNPRINTF(error_msg, error_msg_size, "%s value is required", param_name);
45 }
46 return INT_MIN;
47 }
48
49 int val = strtoint_safe(value_str);
50 if (val == INT_MIN || val < min || val > max) {
51 if (error_msg) {
52 if (min == max) {
53 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid %s '%s'. Must be exactly %d.", param_name, value_str, min);
54 } else if (min == 1 && max == INT_MAX) {
55 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid %s '%s'. Must be a positive integer.", param_name, value_str);
56 } else if (min == 0 && max == INT_MAX) {
57 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid %s '%s'. Must be a non-negative integer.", param_name,
58 value_str);
59 } else {
60 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid %s '%s'. Must be between %d and %d.", param_name, value_str,
61 min, max);
62 }
63 }
64 return INT_MIN;
65 }
66 return val;
67}
68
73int validate_opt_port(const char *value_str, char *error_msg, size_t error_msg_size) {
74 if (!value_str || strlen(value_str) == 0) {
75 if (error_msg) {
76 SAFE_SNPRINTF(error_msg, error_msg_size, "Port value is required");
77 }
78 return -1;
79 }
80
81 // Use safe integer parsing with range validation
82 uint16_t port_num;
83 if (parse_port(value_str, &port_num) != ASCIICHAT_OK) {
84 if (error_msg) {
85 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid port value '%s'. Port must be a number between 1 and 65535.",
86 value_str);
87 }
88 return -1;
89 }
90 return 0;
91}
92
97bool validate_port_callback(const void *options_struct, char **error_msg) {
98 const options_t *opts = (const options_t *)options_struct;
99
100 // Port is stored as an int, but we need to validate it as a string
101 // The issue is that strtol() already accepted invalid formats like " 80", "+80", "0123"
102 // So we can't validate after parsing - we need to prevent the parse in the first place
103 // This callback runs AFTER parsing, so it's too late
104
105 // Instead, check if the port value is in range (this will catch the range but not format issues)
106 if (opts->port < 1 || opts->port > 65535) {
107 if (error_msg) {
108 *error_msg = platform_strdup("Port must be between 1 and 65535");
109 }
110 return false;
111 }
112
113 return true;
114}
115
120int validate_opt_positive_int(const char *value_str, char *error_msg, size_t error_msg_size) {
121 int result = validate_int_range(value_str, 1, INT_MAX, "Value", error_msg, error_msg_size);
122 return (result == INT_MIN) ? -1 : result;
123}
124
129int validate_opt_non_negative_int(const char *value_str, char *error_msg, size_t error_msg_size) {
130 int result = validate_int_range(value_str, 0, INT_MAX, "Value", error_msg, error_msg_size);
131 return (result == INT_MIN) ? -1 : result;
132}
133
138int validate_opt_color_mode(const char *value_str, char *error_msg, size_t error_msg_size) {
139 if (!value_str) {
140 if (error_msg) {
141 SAFE_SNPRINTF(error_msg, error_msg_size, "Color mode value is required");
142 }
143 return -1;
144 }
145
146 if (strcmp(value_str, "auto") == 0) {
147 return COLOR_MODE_AUTO;
148 }
149 if (strcmp(value_str, "none") == 0 || strcmp(value_str, "mono") == 0) {
150 return COLOR_MODE_NONE;
151 }
152 if (strcmp(value_str, "16") == 0 || strcmp(value_str, "16color") == 0) {
153 return COLOR_MODE_16_COLOR;
154 }
155 if (strcmp(value_str, "256") == 0 || strcmp(value_str, "256color") == 0) {
157 }
158 if (strcmp(value_str, "truecolor") == 0 || strcmp(value_str, "24bit") == 0) {
160 }
161 if (error_msg) {
162 SAFE_SNPRINTF(error_msg, error_msg_size,
163 "Invalid color mode '%s'. Valid modes: auto, none, mono, 16, 256, truecolor", value_str);
164 }
165 return -1;
166}
167
172int validate_opt_render_mode(const char *value_str, char *error_msg, size_t error_msg_size) {
173 if (!value_str) {
174 if (error_msg) {
175 SAFE_SNPRINTF(error_msg, error_msg_size, "Render mode value is required");
176 }
177 return -1;
178 }
179
180 if (strcmp(value_str, "foreground") == 0 || strcmp(value_str, "fg") == 0) {
182 }
183 if (strcmp(value_str, "background") == 0 || strcmp(value_str, "bg") == 0) {
185 }
186 if (strcmp(value_str, "half-block") == 0 || strcmp(value_str, "halfblock") == 0) {
188 }
189 if (error_msg) {
190 SAFE_SNPRINTF(error_msg, error_msg_size,
191 "Invalid render mode '%s'. Valid modes: foreground, background, half-block", value_str);
192 }
193 return -1;
194}
195
200int validate_opt_palette(const char *value_str, char *error_msg, size_t error_msg_size) {
201 if (!value_str) {
202 if (error_msg) {
203 SAFE_SNPRINTF(error_msg, error_msg_size, "Palette value is required");
204 }
205 return -1;
206 }
207
208 if (strcmp(value_str, "standard") == 0) {
209 return PALETTE_STANDARD;
210 } else if (strcmp(value_str, "blocks") == 0) {
211 return PALETTE_BLOCKS;
212 } else if (strcmp(value_str, "digital") == 0) {
213 return PALETTE_DIGITAL;
214 } else if (strcmp(value_str, "minimal") == 0) {
215 return PALETTE_MINIMAL;
216 } else if (strcmp(value_str, "cool") == 0) {
217 return PALETTE_COOL;
218 } else if (strcmp(value_str, "custom") == 0) {
219 return PALETTE_CUSTOM;
220 } else {
221 if (error_msg) {
222 SAFE_SNPRINTF(error_msg, error_msg_size,
223 "Invalid palette '%s'. Valid palettes: standard, blocks, digital, minimal, cool, custom",
224 value_str);
225 }
226 return -1;
227 }
228}
229
234int validate_opt_log_level(const char *value_str, char *error_msg, size_t error_msg_size) {
235 if (!value_str) {
236 if (error_msg) {
237 SAFE_SNPRINTF(error_msg, error_msg_size, "Log level value is required");
238 }
239 return -1;
240 }
241
242 if (platform_strcasecmp(value_str, "dev") == 0) {
243 return LOG_DEV;
244 } else if (platform_strcasecmp(value_str, "debug") == 0) {
245 return LOG_DEBUG;
246 } else if (platform_strcasecmp(value_str, "info") == 0) {
247 return LOG_INFO;
248 } else if (platform_strcasecmp(value_str, "warn") == 0) {
249 return LOG_WARN;
250 } else if (platform_strcasecmp(value_str, "error") == 0) {
251 return LOG_ERROR;
252 } else if (platform_strcasecmp(value_str, "fatal") == 0) {
253 return LOG_FATAL;
254 } else {
255 if (error_msg) {
256 SAFE_SNPRINTF(error_msg, error_msg_size,
257 "Invalid log level '%s'. Valid levels: dev, debug, info, warn, error, fatal", value_str);
258 }
259 return -1;
260 }
261}
262
268int validate_opt_ip_address(const char *value_str, char *parsed_address, size_t address_size, bool is_client,
269 char *error_msg, size_t error_msg_size) {
270 (void)is_client; // Parameter not used but kept for API consistency
271 if (!value_str || strlen(value_str) == 0) {
272 if (error_msg) {
273 SAFE_SNPRINTF(error_msg, error_msg_size, "Address value is required");
274 }
275 return -1;
276 }
277
278 // Parse IPv6 address (remove brackets if present)
279 char parsed_addr[OPTIONS_BUFF_SIZE];
280 if (parse_ipv6_address(value_str, parsed_addr, sizeof(parsed_addr)) == 0) {
281 value_str = parsed_addr;
282 }
283
284 // Check if it's a valid IPv4 address
285 if (is_valid_ipv4(value_str)) {
286 SAFE_SNPRINTF(parsed_address, address_size, "%s", value_str);
287 return 0;
288 }
289 // Check if it's a valid IPv6 address
290 if (is_valid_ipv6(value_str)) {
291 SAFE_SNPRINTF(parsed_address, address_size, "%s", value_str);
292 return 0;
293 }
294 // Check if it looks like an invalid IP (has dots but not valid IPv4 format)
295 if (strchr(value_str, '.') != NULL) {
296 if (error_msg) {
297 SAFE_SNPRINTF(error_msg, error_msg_size,
298 "Invalid IP address format '%s'. IPv4 addresses must have exactly 4 octets.", value_str);
299 }
300 return -1;
301 }
302
303 // Otherwise, try to resolve as hostname
304 char resolved_ip[OPTIONS_BUFF_SIZE];
305 if (platform_resolve_hostname_to_ipv4(value_str, resolved_ip, sizeof(resolved_ip)) == 0) {
306 SAFE_SNPRINTF(parsed_address, address_size, "%s", resolved_ip);
307 return 0;
308 } else {
309 if (error_msg) {
310 SAFE_SNPRINTF(error_msg, error_msg_size, "Failed to resolve hostname '%s' to IP address.", value_str);
311 }
312 return -1;
313 }
314}
315
320float validate_opt_float_non_negative(const char *value_str, char *error_msg, size_t error_msg_size) {
321 if (!value_str || strlen(value_str) == 0) {
322 if (error_msg) {
323 SAFE_SNPRINTF(error_msg, error_msg_size, "Value is required");
324 }
325 return -1.0f;
326 }
327
328 char *endptr;
329 float val = strtof(value_str, &endptr);
330 if (*endptr != '\0' || value_str == endptr) {
331 if (error_msg) {
332 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid float value '%s'. Must be a number.", value_str);
333 }
334 return -1.0f;
335 }
336 if (val < 0.0f) {
337 if (error_msg) {
338 SAFE_SNPRINTF(error_msg, error_msg_size, "Value must be non-negative (got %.2f)", val);
339 }
340 return -1.0f;
341 }
342 return val;
343}
344
349float validate_opt_volume(const char *value_str, char *error_msg, size_t error_msg_size) {
350 if (!value_str || strlen(value_str) == 0) {
351 if (error_msg) {
352 SAFE_SNPRINTF(error_msg, error_msg_size, "Volume value is required");
353 }
354 return -1.0f;
355 }
356
357 char *endptr;
358 float val = strtof(value_str, &endptr);
359 if (*endptr != '\0' || value_str == endptr) {
360 if (error_msg) {
361 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid volume value '%s'. Must be a number.", value_str);
362 }
363 return -1.0f;
364 }
365 if (val < 0.0f || val > 1.0f) {
366 if (error_msg) {
367 SAFE_SNPRINTF(error_msg, error_msg_size, "Volume must be between 0.0 and 1.0 (got %.2f)", val);
368 }
369 return -1.0f;
370 }
371 return val;
372}
373
378int validate_opt_max_clients(const char *value_str, char *error_msg, size_t error_msg_size) {
379 int result = validate_int_range(value_str, 1, 32, "Max clients", error_msg, error_msg_size);
380 return (result == INT_MIN) ? -1 : result;
381}
382
387int validate_opt_compression_level(const char *value_str, char *error_msg, size_t error_msg_size) {
388 int result = validate_int_range(value_str, 1, 9, "Compression level", error_msg, error_msg_size);
389 return (result == INT_MIN) ? -1 : result;
390}
391
396int validate_opt_fps(const char *value_str, char *error_msg, size_t error_msg_size) {
397 int result = validate_int_range(value_str, 1, 144, "FPS", error_msg, error_msg_size);
398 return (result == INT_MIN) ? -1 : result;
399}
400
409int validate_opt_reconnect(const char *value_str, char *error_msg, size_t error_msg_size) {
410 if (!value_str || strlen(value_str) == 0) {
411 if (error_msg) {
412 SAFE_SNPRINTF(error_msg, error_msg_size, "Reconnect value is required");
413 }
414 return INT_MIN;
415 }
416
417 // Check for string values first
418 if (platform_strcasecmp(value_str, "off") == 0) {
419 return 0; // No retries
420 }
421 if (platform_strcasecmp(value_str, "auto") == 0) {
422 return -1; // Unlimited retries
423 }
424
425 // Parse as integer
426 int val = strtoint_safe(value_str);
427 if (val == INT_MIN) {
428 if (error_msg) {
429 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid reconnect value '%s'. Use 'off', 'auto', or a number 0-999.",
430 value_str);
431 }
432 return INT_MIN;
433 }
434
435 // 0 means off, -1 means auto, 1-999 is valid range
436 if (val == 0) {
437 return 0; // No retries
438 }
439 if (val == -1) {
440 return -1; // Unlimited retries
441 }
442 if (val < 1 || val > 999) {
443 if (error_msg) {
444 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid reconnect count '%s'. Must be 'off', 'auto', or 1-999.",
445 value_str);
446 }
447 return INT_MIN;
448 }
449 return val;
450}
451
456int validate_opt_device_index(const char *value_str, char *error_msg, size_t error_msg_size) {
457 if (!value_str || strlen(value_str) == 0) {
458 if (error_msg) {
459 SAFE_SNPRINTF(error_msg, error_msg_size, "Device index value is required");
460 }
461 return INT_MIN;
462 }
463
464 int index = strtoint_safe(value_str);
465 if (index == INT_MIN) {
466 if (error_msg) {
467 SAFE_SNPRINTF(error_msg, error_msg_size,
468 "Invalid device index '%s'. Must be -1 (default) or a non-negative integer.", value_str);
469 }
470 return INT_MIN;
471 }
472
473 // -1 is valid (system default), otherwise must be >= 0
474 if (index < -1) {
475 if (error_msg) {
476 SAFE_SNPRINTF(error_msg, error_msg_size,
477 "Invalid device index '%d'. Must be -1 (default) or a non-negative integer.", index);
478 }
479 return INT_MIN;
480 }
481 return index;
482}
483
488int validate_opt_password(const char *value_str, char *error_msg, size_t error_msg_size) {
489 if (!value_str) {
490 if (error_msg) {
491 SAFE_SNPRINTF(error_msg, error_msg_size, "Password value is required");
492 }
493 return -1;
494 }
495
496 size_t len = strlen(value_str);
497 if (len < 8) {
498 if (error_msg) {
499 SAFE_SNPRINTF(error_msg, error_msg_size, "Password too short (%zu chars). Must be at least 8 characters.", len);
500 }
501 return -1;
502 }
503 if (len > 256) {
504 if (error_msg) {
505 SAFE_SNPRINTF(error_msg, error_msg_size, "Password too long (%zu chars). Must be at most 256 characters.", len);
506 }
507 return -1;
508 }
509
510 // Note: No need to check for embedded null bytes - strlen() already stopped at the first null,
511 // so by definition there are no null bytes within [0, len).
512
513 return 0;
514}
515
527int options_collect_identity_keys(options_t *opts, int argc, char *argv[]) {
528 if (!opts || !argv) {
529 log_error("options_collect_identity_keys: Invalid arguments");
530 return -1;
531 }
532
533 size_t key_count = 0;
534
535 // Scan argv for all --key or -K flags
536 for (int i = 1; i < argc && key_count < MAX_IDENTITY_KEYS; i++) {
537 const char *arg = argv[i];
538
539 // Check if this is a --key or -K flag
540 bool is_key_flag = false;
541 const char *key_value = NULL;
542
543 if (strcmp(arg, "--key") == 0 || strcmp(arg, "-K") == 0) {
544 // Next argument is the key path
545 if (i + 1 < argc) {
546 key_value = argv[i + 1];
547 is_key_flag = true;
548 i++; // Skip the value argument
549 }
550 } else if (strncmp(arg, "--key=", 6) == 0) {
551 // --key=value format
552 key_value = arg + 6;
553 is_key_flag = true;
554 }
555
556 if (is_key_flag && key_value && strlen(key_value) > 0) {
557 // Validate that local key files exist (skip for remote/virtual keys)
558 if (!is_remote_key_path(key_value)) {
559 struct stat st;
560 if (stat(key_value, &st) != 0) {
561 log_error("Key file not found: %s", key_value);
562 SET_ERRNO(ERROR_CRYPTO_KEY, "Key file not found: %s", key_value);
563 return -1;
564 }
565 // Check if it's a regular file (S_IFREG) - works on both Windows and Unix
566 if ((st.st_mode & S_IFMT) != S_IFREG) {
567 log_error("Key path is not a regular file: %s", key_value);
568 SET_ERRNO(ERROR_CRYPTO_KEY, "Key path is not a regular file: %s", key_value);
569 return -1;
570 }
571 }
572
573 // Store in identity_keys array
574 SAFE_STRNCPY(opts->identity_keys[key_count], key_value, OPTIONS_BUFF_SIZE);
575
576 // First key also goes into encrypt_key for backward compatibility
577 if (key_count == 0) {
578 SAFE_STRNCPY(opts->encrypt_key, key_value, OPTIONS_BUFF_SIZE);
579 }
580
581 key_count++;
582 log_debug("Collected identity key #%zu: %s", key_count, key_value);
583 }
584 }
585
586 opts->num_identity_keys = key_count;
587
588 if (key_count > 0) {
589 log_info("Collected %zu identity key(s) for multi-key support", key_count);
590 }
591
592 return (int)key_count;
593}
594
595bool is_remote_key_path(const char *key_path) {
596 if (!key_path || key_path[0] == '\0') {
597 return false;
598 }
599 return (strncmp(key_path, "github:", 7) == 0 || strncmp(key_path, "gitlab:", 7) == 0 ||
600 strncmp(key_path, "gpg:", 4) == 0 || strncmp(key_path, "http://", 7) == 0 ||
601 strncmp(key_path, "https://", 8) == 0);
602}
⚙️ Common definitions, error codes, macros, and types shared throughout the application
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#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.
@ ERROR_CRYPTO_KEY
Definition error_codes.h:97
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
float validate_opt_volume(const char *value_str, char *error_msg, size_t error_msg_size)
Validate volume value (0.0-1.0)
Definition validation.c:349
int validate_opt_positive_int(const char *value_str, char *error_msg, size_t error_msg_size)
Validate positive integer.
Definition validation.c:120
#define COLOR_MODE_16_COLOR
16-color mode (full name)
#define MAX_IDENTITY_KEYS
Maximum number of identity keys that can be loaded (for multi-key support)
#define COLOR_MODE_256_COLOR
256-color mode (full name)
#define COLOR_MODE_TRUECOLOR
24-bit truecolor mode
int validate_opt_render_mode(const char *value_str, char *error_msg, size_t error_msg_size)
Validate render mode string.
Definition validation.c:172
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
int validate_opt_color_mode(const char *value_str, char *error_msg, size_t error_msg_size)
Validate color mode string.
Definition validation.c:138
int strtoint_safe(const char *str)
Safely parse string to integer with validation.
int validate_opt_palette(const char *value_str, char *error_msg, size_t error_msg_size)
Validate palette type string.
Definition validation.c:200
int validate_opt_non_negative_int(const char *value_str, char *error_msg, size_t error_msg_size)
Validate non-negative integer.
Definition validation.c:129
int validate_opt_password(const char *value_str, char *error_msg, size_t error_msg_size)
Validate password (8-256 characters)
Definition validation.c:488
int validate_opt_fps(const char *value_str, char *error_msg, size_t error_msg_size)
Validate FPS value (1-144)
Definition validation.c:396
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
float validate_opt_float_non_negative(const char *value_str, char *error_msg, size_t error_msg_size)
Validate non-negative float value.
Definition validation.c:320
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
int validate_opt_reconnect(const char *value_str, char *error_msg, size_t error_msg_size)
Validate reconnect value.
Definition validation.c:409
#define COLOR_MODE_NONE
Monochrome mode.
int validate_opt_max_clients(const char *value_str, char *error_msg, size_t error_msg_size)
Validate max clients value (1-32)
Definition validation.c:378
int validate_opt_ip_address(const char *value_str, char *parsed_address, size_t address_size, bool is_client, char *error_msg, size_t error_msg_size)
Validate IP address or hostname.
Definition validation.c:268
bool is_remote_key_path(const char *key_path)
Check if a key path is a remote/virtual key (not a local file)
Definition validation.c:595
int validate_opt_port(const char *value_str, char *error_msg, size_t error_msg_size)
Validate port number (1-65535)
Definition validation.c:73
int options_collect_identity_keys(options_t *opts, int argc, char *argv[])
Collect multiple –key flags into identity_keys array.
Definition validation.c:527
int validate_opt_compression_level(const char *value_str, char *error_msg, size_t error_msg_size)
Validate compression level (1-9)
Definition validation.c:387
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
@ 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 platform_strcasecmp(const char *s1, const char *s2)
Case-insensitive string comparison.
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)
asciichat_error_t platform_resolve_hostname_to_ipv4(const char *hostname, char *ipv4_out, size_t ipv4_out_size)
Resolve hostname to IPv4 address.
int is_valid_ipv4(const char *ip)
Check if a string is a valid IPv4 address.
Definition ip.c:58
int is_valid_ipv6(const char *ip)
Check if a string is a valid IPv6 address.
Definition ip.c:105
int parse_ipv6_address(const char *input, char *output, size_t output_size)
Parse IPv6 address, removing brackets if present.
Definition ip.c:158
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
🌍 IP Address Parsing and Formatting Utilities
Application limits and constraints.
📝 Logging API with multiple log levels and terminal output control
Validation functions for options parsing.
ASCII Palette Management for Video-to-ASCII Conversion.
Consolidated options structure.
size_t num_identity_keys
Number of identity keys loaded (0 means single-key mode using encrypt_key)
char identity_keys[32][256]
All identity keys (populated when –key is used multiple times)
char encrypt_key[256]
SSH/GPG key file path (first –key flag, kept for compatibility)
🖥️ 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
#define LOG_DEV
Definition types.h:38
#define LOG_DEBUG
Definition types.h:39
#define LOG_FATAL
Definition types.h:43
#define LOG_ERROR
Definition types.h:42
#define LOG_WARN
Definition types.h:41
#define LOG_INFO
Definition types.h:40
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Parse port number (1-65535) from string.
🔍 Safe Parsing Utilities
bool validate_port_callback(const void *options_struct, char **error_msg)
Definition validation.c:97