/** * Debug module */ #include "debug_config.h" #include "platform_compat.h" #include #include #include #include #include #include #include void log_dump(const char* prefix, const uint8_t* data, size_t len) { char hex_buf[513] = {'\0'}; size_t hex_len = 0; size_t show_len = (len > 128) ? 128 : len; for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) { hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]); if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " "); } } if (len > 128) { hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "..."); } // Single-line debug output with packet info DEBUG_INFO(DEBUG_CATEGORY_ETCP, "%s: len=%zu hex=%s", prefix, len, hex_buf); // Additional debug info for first few bytes // if (len >= 2) { // DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x", // prefix, data[0], data[1], data[len-2], data[len-1]); // } } ip_str_t ip_to_str(const void *addr, int family) { ip_str_t result; result.str[0] = '\0'; if (!addr) { return result; } if (family == AF_INET) { inet_ntop(AF_INET, addr, result.str, 16); } else if (family == AF_INET6) { inet_ntop(AF_INET6, addr, result.str, 48); } return result; } /* Get category by name */ static debug_category_t get_category_by_name(const char* name) { struct { const char* n; debug_category_t c; } map[] = { {"none", DEBUG_CATEGORY_NONE}, {"uasync", DEBUG_CATEGORY_UASYNC}, {"ll_queue", DEBUG_CATEGORY_LL_QUEUE}, {"connection", DEBUG_CATEGORY_CONNECTION}, {"etcp", DEBUG_CATEGORY_ETCP}, {"crypto", DEBUG_CATEGORY_CRYPTO}, {"memory", DEBUG_CATEGORY_MEMORY}, {"timing", DEBUG_CATEGORY_TIMING}, {"config", DEBUG_CATEGORY_CONFIG}, {"tun", DEBUG_CATEGORY_TUN}, {"routing", DEBUG_CATEGORY_ROUTING}, {"timers", DEBUG_CATEGORY_TIMERS}, {"normalizer", DEBUG_CATEGORY_NORMALIZER}, {"bgp", DEBUG_CATEGORY_BGP}, {"socket", DEBUG_CATEGORY_SOCKET}, {"control", DEBUG_CATEGORY_CONTROL}, {"dump", DEBUG_CATEGORY_DUMP}, {"all", DEBUG_CATEGORY_ALL}, {NULL, 0} }; for (int i = 0; map[i].n; i++) { if (strcasecmp(map[i].n, name) == 0) { return map[i].c; } } return DEBUG_CATEGORY_NONE; } /* Global debug configuration */ debug_config_t g_debug_config; /* Initialize debug system with default settings */ void debug_config_init(void) { g_debug_config.level = DEBUG_LEVEL_ERROR; // Global level: errors only by default g_debug_config.categories = DEBUG_CATEGORY_ALL; for (int i = 0; i < DEBUG_CATEGORY_COUNT; i++) { g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE; // 0 = disabled per category } g_debug_config.timestamp_enabled = 1; g_debug_config.function_name_enabled = 1; g_debug_config.file_line_enabled = 1; g_debug_config.output_file = NULL; // Dual output defaults g_debug_config.file_output = NULL; // No file by default g_debug_config.file_level = DEBUG_LEVEL_TRACE; // File gets everything g_debug_config.console_level = DEBUG_LEVEL_INFO; // Console gets INFO+ g_debug_config.console_enabled = 1; // Console enabled } /* Set debug level */ void debug_set_level(debug_level_t level) { g_debug_config.level = level; g_debug_config.console_level = level; } /* Set debug level for specific category (0 = disabled, otherwise uses that level) */ void debug_set_category_level(debug_category_t category, debug_level_t level) { if (category == DEBUG_CATEGORY_NONE) return; int idx = 0; while (!(category & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) { idx++; } if (idx < DEBUG_CATEGORY_COUNT) { g_debug_config.category_levels[idx] = level; } } /* Enable/disable specific categories */ void debug_enable_category(debug_category_t category) { g_debug_config.categories |= category; } void debug_disable_category(debug_category_t category) { g_debug_config.categories &= ~category; } void debug_set_categories(debug_category_t categories) { g_debug_config.categories = categories; } /* Set masks directly */ void debug_set_masks(debug_category_t categories, debug_level_t level) { g_debug_config.categories = categories; g_debug_config.level = level; } void debug_enable_function_name(int enable) { g_debug_config.function_name_enabled = enable; } void debug_enable_file_line(int enable) { g_debug_config.file_line_enabled = enable; } void debug_set_output_file(const char* file_path) { if (g_debug_config.file_output) return;// уже установлен, кто первый того и тапки FILE* new_file = fopen(file_path, "a"); if (new_file) { g_debug_config.file_output = new_file; g_debug_config.output_file = file_path; } } /* Set console log level */ void debug_set_console_level(debug_level_t level) { g_debug_config.console_level = level; } /* Set file log level */ void debug_set_file_level(debug_level_t level) { g_debug_config.file_level = level; } /* Enable/disable console output */ void debug_enable_console(int enable) { g_debug_config.console_enabled = enable; } /* Enable file output with optional truncation */ void debug_enable_file_output(const char* file_path, int truncate) { if (!file_path) return; // Close existing file if open if (g_debug_config.file_output) { fclose(g_debug_config.file_output); g_debug_config.file_output = NULL; } // Open new file const char* mode = truncate ? "w" : "a"; FILE* new_file = fopen(file_path, mode); if (new_file) { g_debug_config.file_output = new_file; g_debug_config.output_file = file_path; g_debug_config.console_enabled = 0; } } /* Disable file output */ void debug_disable_file_output(void) { if (g_debug_config.file_output) { fclose(g_debug_config.file_output); g_debug_config.file_output = NULL; g_debug_config.output_file = NULL; } } /* Check if debug output should be shown for given level and category */ int debug_should_output(debug_level_t level, debug_category_t category) { /* Check if category is enabled */ if (!(g_debug_config.categories & category)) { return 0; } /* Determine effective level: max(global, category) */ int idx = 0; while (!(category & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) { idx++; } debug_level_t cat_level = (idx < DEBUG_CATEGORY_COUNT) ? g_debug_config.category_levels[idx] : DEBUG_LEVEL_NONE; debug_level_t effective = (cat_level > g_debug_config.level) ? cat_level : g_debug_config.level; /* Check if level is sufficient */ if (level > effective) { return 0; } return 1; } /* Get current debug level */ debug_level_t debug_get_level(void) { return g_debug_config.level; } /* Get level name */ static const char* get_level_name(debug_level_t level) { switch (level) { case DEBUG_LEVEL_ERROR: return "ERROR"; case DEBUG_LEVEL_WARN: return "WARN"; case DEBUG_LEVEL_INFO: return "INFO"; case DEBUG_LEVEL_DEBUG: return "DEBUG"; case DEBUG_LEVEL_TRACE: return "TRACE"; default: return "UNKNOWN"; } } /* 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, ...) { #define BUFFER_SIZE 4096 char buffer[BUFFER_SIZE]; int offset = 0; size_t remaining = BUFFER_SIZE; va_list args; va_start(args, format); /* Add timestamp with microseconds: hh:mm:ss-xxx.yyy */ struct timeval tv; gettimeofday(&tv, NULL); time_t tv_sec = (time_t)tv.tv_sec; struct tm* tm_info = localtime(&tv_sec); char time_str[32]; strftime(time_str, sizeof(time_str), "%H:%M:%S", tm_info); offset += snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000); remaining = BUFFER_SIZE - offset; /* Add level */ const char* level_name = get_level_name(level); offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); remaining = BUFFER_SIZE - offset; /* Add category */ offset += snprintf(buffer + offset, remaining, "[%llu] ", (unsigned long long)category); remaining = BUFFER_SIZE - offset; /* Add file:line if enabled */ if (g_debug_config.file_line_enabled && file) { offset += snprintf(buffer + offset, remaining, "(%s:%d) ", file, line); remaining = BUFFER_SIZE - offset; } /* Add function name if enabled */ if (g_debug_config.function_name_enabled && function) { offset += snprintf(buffer + offset, remaining, "%s() ", function); remaining = BUFFER_SIZE - offset; } /* Add the actual message */ offset += vsnprintf(buffer + offset, remaining, format, args); remaining = BUFFER_SIZE - offset; /* Add newline */ if (remaining > 1) { buffer[offset] = '\n'; buffer[offset + 1] = '\0'; } else { buffer[BUFFER_SIZE - 2] = '\n'; buffer[BUFFER_SIZE - 1] = '\0'; } va_end(args); /* Output to console if enabled and level is sufficient */ if (g_debug_config.console_enabled && level <= g_debug_config.console_level) { fprintf(stdout, "%s", buffer); fflush(stdout); } /* Output to file if open and level is sufficient */ if (g_debug_config.file_output && level <= g_debug_config.file_level) { fprintf(g_debug_config.file_output, "%s", buffer); fflush(g_debug_config.file_output); } #undef BUFFER_SIZE } /* Parse debug configuration from string */ int debug_parse_config(const char* config_string) { if (!config_string || !*config_string) { return 0; } char* str = strdup(config_string); if (!str) { return -1; } debug_category_t categories = 0; debug_level_t level = DEBUG_LEVEL_NONE; char* token = strtok(str, ";"); while (token) { /* Trim leading spaces */ while (isspace(*token)) { token++; } char* eq = strchr(token, '='); if (!eq) { free(str); return -1; } *eq = '\0'; char* key = token; char* value = eq + 1; /* Trim leading spaces in value */ while (isspace(*value)) { value++; } if (strcasecmp(key, "modules") == 0) { char* mod_token = strtok(value, ","); while (mod_token) { /* Trim leading spaces */ while (isspace(*mod_token)) { mod_token++; } debug_category_t cat = get_category_by_name(mod_token); if (cat == DEBUG_CATEGORY_NONE) { free(str); return -1; } categories |= cat; mod_token = strtok(NULL, ","); } } else if (strcasecmp(key, "level") == 0) { int lev_num = atoi(value); if (lev_num < DEBUG_LEVEL_NONE || lev_num > DEBUG_LEVEL_TRACE) { free(str); return -1; } level = (debug_level_t)lev_num; } else { free(str); return -1; } token = strtok(NULL, ";"); } debug_set_masks(categories, level); free(str); return 0; }