ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
util/parsing.c
Go to the documentation of this file.
1
9#include <string.h>
10#include <errno.h>
11#include <limits.h>
12#include <stdlib.h>
14#include <ascii-chat/log/log.h>
15
16asciichat_error_t safe_parse_size_message(const char *message, unsigned int *width, unsigned int *height) {
17 if (!message || !width || !height) {
18 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for size message parsing");
19 }
20
21 // Check if message starts with "SIZE:"
22 if (strncmp(message, "SIZE:", 5) != 0) {
23 return SET_ERRNO(ERROR_INVALID_PARAM, "Message does not start with 'SIZE:'");
24 }
25
26 const char *ptr = message + 5; // Skip "SIZE:"
27 char *endptr;
28
29 // Parse first number (width)
30 errno = 0;
31 unsigned long w = strtoul(ptr, &endptr, 10);
32 if (errno != 0 || endptr == ptr || w > UINT_MAX || w == 0) {
33 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid width value in size message");
34 }
35
36 // Check for comma separator
37 if (*endptr != ',') {
38 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing comma separator in size message");
39 }
40 ptr = endptr + 1;
41
42 // Parse second number (height)
43 errno = 0;
44 unsigned long h = strtoul(ptr, &endptr, 10);
45 if (errno != 0 || endptr == ptr || h > UINT_MAX || h == 0) {
46 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid height value in size message");
47 }
48
49 // Should end with newline or null terminator
50 if (*endptr != '\n' && *endptr != '\0') {
51 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid format: size message should end with newline or null terminator");
52 }
53
54 // Additional bounds checking
55 if (w > 65535 || h > 65535) {
56 return SET_ERRNO(ERROR_INVALID_PARAM, "Size values too large (max 65535)");
57 }
58
59 *width = (unsigned int)w;
60 *height = (unsigned int)h;
61 return ASCIICHAT_OK;
62}
63
64asciichat_error_t safe_parse_audio_message(const char *message, unsigned int *num_samples) {
65 if (!message || !num_samples) {
66 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for audio message parsing");
67 }
68
69 // Check if message starts with "AUDIO:"
70 if (strncmp(message, "AUDIO:", 6) != 0) {
71 return SET_ERRNO(ERROR_INVALID_PARAM, "Message does not start with 'AUDIO:'");
72 }
73
74 const char *ptr = message + 6; // Skip "AUDIO:"
75 char *endptr;
76
77 // Parse number
78 errno = 0;
79 unsigned long samples = strtoul(ptr, &endptr, 10);
80 if (errno != 0 || endptr == ptr || samples > UINT_MAX || samples == 0) {
81 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid sample count value in audio message");
82 }
83
84 // Should end with newline or null terminator
85 if (*endptr != '\n' && *endptr != '\0') {
86 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid format: audio message should end with newline or null terminator");
87 }
88
89 *num_samples = (unsigned int)samples;
90 return ASCIICHAT_OK;
91}
92
93/* ============================================================================
94 * Integer Parsing Functions
95 * ============================================================================
96 */
97
98asciichat_error_t parse_long(const char *str, long *out_value, long min_value, long max_value) {
99 if (!str) {
100 return SET_ERRNO(ERROR_INVALID_PARAM, "String pointer is NULL");
101 }
102
103 if (!out_value) {
104 return SET_ERRNO(ERROR_INVALID_PARAM, "Output pointer is NULL");
105 }
106
107 if (*str == '\0') {
108 return SET_ERRNO(ERROR_INVALID_PARAM, "Empty string cannot be parsed as integer");
109 }
110
111 // Clear errno before calling strtol to detect errors
112 errno = 0;
113 char *endptr = NULL;
114 long value = strtol(str, &endptr, 10);
115
116 // Check for conversion errors
117 if (errno == ERANGE) {
118 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value out of range: %s", str);
119 }
120
121 // Check that entire string was consumed (no extra characters)
122 if (endptr == str || *endptr != '\0') {
123 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid integer format: %s", str);
124 }
125
126 // Check user-specified range
127 if (value < min_value) {
128 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value %ld is below minimum %ld", value, min_value);
129 }
130
131 if (value > max_value) {
132 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value %ld exceeds maximum %ld", value, max_value);
133 }
134
135 *out_value = value;
136 return ASCIICHAT_OK;
137}
138
139asciichat_error_t parse_ulong(const char *str, unsigned long *out_value, unsigned long min_value,
140 unsigned long max_value) {
141 if (!str) {
142 return SET_ERRNO(ERROR_INVALID_PARAM, "String pointer is NULL");
143 }
144
145 if (!out_value) {
146 return SET_ERRNO(ERROR_INVALID_PARAM, "Output pointer is NULL");
147 }
148
149 if (*str == '\0') {
150 return SET_ERRNO(ERROR_INVALID_PARAM, "Empty string cannot be parsed as integer");
151 }
152
153 // Clear errno before calling strtoul to detect errors
154 errno = 0;
155 char *endptr = NULL;
156 unsigned long value = strtoul(str, &endptr, 10);
157
158 // Check for conversion errors
159 if (errno == ERANGE) {
160 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value out of range: %s", str);
161 }
162
163 // Check that entire string was consumed (no extra characters)
164 if (endptr == str || *endptr != '\0') {
165 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid integer format: %s", str);
166 }
167
168 // Check user-specified range
169 if (value < min_value) {
170 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value %lu is below minimum %lu", value, min_value);
171 }
172
173 if (value > max_value) {
174 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value %lu exceeds maximum %lu", value, max_value);
175 }
176
177 *out_value = value;
178 return ASCIICHAT_OK;
179}
180
181asciichat_error_t parse_ulonglong(const char *str, unsigned long long *out_value, unsigned long long min_value,
182 unsigned long long max_value) {
183 if (!str) {
184 return SET_ERRNO(ERROR_INVALID_PARAM, "String pointer is NULL");
185 }
186
187 if (!out_value) {
188 return SET_ERRNO(ERROR_INVALID_PARAM, "Output pointer is NULL");
189 }
190
191 if (*str == '\0') {
192 return SET_ERRNO(ERROR_INVALID_PARAM, "Empty string cannot be parsed as integer");
193 }
194
195 // Clear errno before calling strtoull to detect errors
196 errno = 0;
197 char *endptr = NULL;
198 unsigned long long value = strtoull(str, &endptr, 10);
199
200 // Check for conversion errors
201 if (errno == ERANGE) {
202 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value out of range: %s", str);
203 }
204
205 // Check that entire string was consumed (no extra characters)
206 if (endptr == str || *endptr != '\0') {
207 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid integer format: %s", str);
208 }
209
210 // Check user-specified range
211 if (value < min_value) {
212 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value too small (below minimum)");
213 }
214
215 if (value > max_value) {
216 return SET_ERRNO(ERROR_INVALID_PARAM, "Integer value too large (exceeds maximum)");
217 }
218
219 *out_value = value;
220 return ASCIICHAT_OK;
221}
222
229static pcre2_singleton_t *g_port_regex = NULL;
230
244static pcre2_code *port_regex_get(void) {
245 if (g_port_regex == NULL) {
246 const char *port_pattern = "^(0|[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$";
247 g_port_regex = asciichat_pcre2_singleton_compile(port_pattern, 0);
248 }
249 return asciichat_pcre2_singleton_get_code(g_port_regex);
250}
251
252asciichat_error_t parse_port(const char *str, uint16_t *out_port) {
253 if (!str) {
254 return SET_ERRNO(ERROR_INVALID_PARAM, "String pointer is NULL");
255 }
256
257 if (!out_port) {
258 return SET_ERRNO(ERROR_INVALID_PARAM, "Output pointer is NULL");
259 }
260
261 if (*str == '\0') {
262 return SET_ERRNO(ERROR_INVALID_PARAM, "Empty string cannot be parsed as port");
263 }
264
265 pcre2_code *regex = port_regex_get();
266 if (!regex) {
267 /* Fallback: use original parse_ulong method if PCRE2 not available */
268 unsigned long port_ulong;
269 asciichat_error_t err = parse_ulong(str, &port_ulong, 0, 65535);
270 if (err != ASCIICHAT_OK) {
271 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port number: %s (must be 0-65535)", str);
272 }
273 *out_port = (uint16_t)port_ulong;
274 return ASCIICHAT_OK;
275 }
276
277 /* Validate port using PCRE2 regex */
278 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
279 if (!match_data) {
280 log_error("Failed to allocate match data for port validation");
281 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port number: %s (must be 0-65535)", str);
282 }
283
284 int match_result = asciichat_pcre2_match(regex, (PCRE2_SPTR8)str, strlen(str), 0, 0, match_data, NULL);
285 pcre2_match_data_free(match_data);
286
287 if (match_result < 0) {
288 /* Format validation failed */
289 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port number: %s (must be 0-65535)", str);
290 }
291
292 /* Extract matched port string and convert to uint16_t */
293 char *endptr = NULL;
294 unsigned long port_value = strtoul(str, &endptr, 10);
295
296 if (port_value < 0 || port_value > 65535) {
297 return SET_ERRNO(ERROR_INVALID_PARAM, "Port out of range: %lu", port_value);
298 }
299
300 *out_port = (uint16_t)port_value;
301 return ASCIICHAT_OK;
302}
303
304asciichat_error_t parse_int32(const char *str, int32_t *out_value, int32_t min_value, int32_t max_value) {
305 if (!str) {
306 return SET_ERRNO(ERROR_INVALID_PARAM, "String pointer is NULL");
307 }
308
309 if (!out_value) {
310 return SET_ERRNO(ERROR_INVALID_PARAM, "Output pointer is NULL");
311 }
312
313 long value;
314 asciichat_error_t err = parse_long(str, &value, (long)min_value, (long)max_value);
315 if (err != ASCIICHAT_OK) {
316 return err;
317 }
318
319 *out_value = (int32_t)value;
320 return ASCIICHAT_OK;
321}
322
323asciichat_error_t parse_uint32(const char *str, uint32_t *out_value, uint32_t min_value, uint32_t max_value) {
324 if (!str) {
325 return SET_ERRNO(ERROR_INVALID_PARAM, "String pointer is NULL");
326 }
327
328 if (!out_value) {
329 return SET_ERRNO(ERROR_INVALID_PARAM, "Output pointer is NULL");
330 }
331
332 unsigned long value;
333 asciichat_error_t err = parse_ulong(str, &value, (unsigned long)min_value, (unsigned long)max_value);
334 if (err != ASCIICHAT_OK) {
335 return err;
336 }
337
338 *out_value = (uint32_t)value;
339 return ASCIICHAT_OK;
340}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
Cross-platform error handling utilities.
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#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
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define ERANGE
int errno
asciichat_error_t safe_parse_size_message(const char *message, unsigned int *width, unsigned int *height)
Parse SIZE message format.
asciichat_error_t safe_parse_audio_message(const char *message, unsigned int *num_samples)
Parse AUDIO message format.
Application limits and constraints.
📝 Logging API with multiple log levels and terminal output control
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:98
int asciichat_pcre2_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, PCRE2_SIZE startoffset, uint32_t options, pcre2_match_data *match_data, pcre2_match_context *context)
Definition pcre2.c:399
pcre2_singleton_t * asciichat_pcre2_singleton_compile(const char *pattern, uint32_t flags)
Compile and cache a PCRE2 regex pattern with thread-safe singleton semantics.
Definition pcre2.c:47
🔍 Centralized PCRE2 singleton pattern for efficient regex compilation
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:22
asciichat_error_t parse_ulonglong(const char *str, unsigned long long *out_value, unsigned long long min_value, unsigned long long max_value)
Parse unsigned long long integer with range validation.
asciichat_error_t parse_uint32(const char *str, uint32_t *out_value, uint32_t min_value, uint32_t max_value)
Parse unsigned 32-bit integer with range validation.
asciichat_error_t parse_ulong(const char *str, unsigned long *out_value, unsigned long min_value, unsigned long max_value)
Parse unsigned long integer with range validation.
asciichat_error_t parse_long(const char *str, long *out_value, long min_value, long max_value)
Parse signed long integer with range validation.
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