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