ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
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
13#include "common.h"
14#include "log/logging.h"
15#include "platform/terminal.h"
16#include "platform/util.h"
17#include "util/ip.h"
18#include "util/parsing.h"
19#include "video/palette.h"
20#include "options/options.h"
21
26int validate_opt_port(const char *value_str, char *error_msg, size_t error_msg_size) {
27 if (!value_str || strlen(value_str) == 0) {
28 if (error_msg) {
29 SAFE_SNPRINTF(error_msg, error_msg_size, "Port value is required");
30 }
31 return -1;
32 }
33
34 // Use safe integer parsing with range validation
35 uint16_t port_num;
36 if (parse_port(value_str, &port_num) != ASCIICHAT_OK) {
37 if (error_msg) {
38 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid port value '%s'. Port must be a number between 1 and 65535.",
39 value_str);
40 }
41 return -1;
42 }
43 return 0;
44}
45
50int validate_opt_positive_int(const char *value_str, char *error_msg, size_t error_msg_size) {
51 if (!value_str || strlen(value_str) == 0) {
52 if (error_msg) {
53 SAFE_SNPRINTF(error_msg, error_msg_size, "Value is required");
54 }
55 return -1;
56 }
57
58 int val = strtoint_safe(value_str);
59 if (val == INT_MIN || val <= 0) {
60 if (error_msg) {
61 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid value '%s'. Must be a positive integer.", value_str);
62 }
63 return -1;
64 }
65 return val;
66}
67
72int validate_opt_non_negative_int(const char *value_str, char *error_msg, size_t error_msg_size) {
73 if (!value_str || strlen(value_str) == 0) {
74 if (error_msg) {
75 SAFE_SNPRINTF(error_msg, error_msg_size, "Value is required");
76 }
77 return -1;
78 }
79
80 int val = strtoint_safe(value_str);
81 if (val == INT_MIN || val < 0) {
82 if (error_msg) {
83 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid value '%s'. Must be a non-negative integer.", value_str);
84 }
85 return -1;
86 }
87 return val;
88}
89
94int validate_opt_color_mode(const char *value_str, char *error_msg, size_t error_msg_size) {
95 if (!value_str) {
96 if (error_msg) {
97 SAFE_SNPRINTF(error_msg, error_msg_size, "Color mode value is required");
98 }
99 return -1;
100 }
101
102 if (strcmp(value_str, "auto") == 0) {
103 return COLOR_MODE_AUTO;
104 }
105 if (strcmp(value_str, "none") == 0 || strcmp(value_str, "mono") == 0) {
106 return COLOR_MODE_NONE;
107 }
108 if (strcmp(value_str, "16") == 0 || strcmp(value_str, "16color") == 0) {
109 return COLOR_MODE_16_COLOR;
110 }
111 if (strcmp(value_str, "256") == 0 || strcmp(value_str, "256color") == 0) {
113 }
114 if (strcmp(value_str, "truecolor") == 0 || strcmp(value_str, "24bit") == 0) {
116 }
117 if (error_msg) {
118 SAFE_SNPRINTF(error_msg, error_msg_size,
119 "Invalid color mode '%s'. Valid modes: auto, none, mono, 16, 256, truecolor", value_str);
120 }
121 return -1;
122}
123
128int validate_opt_render_mode(const char *value_str, char *error_msg, size_t error_msg_size) {
129 if (!value_str) {
130 if (error_msg) {
131 SAFE_SNPRINTF(error_msg, error_msg_size, "Render mode value is required");
132 }
133 return -1;
134 }
135
136 if (strcmp(value_str, "foreground") == 0 || strcmp(value_str, "fg") == 0) {
138 }
139 if (strcmp(value_str, "background") == 0 || strcmp(value_str, "bg") == 0) {
141 }
142 if (strcmp(value_str, "half-block") == 0 || strcmp(value_str, "halfblock") == 0) {
144 }
145 if (error_msg) {
146 SAFE_SNPRINTF(error_msg, error_msg_size,
147 "Invalid render mode '%s'. Valid modes: foreground, background, half-block", value_str);
148 }
149 return -1;
150}
151
156int validate_opt_palette(const char *value_str, char *error_msg, size_t error_msg_size) {
157 if (!value_str) {
158 if (error_msg) {
159 SAFE_SNPRINTF(error_msg, error_msg_size, "Palette value is required");
160 }
161 return -1;
162 }
163
164 if (strcmp(value_str, "standard") == 0) {
165 return PALETTE_STANDARD;
166 } else if (strcmp(value_str, "blocks") == 0) {
167 return PALETTE_BLOCKS;
168 } else if (strcmp(value_str, "digital") == 0) {
169 return PALETTE_DIGITAL;
170 } else if (strcmp(value_str, "minimal") == 0) {
171 return PALETTE_MINIMAL;
172 } else if (strcmp(value_str, "cool") == 0) {
173 return PALETTE_COOL;
174 } else if (strcmp(value_str, "custom") == 0) {
175 return PALETTE_CUSTOM;
176 } else {
177 if (error_msg) {
178 SAFE_SNPRINTF(error_msg, error_msg_size,
179 "Invalid palette '%s'. Valid palettes: standard, blocks, digital, minimal, cool, custom",
180 value_str);
181 }
182 return -1;
183 }
184}
185
190int validate_opt_log_level(const char *value_str, char *error_msg, size_t error_msg_size) {
191 if (!value_str) {
192 if (error_msg) {
193 SAFE_SNPRINTF(error_msg, error_msg_size, "Log level value is required");
194 }
195 return -1;
196 }
197
198 if (platform_strcasecmp(value_str, "dev") == 0) {
199 return LOG_DEV;
200 } else if (platform_strcasecmp(value_str, "debug") == 0) {
201 return LOG_DEBUG;
202 } else if (platform_strcasecmp(value_str, "info") == 0) {
203 return LOG_INFO;
204 } else if (platform_strcasecmp(value_str, "warn") == 0) {
205 return LOG_WARN;
206 } else if (platform_strcasecmp(value_str, "error") == 0) {
207 return LOG_ERROR;
208 } else if (platform_strcasecmp(value_str, "fatal") == 0) {
209 return LOG_FATAL;
210 } else {
211 if (error_msg) {
212 SAFE_SNPRINTF(error_msg, error_msg_size,
213 "Invalid log level '%s'. Valid levels: dev, debug, info, warn, error, fatal", value_str);
214 }
215 return -1;
216 }
217}
218
224int validate_opt_ip_address(const char *value_str, char *parsed_address, size_t address_size, bool is_client,
225 char *error_msg, size_t error_msg_size) {
226 (void)is_client; // Parameter not used but kept for API consistency
227 if (!value_str || strlen(value_str) == 0) {
228 if (error_msg) {
229 SAFE_SNPRINTF(error_msg, error_msg_size, "Address value is required");
230 }
231 return -1;
232 }
233
234 // Parse IPv6 address (remove brackets if present)
235 char parsed_addr[OPTIONS_BUFF_SIZE];
236 if (parse_ipv6_address(value_str, parsed_addr, sizeof(parsed_addr)) == 0) {
237 value_str = parsed_addr;
238 }
239
240 // Check if it's a valid IPv4 address
241 if (is_valid_ipv4(value_str)) {
242 SAFE_SNPRINTF(parsed_address, address_size, "%s", value_str);
243 return 0;
244 }
245 // Check if it's a valid IPv6 address
246 if (is_valid_ipv6(value_str)) {
247 SAFE_SNPRINTF(parsed_address, address_size, "%s", value_str);
248 return 0;
249 }
250 // Check if it looks like an invalid IP (has dots but not valid IPv4 format)
251 if (strchr(value_str, '.') != NULL) {
252 if (error_msg) {
253 SAFE_SNPRINTF(error_msg, error_msg_size,
254 "Invalid IP address format '%s'. IPv4 addresses must have exactly 4 octets.", value_str);
255 }
256 return -1;
257 }
258
259 // Otherwise, try to resolve as hostname
260 char resolved_ip[OPTIONS_BUFF_SIZE];
261 if (platform_resolve_hostname_to_ipv4(value_str, resolved_ip, sizeof(resolved_ip)) == 0) {
262 SAFE_SNPRINTF(parsed_address, address_size, "%s", resolved_ip);
263 return 0;
264 } else {
265 if (error_msg) {
266 SAFE_SNPRINTF(error_msg, error_msg_size, "Failed to resolve hostname '%s' to IP address.", value_str);
267 }
268 return -1;
269 }
270}
271
276float validate_opt_float_non_negative(const char *value_str, char *error_msg, size_t error_msg_size) {
277 if (!value_str || strlen(value_str) == 0) {
278 if (error_msg) {
279 SAFE_SNPRINTF(error_msg, error_msg_size, "Value is required");
280 }
281 return -1.0f;
282 }
283
284 char *endptr;
285 float val = strtof(value_str, &endptr);
286 if (*endptr != '\0' || value_str == endptr) {
287 if (error_msg) {
288 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid float value '%s'. Must be a number.", value_str);
289 }
290 return -1.0f;
291 }
292 if (val < 0.0f) {
293 if (error_msg) {
294 SAFE_SNPRINTF(error_msg, error_msg_size, "Value must be non-negative (got %.2f)", val);
295 }
296 return -1.0f;
297 }
298 return val;
299}
300
305int validate_opt_max_clients(const char *value_str, char *error_msg, size_t error_msg_size) {
306 if (!value_str || strlen(value_str) == 0) {
307 if (error_msg) {
308 SAFE_SNPRINTF(error_msg, error_msg_size, "Max clients value is required");
309 }
310 return -1;
311 }
312
313 int max = strtoint_safe(value_str);
314 if (max == INT_MIN || max < 1 || max > 32) {
315 if (error_msg) {
316 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid max clients '%s'. Must be between 1 and 32.", value_str);
317 }
318 return -1;
319 }
320 return max;
321}
322
327int validate_opt_compression_level(const char *value_str, char *error_msg, size_t error_msg_size) {
328 if (!value_str || strlen(value_str) == 0) {
329 if (error_msg) {
330 SAFE_SNPRINTF(error_msg, error_msg_size, "Compression level value is required");
331 }
332 return -1;
333 }
334
335 int level = strtoint_safe(value_str);
336 if (level == INT_MIN || level < 1 || level > 9) {
337 if (error_msg) {
338 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid compression level '%s'. Must be between 1 and 9.", value_str);
339 }
340 return -1;
341 }
342 return level;
343}
344
349int validate_opt_fps(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, "FPS value is required");
353 }
354 return -1;
355 }
356
357 int fps_val = strtoint_safe(value_str);
358 if (fps_val == INT_MIN || fps_val < 1 || fps_val > 144) {
359 if (error_msg) {
360 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid FPS value '%s'. FPS must be between 1 and 144.", value_str);
361 }
362 return -1;
363 }
364 return fps_val;
365}
366
375int validate_opt_reconnect(const char *value_str, char *error_msg, size_t error_msg_size) {
376 if (!value_str || strlen(value_str) == 0) {
377 if (error_msg) {
378 SAFE_SNPRINTF(error_msg, error_msg_size, "Reconnect value is required");
379 }
380 return INT_MIN;
381 }
382
383 // Check for string values first
384 if (platform_strcasecmp(value_str, "off") == 0) {
385 return 0; // No retries
386 }
387 if (platform_strcasecmp(value_str, "auto") == 0) {
388 return -1; // Unlimited retries
389 }
390
391 // Parse as integer
392 int val = strtoint_safe(value_str);
393 if (val == INT_MIN) {
394 if (error_msg) {
395 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid reconnect value '%s'. Use 'off', 'auto', or a number 0-999.",
396 value_str);
397 }
398 return INT_MIN;
399 }
400
401 // 0 means off, -1 means auto, 1-999 is valid range
402 if (val == 0) {
403 return 0; // No retries
404 }
405 if (val == -1) {
406 return -1; // Unlimited retries
407 }
408 if (val < 1 || val > 999) {
409 if (error_msg) {
410 SAFE_SNPRINTF(error_msg, error_msg_size, "Invalid reconnect count '%s'. Must be 'off', 'auto', or 1-999.",
411 value_str);
412 }
413 return INT_MIN;
414 }
415 return val;
416}
417
422int validate_opt_device_index(const char *value_str, char *error_msg, size_t error_msg_size) {
423 if (!value_str || strlen(value_str) == 0) {
424 if (error_msg) {
425 SAFE_SNPRINTF(error_msg, error_msg_size, "Device index value is required");
426 }
427 return INT_MIN;
428 }
429
430 int index = strtoint_safe(value_str);
431 if (index == INT_MIN) {
432 if (error_msg) {
433 SAFE_SNPRINTF(error_msg, error_msg_size,
434 "Invalid device index '%s'. Must be -1 (default) or a non-negative integer.", value_str);
435 }
436 return INT_MIN;
437 }
438
439 // -1 is valid (system default), otherwise must be >= 0
440 if (index < -1) {
441 if (error_msg) {
442 SAFE_SNPRINTF(error_msg, error_msg_size,
443 "Invalid device index '%d'. Must be -1 (default) or a non-negative integer.", index);
444 }
445 return INT_MIN;
446 }
447 return index;
448}
449
454int validate_opt_password(const char *value_str, char *error_msg, size_t error_msg_size) {
455 if (!value_str) {
456 if (error_msg) {
457 SAFE_SNPRINTF(error_msg, error_msg_size, "Password value is required");
458 }
459 return -1;
460 }
461
462 size_t len = strlen(value_str);
463 if (len < 8) {
464 if (error_msg) {
465 SAFE_SNPRINTF(error_msg, error_msg_size, "Password too short (%zu chars). Must be at least 8 characters.", len);
466 }
467 return -1;
468 }
469 if (len > 256) {
470 if (error_msg) {
471 SAFE_SNPRINTF(error_msg, error_msg_size, "Password too long (%zu chars). Must be at most 256 characters.", len);
472 }
473 return -1;
474 }
475
476 // Note: No need to check for embedded null bytes - strlen() already stopped at the first null,
477 // so by definition there are no null bytes within [0, len).
478
479 return 0;
480}
unsigned short uint16_t
Definition common.h:57
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
@ ASCIICHAT_OK
Definition error_codes.h:48
@ LOG_ERROR
Definition log/logging.h:64
@ LOG_INFO
Definition log/logging.h:62
@ LOG_DEBUG
Definition log/logging.h:61
@ LOG_FATAL
Definition log/logging.h:65
@ LOG_WARN
Definition log/logging.h:63
@ LOG_DEV
Definition log/logging.h:60
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:422
#define COLOR_MODE_16_COLOR
16-color mode (full name)
Definition options.h:159
#define COLOR_MODE_256_COLOR
256-color mode (full name)
Definition options.h:161
#define COLOR_MODE_TRUECOLOR
24-bit truecolor mode
Definition options.h:162
int validate_opt_reconnect(const char *value_str, char *error_msg, size_t error_msg_size)
Validate reconnect value.
Definition validation.c:375
int validate_opt_color_mode(const char *value_str, char *error_msg, size_t error_msg_size)
Validate color mode string.
Definition validation.c:94
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
Definition options.h:156
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:327
int strtoint_safe(const char *str)
Safely parse string to integer with validation.
int validate_opt_fps(const char *value_str, char *error_msg, size_t error_msg_size)
Validate FPS value (1-144)
Definition validation.c:349
int validate_opt_palette(const char *value_str, char *error_msg, size_t error_msg_size)
Validate palette type string.
Definition validation.c:156
int validate_opt_positive_int(const char *value_str, char *error_msg, size_t error_msg_size)
Validate positive integer.
Definition validation.c:50
int validate_opt_password(const char *value_str, char *error_msg, size_t error_msg_size)
Validate password (8-256 characters)
Definition validation.c:454
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
Definition options.h:176
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:276
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:72
#define COLOR_MODE_NONE
Monochrome mode.
Definition options.h:157
int validate_opt_render_mode(const char *value_str, char *error_msg, size_t error_msg_size)
Validate render mode string.
Definition validation.c:128
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:305
int validate_opt_port(const char *value_str, char *error_msg, size_t error_msg_size)
Validate port number (1-65535)
Definition validation.c:26
int validate_opt_log_level(const char *value_str, char *error_msg, size_t error_msg_size)
Validate log level string.
Definition validation.c:190
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:224
@ PALETTE_BLOCKS
Unicode block characters: " ░░▒▒▓▓██".
Definition palette.h:88
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:96
@ PALETTE_COOL
Ascending blocks: " ▁▂▃▄▅▆▇█".
Definition palette.h:94
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
Definition palette.h:86
@ PALETTE_DIGITAL
Digital/glitch aesthetic: " -=≡≣▰▱◼".
Definition palette.h:90
@ PALETTE_MINIMAL
Simple ASCII: " .-+*#".
Definition palette.h:92
int platform_strcasecmp(const char *s1, const char *s2)
Case-insensitive string comparison.
asciichat_error_t platform_resolve_hostname_to_ipv4(const char *hostname, char *ipv4_out, size_t ipv4_out_size)
Resolve hostname to IPv4 address.
@ RENDER_MODE_FOREGROUND
Foreground colors only (text color)
Definition terminal.h:469
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:471
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:473
int is_valid_ipv4(const char *ip)
Check if a string is a valid IPv4 address.
Definition ip.c:22
int is_valid_ipv6(const char *ip)
Check if a string is a valid IPv6 address.
Definition ip.c:77
int parse_ipv6_address(const char *input, char *output, size_t output_size)
Parse IPv6 address, removing brackets if present.
Definition ip.c:167
🌍 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.
⚙️ Command-line options parsing and configuration management for ascii-chat
ASCII Palette Management for Video-to-ASCII Conversion.
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Parse port number (1-65535) from string.
Definition parsing.c:221
🔍 Safe Parsing Utilities
Public platform utility API for string, memory, and file operations.
🖥️ Cross-platform terminal interface for ascii-chat
Common SIMD utilities and structures.