ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
log/named.c
Go to the documentation of this file.
1
15
16/* Storage capacities in elements; text buffers include the terminating NUL. */
17#define LOG_NAMED_ID_BUFFER_SIZE 64
18#define LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE 512
19#define LOG_NAMED_ID_EXTENDED_BUFFER_SIZE 128
20#define LOG_NAMED_TYPE_BUFFER_SIZE 64
21#define LOG_NAMED_KW_BUFFER_SIZE 32
22#define LOG_NAMED_TEMP_BUFFER_SIZE 512
23#define LOG_NAMED_TYPE_SHORT_BUFFER_SIZE 32
24#define LOG_NAMED_TYPE_EXTENDED_BUFFER_SIZE 256
25#define LOG_NAMED_NAME_BUFFER_SIZE 256
26#define LOG_NAMED_ADDR_BUFFER_SIZE 128
27
28#include <string.h>
29#include <stdio.h>
30#include <ctype.h>
31#include <stdlib.h>
34#include "ascii-chat/log/log.h"
35
36/* Use a thread-local buffer to avoid allocations in the hot path */
37#define NAMED_FORMAT_BUFFER_SIZE (4096)
38
48static bool is_type_in_registry(const char *type_str, size_t type_len) {
49 return named_registry_has_type(type_str, type_len);
50}
51
63
75static const char *find_generic_type_prefix(const char *start, const char *p, const char **out_type,
76 size_t *out_type_len) {
77 if (p == start) {
78 *out_type = NULL;
79 *out_type_len = 0;
80 return p;
81 }
82
83 const char *word_start = p - 1;
84 /* Skip back over whitespace, '=', ':' */
85 while (word_start > start &&
86 (*word_start == ' ' || *word_start == '\t' || *word_start == '=' || *word_start == ':')) {
87 word_start--;
88 }
89
90 /* Check if we have a digit (we're in a decimal number) */
91 if (!isdigit(*word_start)) {
92 /* Not a digit, so look for type keywords before us */
93 const char *check = word_start;
94 while (check > start && (isalnum(*check) || *check == '_')) {
95 check--;
96 }
97 check++;
98
99 const char *type_start = check;
100 size_t type_len = word_start - type_start + 1;
101
102 /* Check for known type keywords */
103 static const struct {
104 const char *type;
105 size_t len;
106 } known_types[] = {
107 {"socket", 6}, {"client", 6}, {"connection", 10}, {"fd", 2}, {"file", 4}, {"descriptor", 11},
108 {"thread", 6}, {"decoder", 7}, {"encoder", 7}, {"context", 7}, {"handler", 7},
109 };
110
111 for (size_t i = 0; i < sizeof(known_types) / sizeof(known_types[0]); i++) {
112 if (type_len == known_types[i].len && strncmp(type_start, known_types[i].type, type_len) == 0) {
113 *out_type = type_start;
114 *out_type_len = type_len;
115 return type_start;
116 }
117 }
118 }
119
120 *out_type = NULL;
121 *out_type_len = 0;
122 return p;
123}
124
136static const char *find_hex_type_prefix_start(const char *start, const char *p, const char **out_type,
137 size_t *out_type_len) {
138 *out_type = NULL;
139 *out_type_len = 0;
140
141 if (p <= start)
142 return p;
143
144 /* Check for '=' immediately before hex address */
145 const char *check = p - 1;
146 if (check < start || *check != '=')
147 return p;
148
149 /* Walk back over the keyword before '=' */
150 const char *eq_pos = check;
151 check = eq_pos - 1;
152 if (check < start)
153 return p;
154
155 /* Find start of keyword */
156 while (check > start && (isalnum(*check) || *check == '_')) {
157 check--;
158 }
159 if (!(isalnum(*check) || *check == '_')) {
160 check++;
161 }
162
163 size_t word_len = eq_pos - check;
164 if (word_len == 0)
165 return p;
166
167 /* Known pointer-type keywords that appear with hex values */
168 static const struct {
169 const char *type;
170 size_t len;
171 } known_types[] = {
172 {"transport", 9}, {"client", 6}, {"context", 7}, {"crypto", 6}, {"websocket", 9}, {"mutex", 5},
173 {"rwlock", 6}, {"cond", 4}, {"thread", 6}, {"socket", 6}, {"connection", 10},
174 };
175
176 for (size_t i = 0; i < sizeof(known_types) / sizeof(known_types[0]); i++) {
177 if (word_len == known_types[i].len && strncmp(check, known_types[i].type, word_len) == 0) {
178 *out_type = check;
179 *out_type_len = word_len;
180 return check;
181 }
182 }
183
184 return p;
185}
186
196static const char *find_fd_prefix_start(const char *start, const char *p) {
197 if (p == start)
198 return p;
199
200 const char *initial_pos = p - 1;
201 const char *word_start = initial_pos;
202 /* Skip back over whitespace, '=', ':' */
203 while (word_start > start &&
204 (*word_start == ' ' || *word_start == '\t' || *word_start == '=' || *word_start == ':')) {
205 word_start--;
206 }
207
208 /* Require at least one separator between keyword and digit.
209 * Without this, "fd7a" in IPv6 addresses like "fd7a:115c:..." would
210 * be misinterpreted as fd prefix + digit 7. */
211 if (word_start == initial_pos)
212 return p;
213
214 /* Check if we have a digit (we're in a decimal number) */
215 if (!isdigit(*word_start)) {
216 /* Not a digit, so look for fd/file/descriptor/socket/sockfd keywords before us */
217 const char *check = word_start;
218 while (check > start && (isalnum(*check) || *check == '_')) {
219 check--;
220 }
221 check++; /* Move back to start of word */
222
223 size_t word_len = word_start - check + 1;
224 if (word_len >= 2) {
225 if ((word_len == 2 && strncasecmp(check, "fd", 2) == 0) ||
226 (word_len == 4 && strncasecmp(check, "file", 4) == 0) ||
227 (word_len == 6 && strncasecmp(check, "socket", 6) == 0) ||
228 (word_len == 6 && strncasecmp(check, "sockfd", 6) == 0) ||
229 (word_len == 10 && strncasecmp(check, "descriptor", 10) == 0)) {
230 return check; /* Return start of keyword */
231 }
232 }
233 }
234
235 return p; /* No prefix found, return original position */
236}
237
247static const char *find_packet_type_prefix_start(const char *start, const char *p) {
248 if (p == start)
249 return p;
250
251 const char *initial_pos = p - 1;
252 const char *word_start = initial_pos;
253 /* Skip back over whitespace, '=', ':', '(' */
254 while (word_start > start && (*word_start == ' ' || *word_start == '\t' || *word_start == '=' || *word_start == ':' ||
255 *word_start == '(')) {
256 word_start--;
257 }
258
259 /* Require at least one separator between keyword and digit */
260 if (word_start == initial_pos)
261 return p;
262
263 /* Check if we have a digit (we're in a decimal number) */
264 if (!isdigit(*word_start)) {
265 /* Not a digit, so look for packet type keywords before us */
266 const char *check = word_start;
267 while (check > start && (isalnum(*check) || *check == '_')) {
268 check--;
269 }
270 check++; /* Move back to start of word */
271
272 size_t word_len = word_start - check + 1;
273 /* Check for "type" keyword (part of "packet type") */
274 if (word_len >= 4 && strncasecmp(check, "type", 4) == 0) {
275 /* Look for "packet" before "type" */
276 const char *packet_check = check - 1;
277 while (packet_check > start && (*packet_check == ' ' || *packet_check == '\t')) {
278 packet_check--;
279 }
280 if (packet_check > start) {
281 const char *packet_start = packet_check;
282 while (packet_start > start && (isalnum(*packet_start) || *packet_start == '_')) {
283 packet_start--;
284 }
285 packet_start++;
286 size_t packet_len = packet_check - packet_start + 1;
287 if (packet_len == 6 && strncasecmp(packet_start, "packet", 6) == 0) {
288 return packet_start; /* Return start of "packet" in "packet type" */
289 }
290 }
291 }
292 /* Check for "packet" keyword alone */
293 if (word_len >= 6 && strncasecmp(check, "packet", 6) == 0) {
294 return check; /* Return start of "packet" */
295 }
296 }
297
298 return p; /* No prefix found, return original position */
299}
300
306static bool has_packet_type_prefix(const char *start, const char *p) {
307 return find_packet_type_prefix_start(start, p) != p;
308}
309
315static bool has_fd_prefix(const char *start, const char *p) {
316 return find_fd_prefix_start(start, p) != p;
317}
318
319/* ============================================================================
320 * Granular Helper Functions for Message Transformation
321 * ============================================================================
322 */
323
324/* --- Decimal parsing --- */
325static int parse_decimal_digits(const char *start, const char **end) {
326 int value = 0;
327 int count = 0;
328 const char *p = start;
329 while (*p && isdigit(*p) && count < 6) {
330 value = value * 10 + (*p - '0');
331 p++;
332 count++;
333 }
334 *end = p;
335 return value;
336}
337
338static bool write_formatted_packet_type(int pkt_value, const char *name, char *output, size_t output_size,
339 size_t *out_pos) {
340 named_metadata_t metadata;
341 const char *fmt_spec = named_packet_metadata_copy(pkt_value, &metadata) ? metadata.format : "%d";
342
343 char id_buffer[LOG_NAMED_ID_BUFFER_SIZE];
344 int id_written = snprintf(id_buffer, sizeof(id_buffer), fmt_spec, pkt_value);
345 if (id_written <= 0 || id_written >= (int)sizeof(id_buffer)) {
346 return false;
347 }
348
349 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
350 int temp_written = snprintf(temp_output, sizeof(temp_output), "packet_type/%s (pkt_type=%s)", name, id_buffer);
351 if (temp_written <= 0 || (size_t)temp_written >= sizeof(temp_output)) {
352 return false;
353 }
354
355 if (*out_pos + temp_written >= output_size - 1) {
356 return false;
357 }
358
359 memcpy(output + *out_pos, temp_output, temp_written);
360 *out_pos += temp_written;
361 return true;
362}
363
364static bool write_formatted_fd(int fd_value, const char *name, char *output, size_t output_size, size_t *out_pos) {
365 named_metadata_t metadata;
366 const char *fmt_spec = named_fd_metadata_copy(fd_value, &metadata) ? metadata.format : "%d";
367
368 char id_buffer[LOG_NAMED_ID_BUFFER_SIZE];
369 int id_written = snprintf(id_buffer, sizeof(id_buffer), fmt_spec, fd_value);
370 if (id_written <= 0 || id_written >= (int)sizeof(id_buffer)) {
371 return false;
372 }
373
374 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
375 int temp_written = snprintf(temp_output, sizeof(temp_output), "fd/%s (fd=%s)", name, id_buffer);
376 if (temp_written <= 0 || (size_t)temp_written >= sizeof(temp_output)) {
377 return false;
378 }
379
380 if (*out_pos + temp_written >= output_size - 1) {
381 return false;
382 }
383
384 memcpy(output + *out_pos, temp_output, temp_written);
385 *out_pos += temp_written;
386 return true;
387}
388
389static bool copy_unformatted_decimal(const char *int_start, const char *int_end, char *output, size_t output_size,
390 size_t *out_pos) {
391 size_t int_len = int_end - int_start;
392 if (int_len >= output_size - *out_pos) {
393 return false;
394 }
395 memcpy(output + *out_pos, int_start, int_len);
396 *out_pos += int_len;
397 return true;
398}
399
400/* --- Type= pattern detection and parsing --- */
401static bool is_type_equals_pattern(const char *p) {
402 return p[0] == 't' && p[1] == 'y' && p[2] == 'p' && p[3] == 'e' && p[4] == '=';
403}
404
405static bool type_equals_inside_parens(const char *p, const char *message) {
406 if (p <= message) {
407 return false;
408 }
409
410 const char *back = p - 1;
411 while (back > message && isspace(*back)) {
412 back--;
413 }
414 return back >= message && *back == '(';
415}
416
417static bool write_formatted_type_equals(int pkt_value, const char *name, char *output, size_t output_size,
418 size_t *out_pos) {
419 named_metadata_t metadata;
420 const char *fmt_spec = named_packet_metadata_copy(pkt_value, &metadata) ? metadata.format : "%d";
421
422 char id_buffer[LOG_NAMED_ID_BUFFER_SIZE];
423 int id_written = snprintf(id_buffer, sizeof(id_buffer), fmt_spec, pkt_value);
424 if (id_written <= 0 || id_written >= (int)sizeof(id_buffer)) {
425 return false;
426 }
427
428 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
429 int temp_written = snprintf(temp_output, sizeof(temp_output), "packet_type/%s (type=%s)", name, id_buffer);
430 if (temp_written <= 0 || (size_t)temp_written >= sizeof(temp_output)) {
431 return false;
432 }
433
434 if (*out_pos + temp_written >= output_size - 1) {
435 return false;
436 }
437
438 memcpy(output + *out_pos, temp_output, temp_written);
439 *out_pos += temp_written;
440 return true;
441}
442
443/* --- Buffer operations --- */
444
445static void copy_char_to_output(char *output, size_t *out_pos, char c) {
446 output[(*out_pos)++] = c;
447}
448
449/* ============================================================================
450 * Message Transformation Helpers
451 * ============================================================================
452 */
453
464static bool try_format_hex_address(const char *message, const char *hex_start, char *output, size_t output_size,
465 size_t *out_pos, const char **p) {
466 const char *hex_ptr = hex_start + 2; /* Skip "0x" */
467
468 /* Parse hex digits */
469 uintptr_t address = 0;
470 int hex_count = 0;
471 while (*hex_ptr && isxdigit(*hex_ptr) && hex_count < 16) {
472 address = (address << 4) | (unsigned int)(*hex_ptr > '9' ? (*hex_ptr | 0x20) - 'a' + 10 : *hex_ptr - '0');
473 hex_ptr++;
474 hex_count++;
475 }
476
477 if (hex_count == 0) {
478 return false; /* No valid hex digits */
479 }
480
481 /* Check if already inside parentheses (part of previous formatting) */
482 bool is_already_in_parens = (hex_start > message && *(hex_start - 1) == '(');
483 if (is_already_in_parens) {
484 return false;
485 }
486
487 /* Detect type prefix before hex address (e.g., "transport=" in "transport=0x1234") */
488 const char *prefix_type = NULL;
489 size_t prefix_type_len = 0;
490 const char *prefix_start = find_hex_type_prefix_start(message, hex_start, &prefix_type, &prefix_type_len);
491 bool has_type_prefix = (prefix_type != NULL && prefix_start != hex_start);
492
493 /* Check if address is registered */
494 named_metadata_t metadata;
495 if (named_metadata_copy(address, &metadata)) {
496 const char *name = metadata.name;
497 const char *type = metadata.type;
498 const char *fmt_spec = metadata.format;
499
500 char id_buffer[LOG_NAMED_ID_EXTENDED_BUFFER_SIZE];
501 int id_written = snprintf(id_buffer, sizeof(id_buffer), fmt_spec, (ptrdiff_t)address);
502 if (id_written <= 0 || id_written >= (int)sizeof(id_buffer)) {
503 return false;
504 }
505
506 size_t saved_pos = *out_pos;
507
508 /* Backtrack type prefix if it matches the registered type to avoid redundancy
509 * (e.g., "transport=transport/name" becomes "transport/name") */
510 if (has_type_prefix && prefix_type_len == strlen(type) && strncmp(prefix_type, type, prefix_type_len) == 0) {
511 size_t prefix_len = hex_start - prefix_start;
512 if (*out_pos >= prefix_len) {
513 *out_pos -= prefix_len;
514 }
515 }
516
517 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
518 int temp_written = snprintf(temp_output, sizeof(temp_output), "%s/%s (%s)", type, name, id_buffer);
519 if (temp_written <= 0 || (size_t)temp_written >= sizeof(temp_output)) {
520 *out_pos = saved_pos;
521 return false;
522 }
523
524 if (*out_pos + temp_written >= output_size - 1) {
525 *out_pos = saved_pos;
526 return false;
527 }
528
529 memcpy(output + *out_pos, temp_output, temp_written);
530 *out_pos += temp_written;
531 *p = hex_ptr;
532 return true;
533 }
534
535 /* Handle NULL pointer (address 0) with a known type prefix */
536 if (address == 0 && has_type_prefix) {
537 size_t saved_pos = *out_pos;
538 size_t prefix_len = hex_start - prefix_start;
539 if (*out_pos >= prefix_len) {
540 *out_pos -= prefix_len;
541 }
542
543 char type_str[LOG_NAMED_TYPE_BUFFER_SIZE];
544 if (prefix_type_len >= sizeof(type_str)) {
545 *out_pos = saved_pos;
546 return false;
547 }
548 memcpy(type_str, prefix_type, prefix_type_len);
549 type_str[prefix_type_len] = '\0';
550
551 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
552 int temp_written = snprintf(temp_output, sizeof(temp_output), "%s/(null)", type_str);
553 if (temp_written <= 0 || (size_t)temp_written >= sizeof(temp_output)) {
554 *out_pos = saved_pos;
555 return false;
556 }
557
558 if (*out_pos + temp_written >= output_size - 1) {
559 *out_pos = saved_pos;
560 return false;
561 }
562
563 memcpy(output + *out_pos, temp_output, temp_written);
564 *out_pos += temp_written;
565 *p = hex_ptr;
566 return true;
567 }
568
569 return false;
570}
571
572int log_named_format_message(const char *message, char *output, size_t output_size) {
573 if (!message || !output || output_size == 0) {
574 return -1;
575 }
576
577 size_t out_pos = 0;
578 const char *p = message;
579 bool any_transformed = false;
580
581 while (*p && out_pos < output_size - 1) {
582 /* Look for "0x" prefix indicating a hex address */
583 if (*p == '0' && *(p + 1) == 'x' && isxdigit(*(p + 2))) {
584 const char *hex_start = p;
585 if (try_format_hex_address(message, hex_start, output, output_size, &out_pos, &p)) {
586 any_transformed = true;
587 continue;
588 }
589
590 /* Not registered or formatting failed - copy original hex address */
591 const char *hex_end = hex_start + 2; /* Start after "0x" */
592 while (*hex_end && isxdigit(*hex_end)) {
593 hex_end++;
594 }
595 size_t hex_len = hex_end - hex_start;
596 if (hex_len < output_size - out_pos) {
597 memcpy(output + out_pos, hex_start, hex_len);
598 out_pos += hex_len;
599 p = hex_end;
600 } else {
601 /* Buffer overflow - truncate and stop */
602 break;
603 }
604 } else if (isdigit(*p)) {
605 /* Look for decimal integers that might be FDs or packet types */
606 const char *int_start = p;
607 int fd_value = 0;
608 int digit_count = 0;
609
610 /* Parse decimal digits */
611 while (*p && isdigit(*p) && digit_count < 6) { /* FDs rarely exceed 999999 */
612 fd_value = fd_value * 10 + (*p - '0');
613 p++;
614 digit_count++;
615 }
616
617 /* Skip if this integer is part of already-formatted output like "(fd=20)" or "(type/name (...))" */
618 bool is_already_formatted = false;
619 if (int_start - message >= 5) {
620 /* Check for "(fd=", "(pkt_type=", "(socket=", "(sockfd=", or "(type/" patterns to prevent re-formatting.
621 * Named objects are formatted as: type/name (0xaddress) or type/name (key=value)
622 * We need to detect if we're already inside a formatted output.
623 */
624 const char *check = int_start - 1;
625 while (check > message && isspace(*check))
626 check--;
627
628 // Only access *check if pointer is within bounds
629 if (check >= message && *check == '=') {
630 /* Found "=", now check what prefix it has for patterns like "(fd=20)" or "(socket=20)" or "(pkt_type=123)" */
631 const char *eq_pos = check;
632 check--;
633 while (check > message && (isalnum(*check) || *check == '_')) {
634 check--;
635 }
636 check++;
637
638 size_t prefix_len = eq_pos - check;
639 if ((prefix_len == 2 && strncmp(check, "fd", 2) == 0) ||
640 (prefix_len == 6 && strncmp(check, "socket", 6) == 0) ||
641 (prefix_len == 6 && strncmp(check, "sockfd", 6) == 0) ||
642 (prefix_len == 8 && strncmp(check, "pkt_type", 8) == 0)) {
643 /* Only mark as already-formatted if inside formatted output:
644 * - "(" before keyword: parenthesized value like "(sockfd=15)"
645 * - "/" before keyword: name part of type/name like "fd/fd=7"
646 * Raw log patterns like "sockfd=15" should still be eligible for replacement. */
647 const char *before_kw = check - 1;
648 while (before_kw > message && isspace(*before_kw))
649 before_kw--;
650 if (before_kw >= message && (*before_kw == '(' || *before_kw == '/')) {
651 is_already_formatted = true;
652 }
653 }
654 } else if (check >= message && *check == '/') {
655 /* Found "/", now check if this is a type/name pattern.
656 * Format is "(type/name (...)" where type is word characters (thread, mutex, socket, etc.)
657 * Must verify "/" is preceded by word characters AND "(" to ensure it's a formatted type.
658 */
659 const char *slash_pos = check;
660 check--;
661 while (check > message && (isalnum(*check) || *check == '_')) {
662 check--;
663 }
664 check++;
665
666 /* Check that the "/" was preceded by word characters (indicating a type name) */
667 size_t type_len = slash_pos - check;
668 if (type_len > 0 && (isalpha(*check) || *check == '_')) {
669 /* Verify this is a registered type/name pattern by checking for "(" before the type
670 * AND checking if the type actually exists in the registry */
671 const char *type_start = check;
672 const char *before_type = type_start - 1;
673 while (before_type > message && isspace(*before_type)) {
674 before_type--;
675 }
676
677 /* Only mark as formatted if:
678 * 1. Preceded by "(" - ensures pattern looks like "(type/name ...)"
679 * 2. Type exists in registry - prevents false positives on random text */
680 if (before_type >= message && *before_type == '(' && is_type_in_registry(type_start, type_len)) {
681 is_already_formatted = true;
682 }
683 }
684 }
685 }
686
687 /* Check if this is a registered packet type */
688 if (!is_already_formatted && digit_count > 0 && has_packet_type_prefix(message, int_start)) {
689 named_metadata_t metadata;
690 const char *name = named_packet_metadata_copy(fd_value, &metadata) ? metadata.name : NULL;
691 if (name) {
692 const char *prefix_start = find_packet_type_prefix_start(message, int_start);
693 size_t prefix_len = int_start - prefix_start;
694 size_t saved_pos = out_pos;
695
696 if (out_pos >= prefix_len) {
697 out_pos -= prefix_len;
698 }
699
700 if (write_formatted_packet_type(fd_value, name, output, output_size, &out_pos)) {
701 any_transformed = true;
702 continue;
703 }
704
705 out_pos = saved_pos; /* Restore on failure */
706 }
707 }
708
709 /* Check if this is a registered file descriptor */
710 if (!is_already_formatted && digit_count > 0 && has_fd_prefix(message, int_start)) {
711 named_metadata_t metadata;
712 const char *name = named_fd_metadata_copy(fd_value, &metadata) ? metadata.name : NULL;
713 const char *reg_type = NULL;
714 if (!name && named_metadata_copy((uintptr_t)(intptr_t)fd_value, &metadata)) {
715 name = metadata.name;
716 reg_type = metadata.type;
717 }
718
719 if (name) {
720 const char *prefix_start = find_fd_prefix_start(message, int_start);
721 size_t prefix_len = int_start - prefix_start;
722 size_t saved_pos = out_pos;
723
724 if (out_pos >= prefix_len) {
725 out_pos -= prefix_len;
726 }
727
728 bool formatted = false;
729 if (reg_type) {
730 /* From main registry — use registered type and original prefix keyword */
731 const char *kw_end = prefix_start;
732 while (kw_end < int_start && (isalnum(*kw_end) || *kw_end == '_'))
733 kw_end++;
734 size_t kw_len = kw_end - prefix_start;
735
736 char kw_buf[LOG_NAMED_KW_BUFFER_SIZE];
737 if (kw_len > 0 && kw_len < sizeof(kw_buf)) {
738 memcpy(kw_buf, prefix_start, kw_len);
739 kw_buf[kw_len] = '\0';
740 } else {
741 snprintf(kw_buf, sizeof(kw_buf), "%s", reg_type);
742 }
743
745 int written = snprintf(temp, sizeof(temp), "%s/%s (%s=%d)", reg_type, name, kw_buf, fd_value);
746 if (written > 0 && (size_t)written < sizeof(temp) && out_pos + (size_t)written < output_size - 1) {
747 memcpy(output + out_pos, temp, (size_t)written);
748 out_pos += (size_t)written;
749 formatted = true;
750 }
751 } else {
752 formatted = write_formatted_fd(fd_value, name, output, output_size, &out_pos);
753 }
754
755 if (formatted) {
756 any_transformed = true;
757 continue;
758 }
759
760 out_pos = saved_pos; /* Restore on failure */
761 }
762 }
763
764 /* Check if this integer has a generic type prefix (socket, client, connection, etc.)
765 * DISABLED: This feature has a backtracking bug that corrupts output when applied after
766 * other formatting operations. Re-enable only after fixing the backtracking logic to
767 * properly track correspondence between input and output positions.
768 * See commit b86fed2a8 which introduced the bug.
769 */
770 // NOLINTNEXTLINE(readability-simplify-boolean-expr) - Intentionally disabled code, see comment above
771 if (false && !is_already_formatted && digit_count > 0) {
772 const char *type_name = NULL;
773 size_t type_len = 0;
774 const char *prefix_start = find_generic_type_prefix(message, int_start, &type_name, &type_len);
775
776 if (type_name && prefix_start != int_start) {
777 /* Skip if already formatted with a name (e.g., "socket/listener.0" already has a /) */
778 bool skip_format = false;
779 /* Check if this looks like it's already been formatted by looking for / in the pattern */
780 const char *check_ptr = prefix_start;
781 while (check_ptr < int_start && *check_ptr && *check_ptr != '/') {
782 check_ptr++;
783 }
784 if (check_ptr < int_start && *check_ptr == '/') {
785 /* Already formatted (contains /), skip */
786 skip_format = true;
787 }
788
789 if (!skip_format) {
790 /* We found a type prefix like "socket", "client", etc.
791 * Try to look up a registered name for this type+id */
792
793 /* Normalize type name aliases: "sockfd" → "socket", "descriptor" → "fd" */
794 const char *lookup_type = type_name;
795 size_t lookup_len = type_len;
796 if (type_len == 6 && strncmp(type_name, "sockfd", 6) == 0) {
797 lookup_type = "socket";
798 lookup_len = 6;
799 } else if (type_len == 10 && strncmp(type_name, "descriptor", 10) == 0) {
800 lookup_type = "fd";
801 lookup_len = 2;
802 }
803
804 const char *name = named_get_by_type_and_id(lookup_type, lookup_len, fd_value);
805
806 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
808 char id_buffer[LOG_NAMED_ID_BUFFER_SIZE];
809
810 /* Safely copy type name */
811 if (type_len < sizeof(type_str)) {
812 memcpy(type_str, type_name, type_len);
813 type_str[type_len] = '\0';
814
815 int id_written = snprintf(id_buffer, sizeof(id_buffer), "%d", fd_value);
816 if (id_written > 0 && id_written < (int)sizeof(id_buffer)) {
817 int temp_written = 0;
818 if (name) {
819 /* Format with name: type/name (type=value) */
820 temp_written =
821 snprintf(temp_output, sizeof(temp_output), "%s/%s (%s=%s)", type_str, name, type_str, id_buffer);
822
823 if (temp_written > 0 && (size_t)temp_written < sizeof(temp_output)) {
824 /* Backtrack to remove the prefix we already copied */
825 size_t prefix_len = int_start - prefix_start;
826 if (out_pos >= prefix_len) {
827 out_pos -= prefix_len;
828 }
829
830 int copy_len = temp_written;
831 if (out_pos + copy_len < output_size - 1) {
832 memcpy(output + out_pos, temp_output, copy_len);
833 out_pos += copy_len;
834 any_transformed = true;
835 continue;
836 }
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844
845 /* Not a registered FD or formatting failed - copy original number */
846 if (!copy_unformatted_decimal(int_start, p, output, output_size, &out_pos)) {
847 break;
848 }
849 } else {
850 /* Check if this is the start of "type=DIGIT" pattern */
851 if (*p == 't' && p + 4 < message + strlen(message) && is_type_equals_pattern(p)) {
852 if (!type_equals_inside_parens(p, message)) {
853 const char *digit_end = p + 5;
854 int pkt_value = parse_decimal_digits(digit_end, &digit_end);
855
856 if (pkt_value > 0) {
857 named_metadata_t metadata;
858 const char *name = named_packet_metadata_copy(pkt_value, &metadata) ? metadata.name : NULL;
859 if (name && write_formatted_type_equals(pkt_value, name, output, output_size, &out_pos)) {
860 p = digit_end;
861 any_transformed = true;
862 continue;
863 }
864 }
865 }
866 }
867
868 /* Regular character - copy it */
869 copy_char_to_output(output, &out_pos, *p++);
870 }
871 }
872
873 /* Null terminate */
874 if (out_pos < output_size) {
875 output[out_pos] = '\0';
876 } else {
877 output[output_size - 1] = '\0';
878 }
879
880 return any_transformed ? (int)out_pos : -1;
881}
882
892const char *log_named_format_or_original(const char *message) {
893 if (!message) {
894 return message;
895 }
896
897 static _Thread_local char format_buffer[NAMED_FORMAT_BUFFER_SIZE];
898 static _Thread_local char work_buffer[NAMED_FORMAT_BUFFER_SIZE];
899
900 /* Apply formatting iteratively until the message stabilizes (no more changes) */
901 const char *current = message;
902 size_t max_iterations =
903 3; /* Reduce iterations to prevent recursion (prevent transforming already-transformed output) */
904
905 for (size_t iter = 0; iter < max_iterations; iter++) {
906 int result = log_named_format_message(current, format_buffer, sizeof(format_buffer));
907
908 if (result <= 0) {
909 /* No transformation was made, we've reached a fixpoint */
910 return current == message ? message : format_buffer;
911 }
912
913 /* Check if the message changed */
914 if (strcmp(current, format_buffer) == 0) {
915 /* No actual change, return the current version */
916 return format_buffer;
917 }
918
919 /* Message changed, prepare for next iteration */
920 if (current == message) {
921 /* First iteration, use format_buffer as input for next iteration */
922 current = format_buffer;
923 } else {
924 /* Subsequent iterations, swap buffers */
925 asciichat_error_t strcpy_result = SAFE_STRCPY(work_buffer, sizeof(work_buffer), format_buffer);
926 if (strcpy_result != ASCIICHAT_OK) {
927 log_error("Failed to copy format buffer: %s", asciichat_error_format(NULL, strcpy_result));
928 return format_buffer;
929 }
930 current = work_buffer;
931 }
932 }
933
934 /* Max iterations reached, return last formatted version */
935 return format_buffer;
936}
937
951const char *colorize_named_string(const char *name_str) {
952#define COLORIZE_BUFFERS 4
953#define COLORIZE_BUFFER_SIZE 512
954 static char buffers[COLORIZE_BUFFERS][COLORIZE_BUFFER_SIZE];
955 static int buffer_idx = 0;
956
957 if (!name_str || name_str[0] == '\0') {
958 return name_str;
959 }
960
961 char *current_buf = buffers[buffer_idx];
962 buffer_idx = (buffer_idx + 1) % COLORIZE_BUFFERS;
963
964 /* Find the slash separator (type/name) */
965 const char *slash = strchr(name_str, '/');
966 if (!slash) {
967 /* No slash found - just return the string as-is */
968 return name_str;
969 }
970
971 /* Find opening paren for address */
972 const char *paren = strchr(name_str, '(');
973
974 /* Extract type (before slash) */
975 size_t type_len = slash - name_str;
977 size_t copy_len = type_len < sizeof(type_buf) - 1 ? type_len : sizeof(type_buf) - 1;
978 memcpy(type_buf, name_str, copy_len);
979 type_buf[copy_len] = '\0';
980
981 /* Extract name (between slash and paren, or end of string) */
982 const char *name_start = slash + 1;
983 size_t name_len;
984 if (paren && paren > slash) {
985 /* Skip spaces before paren */
986 const char *name_end = paren - 1;
987 while (name_end > name_start && *name_end == ' ') {
988 name_end--;
989 }
990 name_len = name_end - name_start + 1;
991 } else {
992 name_len = strlen(name_start);
993 }
994
995 char name_buf[LOG_NAMED_NAME_BUFFER_SIZE];
996 size_t name_copy_len = name_len < sizeof(name_buf) - 1 ? name_len : sizeof(name_buf) - 1;
997 memcpy(name_buf, name_start, name_copy_len);
998 name_buf[name_copy_len] = '\0';
999
1000 /* Apply colors */
1001 const char *type_colored = colored_string(LOG_COLOR_WARN, type_buf);
1002 const char *name_colored = colored_string(LOG_COLOR_DEV, name_buf);
1003
1004 /* Format: type(yellow)/name(blue) and add address part if present */
1005 if (paren) {
1006 /* Extract address (0x...) from parentheses */
1007 const char *addr_start = paren + 1;
1008 const char *addr_end = strchr(addr_start, ')');
1009 if (addr_end) {
1010 size_t addr_len = addr_end - addr_start;
1011 char addr_buf[LOG_NAMED_ADDR_BUFFER_SIZE];
1012 size_t addr_copy_len = addr_len < sizeof(addr_buf) - 1 ? addr_len : sizeof(addr_buf) - 1;
1013 memcpy(addr_buf, addr_start, addr_copy_len);
1014 addr_buf[addr_copy_len] = '\0';
1015
1016 const char *addr_colored = colored_string(LOG_COLOR_GREY, addr_buf);
1017 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s (%s)", type_colored, name_colored, addr_colored);
1018 } else {
1019 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s %s", type_colored, name_colored, paren);
1020 }
1021 } else {
1022 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s", type_colored, name_colored);
1023 }
1024
1025 return current_buf;
1026#undef COLORIZE_BUFFERS
1027#undef COLORIZE_BUFFER_SIZE
1028}
const char * asciichat_error_format(const char *message, asciichat_error_t code)
Named object registry for debugging — log identifiable resource names.
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:486
bool named_registry_has_type(const char *type, size_t length)
bool named_packet_metadata_copy(int packet_type, named_metadata_t *output)
bool named_metadata_copy(uintptr_t key, named_metadata_t *output)
bool named_fd_metadata_copy(int fd, named_metadata_t *output)
const char * named_get_by_type_and_id(const char *type_name, size_t type_len, int id)
Look up an integer ID by type name and value.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
int log_named_format_message(const char *message, char *output, size_t output_size)
Format hex addresses in a message as named object descriptions.
Definition log/named.c:572
const char * log_named_format_or_original(const char *message)
Format named objects in a message (thread-local buffer).
Definition log/named.c:892
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
const char * colorize_named_string(const char *name_str)
Colorize a "type/name (0xaddress)" string for display in memory reports.
Definition log/named.c:951
@ LOG_COLOR_GREY
Definition log/log.h:154
@ LOG_COLOR_DEV
Definition log/log.h:148
@ LOG_COLOR_WARN
Definition log/log.h:151
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
Definition string.c:284
📝 Logging API with multiple log levels and terminal output control
#define LOG_NAMED_ID_EXTENDED_BUFFER_SIZE
Definition log/named.c:19
#define LOG_NAMED_KW_BUFFER_SIZE
Definition log/named.c:21
#define LOG_NAMED_TYPE_SHORT_BUFFER_SIZE
Definition log/named.c:23
#define LOG_NAMED_ADDR_BUFFER_SIZE
Definition log/named.c:26
#define LOG_NAMED_ID_BUFFER_SIZE
Definition log/named.c:17
#define COLORIZE_BUFFER_SIZE
#define LOG_NAMED_TYPE_BUFFER_SIZE
Definition log/named.c:20
#define LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE
Definition log/named.c:18
#define LOG_NAMED_TYPE_EXTENDED_BUFFER_SIZE
Definition log/named.c:24
#define LOG_NAMED_TEMP_BUFFER_SIZE
Definition log/named.c:22
#define COLORIZE_BUFFERS
#define NAMED_FORMAT_BUFFER_SIZE
Definition log/named.c:37
#define LOG_NAMED_NAME_BUFFER_SIZE
Definition log/named.c:25
🔤 String Manipulation and Shell Escaping Utilities
The useful part of named metadata is in its fields: together they describe what is pending,...