/** * Runtime debug configuration implementation */ #define _GNU_SOURCE // For strdup #include "debug_config.h" #include #include #include #include #include #include #include #include #include #include FILE* debug_output_file = NULL; /* Global debug configuration */ debug_config_t g_debug_config = { .level = DEBUG_LEVEL_ERROR, // Default: errors only .categories = DEBUG_CATEGORY_ALL, // All categories enabled .timestamp_enabled = 0, // No timestamps by default .function_name_enabled = 0, // No function names by default .file_line_enabled = 0, // No file:line by default .color_enabled = 0, // No colors by default .max_output_per_second = 0, // No rate limiting .output_file = NULL // Output to stderr }; /* Rate limiting state */ static struct { size_t output_count; time_t last_reset_time; pthread_mutex_t mutex; } rate_limit_state = { .output_count = 0, .last_reset_time = 0, .mutex = PTHREAD_MUTEX_INITIALIZER }; /* Configuration file support */ /* ANSI color codes */ #define COLOR_RESET "\033[0m" #define COLOR_RED "\033[31m" #define COLOR_GREEN "\033[32m" #define COLOR_YELLOW "\033[33m" #define COLOR_BLUE "\033[34m" #define COLOR_MAGENTA "\033[35m" #define COLOR_CYAN "\033[36m" #define COLOR_WHITE "\033[37m" /* Level color mapping */ static const char* level_colors[] = { [DEBUG_LEVEL_NONE] = COLOR_RESET, [DEBUG_LEVEL_ERROR] = COLOR_RED, [DEBUG_LEVEL_WARN] = COLOR_YELLOW, [DEBUG_LEVEL_INFO] = COLOR_GREEN, [DEBUG_LEVEL_DEBUG] = COLOR_BLUE, [DEBUG_LEVEL_TRACE] = COLOR_CYAN }; /* Level name mapping */ static const char* level_names[] = { [DEBUG_LEVEL_NONE] = "NONE", [DEBUG_LEVEL_ERROR] = "ERROR", [DEBUG_LEVEL_WARN] = "WARN", [DEBUG_LEVEL_INFO] = "INFO", [DEBUG_LEVEL_DEBUG] = "DEBUG", [DEBUG_LEVEL_TRACE] = "TRACE" }; /* Category name mapping */ static struct { debug_category_t category; const char* name; } category_names[] = { { DEBUG_CATEGORY_UASYNC, "uasync" }, { DEBUG_CATEGORY_LL_QUEUE, "ll_queue" }, { DEBUG_CATEGORY_CONNECTION, "connection" }, { DEBUG_CATEGORY_ETCP, "etcp" }, { DEBUG_CATEGORY_CRYPTO, "crypto" }, { DEBUG_CATEGORY_MEMORY, "memory" }, { DEBUG_CATEGORY_TIMING, "timing" }, }; /* Initialize debug system with default settings */ void debug_config_init(void) { // Configuration is already initialized with defaults // This function is for API compatibility DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug system initialized with level %d", g_debug_config.level); } /* Set debug level */ void debug_set_level(debug_level_t level) { if (level < DEBUG_LEVEL_NONE || level > DEBUG_LEVEL_TRACE) { DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "Invalid debug level: %d", level); return; } g_debug_config.level = level; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug level set to %s", level_names[level]); } /* Enable specific category */ void debug_enable_category(debug_category_t category) { g_debug_config.categories |= category; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug category enabled: 0x%x", category); } /* Disable specific category */ void debug_disable_category(debug_category_t category) { g_debug_config.categories &= ~category; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug category disabled: 0x%x", category); } /* Set all categories at once */ void debug_set_categories(uint32_t categories) { g_debug_config.categories = categories; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug categories set to: 0x%x", categories); } /* Configure output options */ void debug_enable_timestamp(int enable) { g_debug_config.timestamp_enabled = enable ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug timestamps %s", enable ? "enabled" : "disabled"); } void debug_enable_function_name(int enable) { g_debug_config.function_name_enabled = enable ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug function names %s", enable ? "enabled" : "disabled"); } void debug_enable_file_line(int enable) { g_debug_config.file_line_enabled = enable ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug file:line info %s", enable ? "enabled" : "disabled"); } void debug_enable_color(int enable) { g_debug_config.color_enabled = enable ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug colors %s", enable ? "enabled" : "disabled"); } void debug_set_rate_limit(size_t max_per_second) { pthread_mutex_lock(&rate_limit_state.mutex); g_debug_config.max_output_per_second = max_per_second; rate_limit_state.output_count = 0; rate_limit_state.last_reset_time = 0; // Force reset on next check pthread_mutex_unlock(&rate_limit_state.mutex); DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug rate limit set to %zu per second", max_per_second); } void debug_set_output_file(const char* file_path) { // Close existing file if open if (debug_output_file && debug_output_file != stderr) { fclose(debug_output_file); debug_output_file = NULL; } if (file_path) { debug_output_file = fopen(file_path, "a"); if (!debug_output_file) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to open debug output file: %s", file_path); debug_output_file = stderr; } else { g_debug_config.output_file = file_path; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug output redirected to file: %s", file_path); } } else { debug_output_file = stderr; g_debug_config.output_file = NULL; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Debug output redirected to stderr"); } } /* Check if debug output should be shown */ int debug_should_output(debug_level_t level, debug_category_t category) { // ERROR level always outputs as fallback, regardless of settings if (level == DEBUG_LEVEL_ERROR) { return 1; } // Check if this category is enabled if (!(g_debug_config.categories & category)) { return 0; } // Check if this level is enabled debug_level_t effective_level = debug_get_effective_level(category); if (level > effective_level) { return 0; } // Check rate limiting if (g_debug_config.max_output_per_second > 0) { pthread_mutex_lock(&rate_limit_state.mutex); time_t current_time = time(NULL); // Reset counter if a second has passed or if last_reset_time is 0 (initial state) if (rate_limit_state.last_reset_time == 0 || current_time != rate_limit_state.last_reset_time) { rate_limit_state.output_count = 0; rate_limit_state.last_reset_time = current_time; } // For testing: if we haven't exceeded the limit, allow it if (rate_limit_state.output_count < g_debug_config.max_output_per_second) { rate_limit_state.output_count++; pthread_mutex_unlock(&rate_limit_state.mutex); return 1; } pthread_mutex_unlock(&rate_limit_state.mutex); return 0; } return 1; } /* Get current debug level for a category */ debug_level_t debug_get_effective_level(debug_category_t category) { // For now, return global level (can be extended per-category in future) (void)category; // Unused parameter return g_debug_config.level; } /* Format and output debug message */ void debug_output(debug_level_t level, debug_category_t category, const char* function, const char* file, int line, const char* format, ...) { if (!debug_should_output(level, category)) { return; } FILE* output = debug_output_file ? debug_output_file : stderr; va_list args; // Get current time for timestamp struct timeval tv; gettimeofday(&tv, NULL); struct tm* tm_info = localtime(&tv.tv_sec); // Print timestamp if enabled if (g_debug_config.timestamp_enabled) { if (g_debug_config.color_enabled) { fprintf(output, "[%02d:%02d:%02d.%03ld] ", tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, tv.tv_usec / 1000); } else { fprintf(output, "[%02d:%02d:%02d.%03ld] ", tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, tv.tv_usec / 1000); } } // Print level with color if (g_debug_config.color_enabled) { fprintf(output, "%s[%s]%s ", level_colors[level], level_names[level], COLOR_RESET); } else { fprintf(output, "[%s] ", level_names[level]); } // Print category const char* category_name = "unknown"; for (size_t i = 0; i < sizeof(category_names) / sizeof(category_names[0]); i++) { if (category_names[i].category == category) { category_name = category_names[i].name; break; } } fprintf(output, "[%s] ", category_name); // Print file:line if enabled if (g_debug_config.file_line_enabled) { fprintf(output, "%s:%d: ", file, line); } // Print function name if enabled if (g_debug_config.function_name_enabled) { fprintf(output, "%s(): ", function); } // Print the actual message va_start(args, format); vfprintf(output, format, args); va_end(args); fprintf(output, "\n"); fflush(output); } /* Parse debug configuration from string */ int debug_parse_config(const char* config_string) { if (!config_string) return -1; // Handle simple level format directly if (strchr(config_string, ':') == NULL) { debug_level_t level = DEBUG_LEVEL_NONE; if (strcasecmp(config_string, "none") == 0) level = DEBUG_LEVEL_NONE; else if (strcasecmp(config_string, "error") == 0) level = DEBUG_LEVEL_ERROR; else if (strcasecmp(config_string, "warn") == 0) level = DEBUG_LEVEL_WARN; else if (strcasecmp(config_string, "info") == 0) level = DEBUG_LEVEL_INFO; else if (strcasecmp(config_string, "debug") == 0) level = DEBUG_LEVEL_DEBUG; else if (strcasecmp(config_string, "trace") == 0) level = DEBUG_LEVEL_TRACE; else { DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "Invalid debug level: %s", config_string); return -1; } debug_set_level(level); debug_set_categories(DEBUG_CATEGORY_ALL); return 0; } // Handle category:level format char* config_copy = strdup(config_string); if (!config_copy) return -1; char* token = strtok(config_copy, ","); int errors = 0; while (token) { // Trim whitespace while (*token == ' ' || *token == '\t') token++; char* end = token + strlen(token) - 1; while (end > token && (*end == ' ' || *end == '\t')) end--; *(end + 1) = '\0'; // Parse category:level format char* colon = strchr(token, ':'); if (!colon) { DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "Invalid config format (missing ':'): %s", token); errors++; token = strtok(NULL, ","); continue; } *colon = '\0'; char* category_str = token; char* level_str = colon + 1; // Parse level debug_level_t level = DEBUG_LEVEL_NONE; if (strcasecmp(level_str, "none") == 0) level = DEBUG_LEVEL_NONE; else if (strcasecmp(level_str, "error") == 0) level = DEBUG_LEVEL_ERROR; else if (strcasecmp(level_str, "warn") == 0) level = DEBUG_LEVEL_WARN; else if (strcasecmp(level_str, "info") == 0) level = DEBUG_LEVEL_INFO; else if (strcasecmp(level_str, "debug") == 0) level = DEBUG_LEVEL_DEBUG; else if (strcasecmp(level_str, "trace") == 0) level = DEBUG_LEVEL_TRACE; else { DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "Invalid debug level: %s", level_str); errors++; token = strtok(NULL, ","); continue; } // Parse category debug_category_t category = DEBUG_CATEGORY_NONE; if (strcasecmp(category_str, "uasync") == 0) category = DEBUG_CATEGORY_UASYNC; else if (strcasecmp(category_str, "ll_queue") == 0) category = DEBUG_CATEGORY_LL_QUEUE; else if (strcasecmp(category_str, "connection") == 0) category = DEBUG_CATEGORY_CONNECTION; else if (strcasecmp(category_str, "etcp") == 0) category = DEBUG_CATEGORY_ETCP; else if (strcasecmp(category_str, "crypto") == 0) category = DEBUG_CATEGORY_CRYPTO; else if (strcasecmp(category_str, "memory") == 0) category = DEBUG_CATEGORY_MEMORY; else if (strcasecmp(category_str, "timing") == 0) category = DEBUG_CATEGORY_TIMING; else if (strcasecmp(category_str, "all") == 0) category = DEBUG_CATEGORY_ALL; else if (strcasecmp(category_str, "config") == 0) category = DEBUG_CATEGORY_CONFIG; else if (strcasecmp(category_str, "tun") == 0) category = DEBUG_CATEGORY_TUN; else if (strcasecmp(category_str, "routing") == 0) category = DEBUG_CATEGORY_ROUTING; else { DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "Unknown debug category: %s", category_str); errors++; token = strtok(NULL, ","); continue; } // Apply configuration if (category == DEBUG_CATEGORY_ALL) { debug_set_level(level); } else { debug_enable_category(category); // Note: per-category levels not yet implemented, use global level for now } token = strtok(NULL, ","); } free(config_copy); return (errors == 0) ? 0 : -1; }