Browse Source
Changes: - ll_queue callback now receives only queue pointer and arg (no data) - Added log_dump and addr_to_string utility functions - Removed temporary backup files - Updated AGENTS.md with additional guidelines - Fixed compiler warnings in etcp.c and pkt_normalizer.cnodeinfo-routing-update
43 changed files with 853 additions and 5777 deletions
@ -1,435 +0,0 @@
|
||||
/** |
||||
* Debug configuration implementation |
||||
* Runtime debug configuration system for flexible debug output control |
||||
*/ |
||||
|
||||
#include "debug_config.h" |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <ctype.h> |
||||
#include <stdarg.h> |
||||
#include <stdio.h> |
||||
#include <time.h> |
||||
#include <strings.h> |
||||
|
||||
/* Rate limiting */ |
||||
static size_t output_count = 0; |
||||
static time_t last_output_time = 0; |
||||
|
||||
/* ANSI color codes */ |
||||
static const char* color_red = "\033[31m"; |
||||
static const char* color_yellow = "\033[33m"; |
||||
static const char* color_green = "\033[32m"; |
||||
static const char* color_blue = "\033[34m"; |
||||
static const char* color_magenta = "\033[35m"; |
||||
static const char* color_cyan = "\033[36m"; |
||||
static const char* color_reset = "\033[0m"; |
||||
|
||||
/* 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}, |
||||
{"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; |
||||
} |
||||
|
||||
/* Get level by name */ |
||||
static debug_level_t get_level_by_name(const char* name) { |
||||
struct { |
||||
const char* n; |
||||
debug_level_t l; |
||||
} map[] = { |
||||
{"none", DEBUG_LEVEL_NONE}, |
||||
{"error", DEBUG_LEVEL_ERROR}, |
||||
{"warn", DEBUG_LEVEL_WARN}, |
||||
{"info", DEBUG_LEVEL_INFO}, |
||||
{"debug", DEBUG_LEVEL_DEBUG}, |
||||
{"trace", DEBUG_LEVEL_TRACE}, |
||||
{NULL, 0} |
||||
}; |
||||
|
||||
for (int i = 0; map[i].n; i++) { |
||||
if (strcasecmp(map[i].n, name) == 0) { |
||||
return map[i].l; |
||||
} |
||||
} |
||||
return DEBUG_LEVEL_NONE; |
||||
} |
||||
|
||||
/* Global debug configuration */ |
||||
debug_config_t g_debug_config; |
||||
FILE* debug_output_file = NULL; |
||||
|
||||
/* Forward declaration for global_config structure */ |
||||
struct global_config { |
||||
char log_file[256]; |
||||
char debug_level[32]; |
||||
uint32_t debug_categories; |
||||
int enable_timestamp; |
||||
int enable_function_names; |
||||
int enable_file_lines; |
||||
int enable_colors; |
||||
}; |
||||
|
||||
/* Apply debug settings from configuration if not overridden by CLI */ |
||||
void debug_apply_config(const struct global_config *config, int cli_override_level, int cli_override_categories) { |
||||
if (!config) return; |
||||
|
||||
// Apply log file if specified and not overridden |
||||
if (config->log_file[0] != '\0') { |
||||
debug_set_output_file(config->log_file); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set log file to: %s", config->log_file); |
||||
} |
||||
|
||||
// Apply debug level from config if not overridden by CLI |
||||
if (!cli_override_level && config->debug_level[0] != '\0') { |
||||
debug_level_t level = DEBUG_LEVEL_INFO; // default |
||||
if (strcasecmp(config->debug_level, "error") == 0) level = DEBUG_LEVEL_ERROR; |
||||
else if (strcasecmp(config->debug_level, "warn") == 0) level = DEBUG_LEVEL_WARN; |
||||
else if (strcasecmp(config->debug_level, "info") == 0) level = DEBUG_LEVEL_INFO; |
||||
else if (strcasecmp(config->debug_level, "debug") == 0) level = DEBUG_LEVEL_DEBUG; |
||||
else if (strcasecmp(config->debug_level, "trace") == 0) level = DEBUG_LEVEL_TRACE; |
||||
|
||||
debug_set_level(level); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug level from config: %s", config->debug_level); |
||||
} |
||||
|
||||
// Apply debug categories from config if not overridden by CLI |
||||
if (!cli_override_categories && config->debug_categories != 0) { |
||||
debug_set_categories(config->debug_categories); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug categories from config: 0x%X", config->debug_categories); |
||||
} |
||||
|
||||
// Apply output options from config |
||||
debug_enable_timestamp(config->enable_timestamp); |
||||
debug_enable_function_name(config->enable_function_names); |
||||
debug_enable_file_line(config->enable_file_lines); |
||||
debug_enable_color(config->enable_colors); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied debug configuration from file"); |
||||
} |
||||
|
||||
/* Initialize debug system with default settings */ |
||||
void debug_config_init(void) { |
||||
g_debug_config.default_level = DEBUG_LEVEL_ERROR; |
||||
g_debug_config.categories = DEBUG_CATEGORY_ALL; |
||||
memset(g_debug_config.category_levels, 0, sizeof(g_debug_config.category_levels)); |
||||
g_debug_config.timestamp_enabled = 1; |
||||
g_debug_config.function_name_enabled = 1; |
||||
g_debug_config.file_line_enabled = 1; |
||||
g_debug_config.color_enabled = 1; |
||||
g_debug_config.max_output_per_second = 0; |
||||
g_debug_config.output_file = NULL; |
||||
|
||||
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { |
||||
fclose(debug_output_file); |
||||
} |
||||
debug_output_file = stdout; |
||||
|
||||
output_count = 0; |
||||
last_output_time = 0; |
||||
} |
||||
|
||||
/* Set debug level */ |
||||
void debug_set_level(debug_level_t level) { |
||||
g_debug_config.default_level = 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(uint32_t categories) { |
||||
g_debug_config.categories = categories; |
||||
} |
||||
|
||||
/* Configure output options */ |
||||
void debug_enable_timestamp(int enable) { |
||||
g_debug_config.timestamp_enabled = enable; |
||||
} |
||||
|
||||
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_enable_color(int enable) { |
||||
g_debug_config.color_enabled = enable; |
||||
} |
||||
|
||||
void debug_set_rate_limit(size_t max_per_second) { |
||||
g_debug_config.max_output_per_second = max_per_second; |
||||
} |
||||
|
||||
void debug_set_output_file(const char* file_path) { |
||||
if (file_path == NULL) { |
||||
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { |
||||
fclose(debug_output_file); |
||||
} |
||||
debug_output_file = stdout; |
||||
g_debug_config.output_file = NULL; |
||||
} else { |
||||
FILE* new_file = fopen(file_path, "a"); |
||||
if (new_file) { |
||||
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { |
||||
fclose(debug_output_file); |
||||
} |
||||
debug_output_file = new_file; |
||||
g_debug_config.output_file = file_path; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* 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; |
||||
} |
||||
|
||||
/* Check if level is sufficient */ |
||||
debug_level_t eff_level = debug_get_effective_level(category); |
||||
if (level > eff_level) { |
||||
return 0; |
||||
} |
||||
|
||||
/* Rate limiting check */ |
||||
if (g_debug_config.max_output_per_second > 0) { |
||||
time_t current_time = time(NULL); |
||||
if (current_time != last_output_time) { |
||||
output_count = 0; |
||||
last_output_time = current_time; |
||||
} |
||||
|
||||
if (output_count >= g_debug_config.max_output_per_second) { |
||||
return 0; |
||||
} |
||||
output_count++; |
||||
} |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
/* Get current debug level for a category */ |
||||
debug_level_t debug_get_effective_level(debug_category_t category) { |
||||
if (category == DEBUG_CATEGORY_ALL || category == DEBUG_CATEGORY_NONE) { |
||||
return g_debug_config.default_level; |
||||
} |
||||
|
||||
/* Assume single category bit */ |
||||
uint32_t cat = category; |
||||
if (__builtin_popcount(cat) != 1) { |
||||
return DEBUG_LEVEL_NONE; /* Not supported for multiple */ |
||||
} |
||||
|
||||
int index = __builtin_ctz(cat); |
||||
if (index >= NUM_DEBUG_CATEGORIES) { |
||||
return DEBUG_LEVEL_NONE; |
||||
} |
||||
|
||||
debug_level_t lev = g_debug_config.category_levels[index]; |
||||
return (lev == DEBUG_LEVEL_NONE) ? g_debug_config.default_level : lev; |
||||
} |
||||
|
||||
/* Get color for debug level */ |
||||
static const char* get_level_color(debug_level_t level) { |
||||
if (!g_debug_config.color_enabled) { |
||||
return ""; |
||||
} |
||||
|
||||
switch (level) { |
||||
case DEBUG_LEVEL_ERROR: return color_red; |
||||
case DEBUG_LEVEL_WARN: return color_yellow; |
||||
case DEBUG_LEVEL_INFO: return color_green; |
||||
case DEBUG_LEVEL_DEBUG: return color_blue; |
||||
case DEBUG_LEVEL_TRACE: return color_magenta; |
||||
default: return ""; |
||||
} |
||||
} |
||||
|
||||
/* 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); |
||||
|
||||
FILE* output = debug_output_file ? debug_output_file : stdout; |
||||
|
||||
/* Add timestamp if enabled */ |
||||
if (g_debug_config.timestamp_enabled) { |
||||
time_t now = time(NULL); |
||||
struct tm* tm_info = localtime(&now); |
||||
char time_str[32]; |
||||
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); |
||||
offset += snprintf(buffer + offset, remaining, "[%s] ", time_str); |
||||
remaining = BUFFER_SIZE - offset; |
||||
} |
||||
|
||||
/* Add level and color */ |
||||
const char* color = get_level_color(level); |
||||
const char* level_name = get_level_name(level); |
||||
if (g_debug_config.color_enabled) { |
||||
offset += snprintf(buffer + offset, remaining, "%s[%s]%s ", color, level_name, color_reset); |
||||
} else { |
||||
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); |
||||
} |
||||
remaining = BUFFER_SIZE - offset; |
||||
|
||||
/* Add category */ |
||||
offset += snprintf(buffer + offset, remaining, "[%d] ", category); |
||||
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 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 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 the entire line at once */ |
||||
fprintf(output, "%s", buffer); |
||||
fflush(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; |
||||
} |
||||
|
||||
char* token = strtok(str, ","); |
||||
while (token) { |
||||
/* Trim leading spaces */ |
||||
while (isspace(*token)) { |
||||
token++; |
||||
} |
||||
|
||||
char* colon = strchr(token, ':'); |
||||
if (!colon) { |
||||
free(str); |
||||
return -1; |
||||
} |
||||
|
||||
*colon = '\0'; |
||||
char* cat_name = token; |
||||
char* level_name = colon + 1; |
||||
|
||||
/* Trim leading spaces in level_name */ |
||||
while (isspace(*level_name)) { |
||||
level_name++; |
||||
} |
||||
|
||||
/* Find category */ |
||||
debug_category_t cat = get_category_by_name(cat_name); |
||||
if (cat == DEBUG_CATEGORY_NONE) { |
||||
free(str); |
||||
return -1; |
||||
} |
||||
|
||||
/* Find level */ |
||||
debug_level_t lev = get_level_by_name(level_name); |
||||
if (lev == DEBUG_LEVEL_NONE) { |
||||
free(str); |
||||
return -1; |
||||
} |
||||
|
||||
if (cat == DEBUG_CATEGORY_ALL) { |
||||
g_debug_config.default_level = lev; |
||||
} else { |
||||
g_debug_config.categories |= cat; |
||||
int index = __builtin_ctz(cat); |
||||
if (index < NUM_DEBUG_CATEGORIES) { |
||||
g_debug_config.category_levels[index] = lev; |
||||
} |
||||
} |
||||
|
||||
token = strtok(NULL, ","); |
||||
} |
||||
|
||||
free(str); |
||||
return 0; |
||||
} |
||||
|
||||
/* Apply debug settings from configuration string values */ |
||||
void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories, |
||||
int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors, |
||||
int cli_override_level, int cli_override_categories) { |
||||
// This function would be implemented to apply individual config values |
||||
// For now, it's a placeholder that could be expanded based on specific needs |
||||
} |
||||
@ -1,137 +0,0 @@
|
||||
/** |
||||
* Runtime debug configuration system |
||||
* Provides flexible control over debug output without recompilation |
||||
*/ |
||||
|
||||
#ifndef DEBUG_CONFIG_H |
||||
#define DEBUG_CONFIG_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include <time.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/* Number of categories (bits 0-10) */ |
||||
#define NUM_DEBUG_CATEGORIES 11 |
||||
|
||||
/* Debug levels */ |
||||
typedef enum { |
||||
DEBUG_LEVEL_NONE = 0, // No debug output |
||||
DEBUG_LEVEL_ERROR = 1, // Errors only |
||||
DEBUG_LEVEL_WARN = 2, // Warnings and errors |
||||
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors |
||||
DEBUG_LEVEL_DEBUG = 4, // Full debug output |
||||
DEBUG_LEVEL_TRACE = 5 // Trace everything |
||||
} debug_level_t; |
||||
|
||||
/* Debug categories - can be combined with bitwise OR */ |
||||
typedef enum { |
||||
DEBUG_CATEGORY_NONE = 0, |
||||
DEBUG_CATEGORY_UASYNC = 1 << 0, // u_async module |
||||
DEBUG_CATEGORY_LL_QUEUE = 1 << 1, // ll_queue module |
||||
DEBUG_CATEGORY_CONNECTION = 1 << 2, // connection module |
||||
DEBUG_CATEGORY_ETCP = 1 << 3, // etcp module |
||||
DEBUG_CATEGORY_CRYPTO = 1 << 4, // crypto operations |
||||
DEBUG_CATEGORY_MEMORY = 1 << 5, // memory management |
||||
DEBUG_CATEGORY_TIMING = 1 << 6, // timing/performance |
||||
DEBUG_CATEGORY_CONFIG = 1 << 7, // configuration parsing |
||||
DEBUG_CATEGORY_TUN = 1 << 8, // TUN interface |
||||
DEBUG_CATEGORY_ROUTING = 1 << 9, // routing table |
||||
DEBUG_CATEGORY_TIMERS = 1 << 10, // timer management |
||||
DEBUG_CATEGORY_ALL = 0xFFFFFFFF |
||||
} debug_category_t; |
||||
|
||||
/* Debug configuration structure */ |
||||
typedef struct { |
||||
debug_level_t default_level; // Default debug level |
||||
uint32_t categories; // Enabled categories (bitmask) |
||||
debug_level_t category_levels[NUM_DEBUG_CATEGORIES]; // Per-category levels (NONE means use default) |
||||
int timestamp_enabled; // Include timestamps in output |
||||
int function_name_enabled; // Include function names |
||||
int file_line_enabled; // Include file:line info |
||||
int color_enabled; // Use ANSI colors (if terminal supports) |
||||
size_t max_output_per_second; // Rate limiting (0 = unlimited) |
||||
const char* output_file; // NULL = stdout, otherwise file path |
||||
} debug_config_t; |
||||
|
||||
/* Global debug configuration */ |
||||
extern debug_config_t g_debug_config; |
||||
|
||||
/* Initialize debug system with default settings */ |
||||
void debug_config_init(void); |
||||
|
||||
/* Set debug level */ |
||||
void debug_set_level(debug_level_t level); |
||||
|
||||
/* Enable/disable specific categories */ |
||||
void debug_enable_category(debug_category_t category); |
||||
void debug_disable_category(debug_category_t category); |
||||
void debug_set_categories(uint32_t categories); |
||||
|
||||
/* Configure output options */ |
||||
void debug_enable_timestamp(int enable); |
||||
void debug_enable_function_name(int enable); |
||||
void debug_enable_file_line(int enable); |
||||
void debug_enable_color(int enable); |
||||
void debug_set_rate_limit(size_t max_per_second); |
||||
void debug_set_output_file(const char* file_path); |
||||
|
||||
/* Parse debug configuration from string (e.g., "uasync:debug,ll_queue:info") */ |
||||
int debug_parse_config(const char* config_string); |
||||
|
||||
#include <stdio.h> |
||||
extern FILE* debug_output_file; |
||||
|
||||
/* Check if debug output should be shown for given level and category */ |
||||
int debug_should_output(debug_level_t level, debug_category_t category); |
||||
|
||||
/* Get current debug level for a category */ |
||||
debug_level_t debug_get_effective_level(debug_category_t category); |
||||
|
||||
/* Format and output debug message (internal use by macros) */ |
||||
void debug_output(debug_level_t level, debug_category_t category, |
||||
const char* function, const char* file, int line, |
||||
const char* format, ...); |
||||
|
||||
/* Convenience macros for debug output */ |
||||
#define DEBUG_ERROR(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_ERROR, category)) { \ |
||||
debug_output(DEBUG_LEVEL_ERROR, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ |
||||
} } while(0) |
||||
|
||||
#define DEBUG_WARN(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_WARN, category)) { \ |
||||
debug_output(DEBUG_LEVEL_WARN, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ |
||||
} } while(0) |
||||
|
||||
#define DEBUG_INFO(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_INFO, category)) { \ |
||||
debug_output(DEBUG_LEVEL_INFO, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ |
||||
} } while(0) |
||||
|
||||
#define DEBUG_DEBUG(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_DEBUG, category)) { \ |
||||
debug_output(DEBUG_LEVEL_DEBUG, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ |
||||
} } while(0) |
||||
|
||||
#define DEBUG_TRACE(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_TRACE, category)) { \ |
||||
debug_output(DEBUG_LEVEL_TRACE, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ |
||||
} } while(0) |
||||
|
||||
/* Backward compatibility - default to ERROR level if not specified */ |
||||
#define DEBUG_OUTPUT(fmt, ...) DEBUG_ERROR(DEBUG_CATEGORY_ALL, fmt, ##__VA_ARGS__) |
||||
|
||||
/* Apply debug settings from configuration string values */ |
||||
void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories, |
||||
int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors, |
||||
int cli_override_level, int cli_override_categories); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif // DEBUG_CONFIG_H |
||||
@ -1,131 +0,0 @@
|
||||
// timeout_heap.c |
||||
|
||||
#include "timeout_heap.h" |
||||
#include "debug_config.h" |
||||
#include <stdlib.h> |
||||
#include <stdio.h> // For potential error printing, optional |
||||
|
||||
// Helper macros for 1-based indices |
||||
#define PARENT(i) ((i) / 2) |
||||
#define LEFT_CHILD(i) (2 * (i)) |
||||
#define RIGHT_CHILD(i) (2 * (i) + 1) |
||||
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) { |
||||
TimeoutHeap *h = malloc(sizeof(TimeoutHeap)); |
||||
if (!h) return NULL; |
||||
h->heap = malloc(sizeof(TimeoutEntry) * initial_capacity); |
||||
if (!h->heap) { |
||||
free(h); |
||||
return NULL; |
||||
} |
||||
h->size = 0; |
||||
h->capacity = initial_capacity; |
||||
return h; |
||||
} |
||||
|
||||
void timeout_heap_destroy(TimeoutHeap *h) { |
||||
if (h) { |
||||
free(h->heap); |
||||
free(h); |
||||
} |
||||
} |
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) { |
||||
// i is 1-based |
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) { |
||||
// Swap with parent |
||||
TimeoutEntry temp = h->heap[PARENT(i) - 1]; |
||||
h->heap[PARENT(i) - 1] = h->heap[i - 1]; |
||||
h->heap[i - 1] = temp; |
||||
i = PARENT(i); |
||||
} |
||||
} |
||||
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) { |
||||
if (h->size == h->capacity) { |
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1; |
||||
TimeoutEntry *new_heap = realloc(h->heap, sizeof(TimeoutEntry) * new_cap); |
||||
if (!new_heap) return -1; // Allocation failed |
||||
h->heap = new_heap; |
||||
h->capacity = new_cap; |
||||
} |
||||
|
||||
// Insert at end (0-based) |
||||
size_t idx = h->size++; |
||||
h->heap[idx].expiration = expiration; |
||||
h->heap[idx].data = data; |
||||
|
||||
// Bubble up (1-based) |
||||
bubble_up(h, idx + 1); |
||||
return 0; |
||||
} |
||||
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) { |
||||
// i is 1-based |
||||
while (1) { |
||||
size_t smallest = i; |
||||
size_t left = LEFT_CHILD(i); |
||||
size_t right = RIGHT_CHILD(i); |
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) { |
||||
smallest = left; |
||||
} |
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) { |
||||
smallest = right; |
||||
} |
||||
if (smallest == i) break; |
||||
|
||||
// Swap |
||||
TimeoutEntry temp = h->heap[smallest - 1]; |
||||
h->heap[smallest - 1] = h->heap[i - 1]; |
||||
h->heap[i - 1] = temp; |
||||
i = smallest; |
||||
} |
||||
} |
||||
|
||||
static void remove_root(TimeoutHeap *h) { |
||||
if (h->size == 0) return; |
||||
|
||||
// Move last to root |
||||
h->heap[0] = h->heap[--h->size]; |
||||
|
||||
// Heapify down (1-based) |
||||
if (h->size > 0) { |
||||
heapify_down(h, 1); |
||||
} |
||||
} |
||||
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) { |
||||
if (h->size == 0) return -1; |
||||
*out = h->heap[0]; |
||||
return 0; |
||||
} |
||||
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { |
||||
if (h->size == 0) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: entering, size=%zu", h->size); |
||||
|
||||
*out = h->heap[0]; |
||||
remove_root(h); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: returning element, data=%p", out->data); |
||||
return 0; |
||||
} |
||||
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) { |
||||
// Linear search for the entry |
||||
for (size_t i = 0; i < h->size; ++i) { |
||||
if (h->heap[i].expiration == expiration && h->heap[i].data == data) { |
||||
// Remove by swapping with last and heapify down |
||||
if (i != h->size - 1) { |
||||
h->heap[i] = h->heap[h->size - 1]; |
||||
// Heapify down from the position (1-based) |
||||
heapify_down(h, i + 1); |
||||
} |
||||
h->size--; |
||||
return 0; |
||||
} |
||||
} |
||||
return -1; // Not found |
||||
} |
||||
@ -1,170 +0,0 @@
|
||||
// Updated timeout_heap.c |
||||
|
||||
#include "timeout_heap.h" |
||||
#include "debug_config.h" |
||||
#include <stdlib.h> |
||||
#include <stdio.h> // For potential error printing, optional |
||||
#include <limits.h> // For SIZE_MAX |
||||
|
||||
// Helper macros for 1-based indices |
||||
#define PARENT(i) ((i) / 2) |
||||
#define LEFT_CHILD(i) (2 * (i)) |
||||
#define RIGHT_CHILD(i) (2 * (i) + 1) |
||||
|
||||
// Forward declaration of timeout_node (from u_async.h, but included here for type safety) |
||||
struct timeout_node; |
||||
|
||||
// Private helpers |
||||
static void update_index(void *data, size_t idx) { |
||||
if (data) { |
||||
((struct timeout_node *)data)->heap_index = idx; |
||||
} |
||||
} |
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) { |
||||
// i is 1-based |
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) { |
||||
// Swap with parent |
||||
TimeoutEntry temp = h->heap[PARENT(i) - 1]; |
||||
h->heap[PARENT(i) - 1] = h->heap[i - 1]; |
||||
h->heap[i - 1] = temp; |
||||
|
||||
// Update indices |
||||
update_index(h->heap[PARENT(i) - 1].data, PARENT(i) - 1); |
||||
update_index(h->heap[i - 1].data, i - 1); |
||||
|
||||
i = PARENT(i); |
||||
} |
||||
} |
||||
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) { |
||||
// i is 1-based |
||||
while (1) { |
||||
size_t smallest = i; |
||||
size_t left = LEFT_CHILD(i); |
||||
size_t right = RIGHT_CHILD(i); |
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) { |
||||
smallest = left; |
||||
} |
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) { |
||||
smallest = right; |
||||
} |
||||
if (smallest == i) break; |
||||
|
||||
// Swap |
||||
TimeoutEntry temp = h->heap[smallest - 1]; |
||||
h->heap[smallest - 1] = h->heap[i - 1]; |
||||
h->heap[i - 1] = temp; |
||||
|
||||
// Update indices |
||||
update_index(h->heap[smallest - 1].data, smallest - 1); |
||||
update_index(h->heap[i - 1].data, i - 1); |
||||
|
||||
i = smallest; |
||||
} |
||||
} |
||||
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) { |
||||
TimeoutHeap *h = malloc(sizeof(TimeoutHeap)); |
||||
if (!h) return NULL; |
||||
h->heap = malloc(sizeof(TimeoutEntry) * initial_capacity); |
||||
if (!h->heap) { |
||||
free(h); |
||||
return NULL; |
||||
} |
||||
h->size = 0; |
||||
h->capacity = initial_capacity; |
||||
return h; |
||||
} |
||||
|
||||
void timeout_heap_destroy(TimeoutHeap *h) { |
||||
if (h) { |
||||
free(h->heap); |
||||
free(h); |
||||
} |
||||
} |
||||
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) { |
||||
if (h->size == h->capacity) { |
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1; |
||||
TimeoutEntry *new_heap = realloc(h->heap, sizeof(TimeoutEntry) * new_cap); |
||||
if (!new_heap) return -1; // Allocation failed |
||||
h->heap = new_heap; |
||||
h->capacity = new_cap; |
||||
} |
||||
|
||||
// Insert at end (0-based) |
||||
size_t idx = h->size++; |
||||
h->heap[idx].expiration = expiration; |
||||
h->heap[idx].data = data; |
||||
|
||||
// Set initial index |
||||
update_index(data, idx); |
||||
|
||||
// Bubble up (1-based) |
||||
bubble_up(h, idx + 1); |
||||
return 0; |
||||
} |
||||
|
||||
static void remove_root(TimeoutHeap *h) { |
||||
if (h->size == 0) return; |
||||
|
||||
// Move last to root |
||||
size_t last = --h->size; |
||||
h->heap[0] = h->heap[last]; |
||||
|
||||
// Update index for new root |
||||
update_index(h->heap[0].data, 0); |
||||
|
||||
// Heapify down (1-based) |
||||
if (h->size > 0) { |
||||
heapify_down(h, 1); |
||||
} |
||||
} |
||||
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) { |
||||
if (h->size == 0) return -1; |
||||
*out = h->heap[0]; |
||||
return 0; |
||||
} |
||||
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { |
||||
if (h->size == 0) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: entering, size=%zu", h->size); |
||||
|
||||
*out = h->heap[0]; |
||||
remove_root(h); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: returning element, data=%p", out->data); |
||||
return 0; |
||||
} |
||||
|
||||
int timeout_heap_remove(TimeoutHeap *h, void *data) { |
||||
struct timeout_node *node = (struct timeout_node *)data; |
||||
size_t idx = node->heap_index; |
||||
if (idx == SIZE_MAX || idx >= h->size || h->heap[idx].data != data) { |
||||
return -1; // Not found or invalid |
||||
} |
||||
|
||||
// Swap with last |
||||
size_t last = --h->size; |
||||
if (idx != last) { |
||||
TimeoutEntry temp = h->heap[idx]; |
||||
h->heap[idx] = h->heap[last]; |
||||
h->heap[last] = temp; |
||||
|
||||
// Update indices |
||||
update_index(h->heap[idx].data, idx); |
||||
update_index(h->heap[last].data, last); |
||||
|
||||
// Heapify down and up to restore property |
||||
heapify_down(h, idx + 1); |
||||
bubble_up(h, idx + 1); |
||||
} |
||||
|
||||
// Invalidate index |
||||
node->heap_index = SIZE_MAX; |
||||
return 0; |
||||
} |
||||
@ -1,75 +0,0 @@
|
||||
// timeout_heap.h |
||||
|
||||
#ifndef TIMEOUT_HEAP_H |
||||
#define TIMEOUT_HEAP_H |
||||
|
||||
#include <stdint.h> // For uint64_t |
||||
#include <stddef.h> // For size_t |
||||
|
||||
typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now |
||||
|
||||
typedef struct { |
||||
TimeoutTime expiration; // Sort key (smaller = earlier) |
||||
void *data; // User data (e.g., callback or ID) |
||||
} TimeoutEntry; // Removed deleted flag |
||||
|
||||
typedef struct TimeoutHeap TimeoutHeap; |
||||
|
||||
struct TimeoutHeap { |
||||
TimeoutEntry *heap; // Dynamic array |
||||
size_t size; // Current number of elements |
||||
size_t capacity; // Allocated size |
||||
// Removed freed_count and free_callback |
||||
}; |
||||
|
||||
/** |
||||
* Create a new timeout heap with initial capacity. |
||||
* @param initial_capacity Starting capacity (will grow as needed). |
||||
* @return Pointer to the heap, or NULL on failure. |
||||
*/ |
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity); |
||||
|
||||
/** |
||||
* Destroy the timeout heap and free resources (but not the data pointers). |
||||
* @param h The heap to destroy. |
||||
*/ |
||||
void timeout_heap_destroy(TimeoutHeap *h); |
||||
|
||||
/** |
||||
* Insert a new timeout into the heap. |
||||
* @param h The heap. |
||||
* @param expiration The expiration time. |
||||
* @param data User data associated with the timeout. |
||||
* @return 0 on success, -1 on allocation failure. |
||||
*/ |
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data); |
||||
|
||||
/** |
||||
* Peek at the earliest timeout without removing it. |
||||
* @param h The heap. |
||||
* @param out Where to store the entry. |
||||
* @return 0 on success, -1 if empty. |
||||
*/ |
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out); |
||||
|
||||
/** |
||||
* Pop the earliest timeout from the heap. |
||||
* @param h The heap. |
||||
* @param out Where to store the entry. |
||||
* @return 0 on success, -1 if empty. |
||||
*/ |
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out); |
||||
|
||||
/** |
||||
* Remove a timeout by matching expiration and data (immediate removal). |
||||
* Scans the heap linearly, O(n) time. |
||||
* Assumes combinations are unique; removes the first match. |
||||
* Does not free the data — caller should do it if success. |
||||
* @param h The heap. |
||||
* @param expiration The expiration time to match. |
||||
* @param data The data to match. |
||||
* @return 0 if found and removed, -1 if not found. |
||||
*/ |
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data); |
||||
|
||||
#endif // TIMEOUT_HEAP_H |
||||
@ -1,834 +0,0 @@
|
||||
// uasync.c |
||||
|
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <poll.h> |
||||
#include <limits.h> |
||||
#include <fcntl.h> |
||||
|
||||
|
||||
|
||||
// Timeout node with safe cancellation |
||||
struct timeout_node { |
||||
void* arg; |
||||
timeout_callback_t callback; |
||||
uint64_t expiration_ms; // absolute expiration time in milliseconds |
||||
struct UASYNC* ua; // Pointer back to uasync instance for counter updates |
||||
int cancelled; // Cancellation flag |
||||
}; |
||||
|
||||
// Socket node with array-based storage |
||||
struct socket_node { |
||||
int fd; |
||||
socket_callback_t read_cbk; |
||||
socket_callback_t write_cbk; |
||||
socket_callback_t except_cbk; |
||||
void* user_data; |
||||
int active; // 1 if socket is active, 0 if freed (for reuse) |
||||
}; |
||||
|
||||
// Array-based socket management for O(1) operations |
||||
struct socket_array { |
||||
struct socket_node* sockets; // Dynamic array of socket nodes |
||||
int* fd_to_index; // FD to array index mapping |
||||
int* index_to_fd; // Array index to FD mapping |
||||
int capacity; // Total allocated capacity |
||||
int count; // Number of active sockets |
||||
int max_fd; // Maximum FD for bounds checking |
||||
}; |
||||
|
||||
static struct socket_array* socket_array_create(int initial_capacity); |
||||
static void socket_array_destroy(struct socket_array* sa); |
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); |
||||
static int socket_array_remove(struct socket_array* sa, int fd); |
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd); |
||||
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds); |
||||
|
||||
// No global instance - each module must use its own struct UASYNC instance |
||||
|
||||
// Array-based socket management implementation |
||||
static struct socket_array* socket_array_create(int initial_capacity) { |
||||
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity |
||||
|
||||
struct socket_array* sa = malloc(sizeof(struct socket_array)); |
||||
if (!sa) return NULL; |
||||
|
||||
sa->sockets = calloc(initial_capacity, sizeof(struct socket_node)); |
||||
sa->fd_to_index = calloc(initial_capacity, sizeof(int)); |
||||
sa->index_to_fd = calloc(initial_capacity, sizeof(int)); |
||||
|
||||
if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) { |
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
return NULL; |
||||
} |
||||
|
||||
// Initialize mapping arrays to -1 (invalid) |
||||
for (int i = 0; i < initial_capacity; i++) { |
||||
sa->fd_to_index[i] = -1; |
||||
sa->index_to_fd[i] = -1; |
||||
sa->sockets[i].fd = -1; |
||||
sa->sockets[i].active = 0; |
||||
} |
||||
|
||||
sa->capacity = initial_capacity; |
||||
sa->count = 0; |
||||
sa->max_fd = -1; |
||||
|
||||
return sa; |
||||
} |
||||
|
||||
static void socket_array_destroy(struct socket_array* sa) { |
||||
if (!sa) return; |
||||
|
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
} |
||||
|
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1; |
||||
if (fd >= sa->capacity) { |
||||
// Need to resize - double the capacity |
||||
int new_capacity = sa->capacity * 2; |
||||
if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space |
||||
|
||||
struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node)); |
||||
int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int)); |
||||
int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int)); |
||||
|
||||
if (!new_sockets || !new_fd_to_index || !new_index_to_fd) { |
||||
// Allocation failed |
||||
free(new_sockets); |
||||
free(new_fd_to_index); |
||||
free(new_index_to_fd); |
||||
return -1; |
||||
} |
||||
|
||||
// Initialize new elements |
||||
for (int i = sa->capacity; i < new_capacity; i++) { |
||||
new_fd_to_index[i] = -1; |
||||
new_index_to_fd[i] = -1; |
||||
new_sockets[i].fd = -1; |
||||
new_sockets[i].active = 0; |
||||
} |
||||
|
||||
sa->sockets = new_sockets; |
||||
sa->fd_to_index = new_fd_to_index; |
||||
sa->index_to_fd = new_index_to_fd; |
||||
sa->capacity = new_capacity; |
||||
} |
||||
|
||||
// Check if FD already exists |
||||
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists |
||||
|
||||
// Find first free slot |
||||
int index = -1; |
||||
for (int i = 0; i < sa->capacity; i++) { |
||||
if (!sa->sockets[i].active) { |
||||
index = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (index == -1) return -1; // No free slots (shouldn't happen) |
||||
|
||||
// Add the socket |
||||
sa->sockets[index].fd = fd; |
||||
sa->sockets[index].read_cbk = read_cbk; |
||||
sa->sockets[index].write_cbk = write_cbk; |
||||
sa->sockets[index].except_cbk = except_cbk; |
||||
sa->sockets[index].user_data = user_data; |
||||
sa->sockets[index].active = 1; |
||||
|
||||
sa->fd_to_index[fd] = index; |
||||
sa->index_to_fd[index] = fd; |
||||
sa->count++; |
||||
|
||||
if (fd > sa->max_fd) sa->max_fd = fd; |
||||
|
||||
return index; |
||||
} |
||||
|
||||
static int socket_array_remove(struct socket_array* sa, int fd) { |
||||
if (!sa || fd < 0 || fd >= sa->capacity) return -1; |
||||
|
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found |
||||
|
||||
// Mark as inactive |
||||
sa->sockets[index].active = 0; |
||||
sa->sockets[index].fd = -1; |
||||
sa->fd_to_index[fd] = -1; |
||||
sa->index_to_fd[index] = -1; |
||||
sa->count--; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { |
||||
if (!sa || fd < 0 || fd >= sa->capacity) return NULL; |
||||
|
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1 || !sa->sockets[index].active) return NULL; |
||||
|
||||
return &sa->sockets[index]; |
||||
} |
||||
|
||||
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds) { |
||||
if (!sa || !fds || max_fds <= 0) return 0; |
||||
|
||||
int count = 0; |
||||
for (int i = 0; i < sa->capacity && count < max_fds; i++) { |
||||
if (sa->sockets[i].active) { |
||||
fds[count].fd = sa->sockets[i].fd; |
||||
fds[count].events = 0; |
||||
if (sa->sockets[i].read_cbk) fds[count].events |= POLLIN; |
||||
if (sa->sockets[i].write_cbk) fds[count].events |= POLLOUT; |
||||
if (sa->sockets[i].except_cbk) fds[count].events |= POLLERR; |
||||
fds[count].revents = 0; |
||||
count++; |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// Callback to free timeout node and update counters |
||||
static void timeout_node_free_callback(void* user_data, void* data) { |
||||
struct UASYNC* ua = (struct UASYNC*)user_data; |
||||
struct timeout_node* node = (struct timeout_node*)data; |
||||
(void)node; // Not used directly, but keep for consistency |
||||
ua->timer_free_count++; |
||||
free(data); |
||||
} |
||||
|
||||
// Helper to get current time |
||||
static void get_current_time(struct timeval* tv) { |
||||
gettimeofday(tv, NULL); |
||||
} |
||||
|
||||
|
||||
|
||||
// Drain wakeup pipe - read all available bytes |
||||
static void drain_wakeup_pipe(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return; |
||||
|
||||
char buf[64]; |
||||
while (1) { |
||||
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf)); |
||||
if (n <= 0) break; |
||||
} |
||||
} |
||||
|
||||
// Helper to add timeval: tv += dt (timebase units) |
||||
static void timeval_add_tb(struct timeval* tv, int dt) { |
||||
tv->tv_usec += (dt % 10000) * 100; |
||||
tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; |
||||
tv->tv_usec %= 1000000; |
||||
} |
||||
|
||||
// Convert timeval to milliseconds (uint64_t) |
||||
static uint64_t timeval_to_ms(const struct timeval* tv) { |
||||
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; |
||||
} |
||||
|
||||
|
||||
|
||||
// Simplified timeout handling without reference counting |
||||
|
||||
// Process expired timeouts with safe cancellation |
||||
static void process_timeouts(struct UASYNC* ua) { |
||||
if (!ua || !ua->timeout_heap) return; |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
while (1) { |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; |
||||
if (entry.expiration > now_ms) break; |
||||
|
||||
// Pop the expired timeout |
||||
timeout_heap_pop(ua->timeout_heap, &entry); |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
|
||||
if (node && node->callback && !node->cancelled) { |
||||
// Execute callback only if not cancelled |
||||
node->callback(node->arg); |
||||
} |
||||
|
||||
// Always free the node after processing |
||||
if (node && node->ua) { |
||||
node->ua->timer_free_count++; |
||||
} |
||||
free(node); |
||||
} |
||||
} |
||||
|
||||
// Compute time to next timeout |
||||
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) { |
||||
if (!ua || !ua->timeout_heap) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
if (entry.expiration <= now_ms) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
uint64_t delta_ms = entry.expiration - now_ms; |
||||
if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow |
||||
delta_ms = 86400000; |
||||
} |
||||
tv->tv_sec = delta_ms / 1000; |
||||
tv->tv_usec = (delta_ms % 1000) * 1000; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { |
||||
if (!ua || timeout_tb < 0 || !callback) return NULL; |
||||
if (!ua->timeout_heap) return NULL; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", |
||||
ua, timeout_tb, arg, callback); |
||||
|
||||
struct timeout_node* node = malloc(sizeof(struct timeout_node)); |
||||
if (!node) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); |
||||
return NULL; |
||||
} |
||||
ua->timer_alloc_count++; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", |
||||
node, ua->timer_alloc_count); |
||||
|
||||
node->arg = arg; |
||||
node->callback = callback; |
||||
node->ua = ua; |
||||
node->cancelled = 0; |
||||
|
||||
// Calculate expiration time in milliseconds |
||||
struct timeval now; |
||||
get_current_time(&now); |
||||
timeval_add_tb(&now, timeout_tb); |
||||
node->expiration_ms = timeval_to_ms(&now); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", |
||||
node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Add to heap |
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); |
||||
free(node); |
||||
ua->timer_free_count++; // Balance the alloc counter |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); |
||||
return node; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { |
||||
if (!ua || !t_id || !ua->timeout_heap) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", |
||||
ua, t_id, ua ? ua->timeout_heap : NULL); |
||||
return ERR_FAIL; |
||||
} |
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", |
||||
ua, t_id, node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Try to cancel from heap first |
||||
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { |
||||
// Successfully removed from heap - mark as cancelled |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap", node); |
||||
return ERR_OK; |
||||
|
||||
// If not found in heap (maybe already expired and being processed) |
||||
// We still need to mark it as cancelled to prevent callback execution |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: timer %p not found in heap, marking as cancelled", node); |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
return ERR_OK; |
||||
} // Successfully cancelled (marked) |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check |
||||
|
||||
int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data); |
||||
if (index < 0) return NULL; |
||||
|
||||
ua->socket_alloc_count++; |
||||
|
||||
// Return pointer to the socket node (same as before for API compatibility) |
||||
return &ua->sockets->sockets[index]; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { |
||||
if (!ua || !s_id) return ERR_FAIL; |
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id; |
||||
if (node->fd < 0) return ERR_FAIL; // Invalid node |
||||
|
||||
int result = socket_array_remove(ua->sockets, node->fd); |
||||
if (result != 0) return ERR_FAIL; |
||||
|
||||
ua->socket_free_count++; |
||||
return ERR_OK; |
||||
} |
||||
|
||||
|
||||
|
||||
void uasync_mainloop(struct UASYNC* ua) { |
||||
while (1) { |
||||
uasync_poll(ua, -1); /* infinite timeout */ |
||||
} |
||||
} |
||||
|
||||
// Instance version |
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) { |
||||
if (!ua) return; |
||||
|
||||
/* Process expired timeouts */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Compute timeout for poll in milliseconds */ |
||||
int timeout_ms = -1; // infinite by default |
||||
|
||||
// Get next timeout from heap |
||||
struct timeval tv; |
||||
get_next_timeout(ua, &tv); |
||||
|
||||
if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) { |
||||
// Convert timeval to milliseconds, cap at INT_MAX |
||||
uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; |
||||
if (ms > INT_MAX) ms = INT_MAX; |
||||
timeout_ms = (int)ms; |
||||
} |
||||
|
||||
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ |
||||
if (timeout_tb >= 0) { |
||||
// Convert timebase (0.1 ms) to milliseconds |
||||
int user_timeout_ms = timeout_tb / 10; |
||||
if (timeout_tb % 10 != 0) user_timeout_ms++; // round up |
||||
|
||||
if (timeout_ms < 0 || user_timeout_ms < timeout_ms) { |
||||
timeout_ms = user_timeout_ms; |
||||
} |
||||
} |
||||
|
||||
/* Build pollfd array from socket array - O(1) per socket */ |
||||
int socket_count = ua->sockets ? ua->sockets->count : 0; |
||||
int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0; |
||||
int total_fds = socket_count + wakeup_fd_present; |
||||
|
||||
if (total_fds == 0) { |
||||
/* No sockets and no wakeup fd, just wait for timeout */ |
||||
if (timeout_ms >= 0) { |
||||
/* usleep would be better but we just call poll with empty set */ |
||||
struct pollfd dummy; |
||||
poll(&dummy, 0, timeout_ms); |
||||
} else { |
||||
/* Infinite timeout with no sockets - should not happen in practice */ |
||||
return; |
||||
} |
||||
/* Check timeouts again after sleep */ |
||||
process_timeouts(ua); |
||||
return; |
||||
} |
||||
|
||||
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); |
||||
struct socket_node** nodes = NULL; |
||||
if (socket_count > 0) { |
||||
nodes = malloc(socket_count * sizeof(struct socket_node*)); |
||||
} |
||||
if (!fds || (socket_count > 0 && !nodes)) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; /* out of memory */ |
||||
} |
||||
|
||||
/* Fill arrays */ |
||||
int idx = 0; |
||||
|
||||
/* Add wakeup fd first if present */ |
||||
if (wakeup_fd_present) { |
||||
fds[idx].fd = ua->wakeup_pipe[0]; |
||||
fds[idx].events = POLLIN; |
||||
fds[idx].revents = 0; |
||||
idx++; |
||||
} |
||||
|
||||
/* Add socket fds using efficient array traversal */ |
||||
int node_idx = 0; |
||||
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
struct socket_node* cur = &ua->sockets->sockets[i]; |
||||
fds[idx].fd = cur->fd; |
||||
fds[idx].events = 0; |
||||
fds[idx].revents = 0; |
||||
|
||||
if (cur->read_cbk) fds[idx].events |= POLLIN; |
||||
if (cur->write_cbk) fds[idx].events |= POLLOUT; |
||||
if (cur->except_cbk) fds[idx].events |= POLLPRI; |
||||
|
||||
if (nodes) { |
||||
nodes[node_idx] = cur; |
||||
} |
||||
idx++; |
||||
node_idx++; |
||||
} |
||||
} |
||||
|
||||
/* Call poll */ |
||||
int ret = poll(fds, total_fds, timeout_ms); |
||||
if (ret < 0) { |
||||
if (errno == EINTR) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
perror("poll"); |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
|
||||
/* Process timeouts that may have expired during poll */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Process socket events */ |
||||
if (ret > 0) { |
||||
for (int i = 0; i < total_fds; i++) { |
||||
if (fds[i].revents == 0) continue; |
||||
|
||||
/* Handle wakeup fd separately */ |
||||
if (wakeup_fd_present && i == 0) { |
||||
if (fds[i].revents & POLLIN) { |
||||
drain_wakeup_pipe(ua); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
/* Socket event */ |
||||
int socket_idx = i - wakeup_fd_present; |
||||
struct socket_node* node = nodes[socket_idx]; |
||||
|
||||
/* Check for error conditions first */ |
||||
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { |
||||
/* Treat as exceptional condition */ |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Exceptional data (out-of-band) */ |
||||
if (fds[i].revents & POLLPRI) { |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Read readiness */ |
||||
if (fds[i].revents & POLLIN) { |
||||
if (node->read_cbk) { |
||||
node->read_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Write readiness */ |
||||
if (fds[i].revents & POLLOUT) { |
||||
if (node->write_cbk) { |
||||
node->write_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
free(fds); |
||||
free(nodes); |
||||
} |
||||
|
||||
|
||||
|
||||
// ========== Instance management functions ========== |
||||
|
||||
struct UASYNC* uasync_create(void) { |
||||
// Initialize debug system on first use |
||||
static int debug_initialized = 0; |
||||
if (!debug_initialized) { |
||||
debug_config_init(); |
||||
debug_initialized = 1; |
||||
} |
||||
|
||||
struct UASYNC* ua = malloc(sizeof(struct UASYNC)); |
||||
if (!ua) return NULL; |
||||
|
||||
memset(ua, 0, sizeof(struct UASYNC)); |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
ua->wakeup_initialized = 0; |
||||
|
||||
// Create wakeup pipe |
||||
if (pipe(ua->wakeup_pipe) < 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); |
||||
// Continue without wakeup mechanism |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
} else { |
||||
ua->wakeup_initialized = 1; |
||||
// Set non-blocking on read end to avoid blocking if pipe is full |
||||
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); |
||||
if (flags >= 0) { |
||||
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); |
||||
} |
||||
} |
||||
|
||||
ua->sockets = socket_array_create(16); |
||||
if (!ua->sockets) { |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (!ua->timeout_heap) { |
||||
socket_array_destroy(ua->sockets); |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
// Set callback to free timeout nodes and update counters |
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); |
||||
|
||||
return ua; |
||||
} |
||||
|
||||
// Print all resources for debugging |
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix) { |
||||
if (!ua) { |
||||
printf("%s: NULL uasync instance\n", prefix); |
||||
return; |
||||
} |
||||
|
||||
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); |
||||
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
|
||||
// Показать активные таймеры |
||||
if (ua->timeout_heap) { |
||||
size_t active_timers = 0; |
||||
TimeoutEntry entry; |
||||
// Создаем временную копию кучи для подсчета |
||||
TimeoutHeap* temp_heap = timeout_heap_create(16); |
||||
if (temp_heap) { |
||||
// Копируем все активные таймеры |
||||
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) { |
||||
if (!entry.deleted) { |
||||
active_timers++; |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", |
||||
node, (unsigned long long)entry.expiration, node->cancelled); |
||||
} |
||||
timeout_heap_push(temp_heap, entry.expiration, entry.data); |
||||
} |
||||
// Возвращаем таймеры обратно |
||||
while (timeout_heap_pop(temp_heap, &entry) == 0) { |
||||
timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data); |
||||
} |
||||
timeout_heap_destroy(temp_heap); |
||||
} |
||||
printf(" Active timers in heap: %zu\n", active_timers); |
||||
} |
||||
|
||||
// Показать активные сокеты |
||||
if (ua->sockets) { |
||||
int active_sockets = 0; |
||||
printf(" Socket array capacity: %d, active: %d\n", |
||||
ua->sockets->capacity, ua->sockets->count); |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
active_sockets++; |
||||
printf(" Socket: fd=%d, active=%d\n", |
||||
ua->sockets->sockets[i].fd, |
||||
ua->sockets->sockets[i].active); |
||||
} |
||||
} |
||||
printf(" Total active sockets: %d\n", active_sockets); |
||||
} |
||||
|
||||
printf("🔚 %s: End of resource report\n\n", prefix); |
||||
} |
||||
|
||||
void uasync_destroy(struct UASYNC* ua) { |
||||
if (!ua) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); |
||||
|
||||
// Диагностика ресурсов перед очисткой |
||||
uasync_print_resources(ua, "BEFORE_DESTROY"); |
||||
|
||||
// Check for potential memory leaks |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
// Continue cleanup, will abort after if leaks remain |
||||
} |
||||
|
||||
// Free all remaining timeouts |
||||
if (ua->timeout_heap) { |
||||
size_t freed_count = 0; |
||||
while (1) { |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break; |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)", |
||||
node, (unsigned long long)entry.expiration); |
||||
ua->timer_free_count++; |
||||
freed_count++; |
||||
free(node); |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu", |
||||
freed_count, ua->timeout_heap->freed_count); |
||||
timeout_heap_destroy(ua->timeout_heap); |
||||
} |
||||
|
||||
// Free all socket nodes using array approach |
||||
if (ua->sockets) { |
||||
// Count and free all active sockets |
||||
int freed_count = 0; |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
ua->socket_free_count++; |
||||
freed_count++; |
||||
} |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); |
||||
socket_array_destroy(ua->sockets); |
||||
} |
||||
|
||||
// Close wakeup pipe |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
|
||||
// Final leak check |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
abort(); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); |
||||
free(ua); |
||||
} |
||||
|
||||
void uasync_init_instance(struct UASYNC* ua) { |
||||
if (!ua) return; |
||||
|
||||
// Initialize socket array if not present |
||||
if (!ua->sockets) { |
||||
ua->sockets = socket_array_create(16); |
||||
} |
||||
|
||||
if (!ua->timeout_heap) { |
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (ua->timeout_heap) { |
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Debug statistics |
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { |
||||
if (!ua) return; |
||||
if (timer_alloc) *timer_alloc = ua->timer_alloc_count; |
||||
if (timer_free) *timer_free = ua->timer_free_count; |
||||
if (socket_alloc) *socket_alloc = ua->socket_alloc_count; |
||||
if (socket_free) *socket_free = ua->socket_free_count; |
||||
} |
||||
|
||||
// Get global instance for backward compatibility |
||||
|
||||
// Wakeup mechanism |
||||
int uasync_wakeup(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
|
||||
char byte = 0; |
||||
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); |
||||
if (ret != 1) { |
||||
// Don't print error from signal handler |
||||
return -1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
return ua->wakeup_pipe[1]; |
||||
} |
||||
|
||||
@ -1,830 +0,0 @@
|
||||
// uasync.c |
||||
|
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <poll.h> |
||||
#include <limits.h> |
||||
#include <fcntl.h> |
||||
|
||||
|
||||
|
||||
// Timeout node with safe cancellation |
||||
struct timeout_node { |
||||
void* arg; |
||||
timeout_callback_t callback; |
||||
uint64_t expiration_ms; // absolute expiration time in milliseconds |
||||
struct UASYNC* ua; // Pointer back to uasync instance for counter updates |
||||
int cancelled; // Cancellation flag |
||||
}; |
||||
|
||||
// Socket node with array-based storage |
||||
struct socket_node { |
||||
int fd; |
||||
socket_callback_t read_cbk; |
||||
socket_callback_t write_cbk; |
||||
socket_callback_t except_cbk; |
||||
void* user_data; |
||||
int active; // 1 if socket is active, 0 if freed (for reuse) |
||||
}; |
||||
|
||||
// Array-based socket management for O(1) operations |
||||
struct socket_array { |
||||
struct socket_node* sockets; // Dynamic array of socket nodes |
||||
int* fd_to_index; // FD to array index mapping |
||||
int* index_to_fd; // Array index to FD mapping |
||||
int capacity; // Total allocated capacity |
||||
int count; // Number of active sockets |
||||
int max_fd; // Maximum FD for bounds checking |
||||
}; |
||||
|
||||
static struct socket_array* socket_array_create(int initial_capacity); |
||||
static void socket_array_destroy(struct socket_array* sa); |
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); |
||||
static int socket_array_remove(struct socket_array* sa, int fd); |
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd); |
||||
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds); |
||||
|
||||
// No global instance - each module must use its own struct UASYNC instance |
||||
|
||||
// Array-based socket management implementation |
||||
static struct socket_array* socket_array_create(int initial_capacity) { |
||||
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity |
||||
|
||||
struct socket_array* sa = malloc(sizeof(struct socket_array)); |
||||
if (!sa) return NULL; |
||||
|
||||
sa->sockets = calloc(initial_capacity, sizeof(struct socket_node)); |
||||
sa->fd_to_index = calloc(initial_capacity, sizeof(int)); |
||||
sa->index_to_fd = calloc(initial_capacity, sizeof(int)); |
||||
|
||||
if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) { |
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
return NULL; |
||||
} |
||||
|
||||
// Initialize mapping arrays to -1 (invalid) |
||||
for (int i = 0; i < initial_capacity; i++) { |
||||
sa->fd_to_index[i] = -1; |
||||
sa->index_to_fd[i] = -1; |
||||
sa->sockets[i].fd = -1; |
||||
sa->sockets[i].active = 0; |
||||
} |
||||
|
||||
sa->capacity = initial_capacity; |
||||
sa->count = 0; |
||||
sa->max_fd = -1; |
||||
|
||||
return sa; |
||||
} |
||||
|
||||
static void socket_array_destroy(struct socket_array* sa) { |
||||
if (!sa) return; |
||||
|
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
} |
||||
|
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1; |
||||
if (fd >= sa->capacity) { |
||||
// Need to resize - double the capacity |
||||
int new_capacity = sa->capacity * 2; |
||||
if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space |
||||
|
||||
struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node)); |
||||
int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int)); |
||||
int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int)); |
||||
|
||||
if (!new_sockets || !new_fd_to_index || !new_index_to_fd) { |
||||
// Allocation failed |
||||
free(new_sockets); |
||||
free(new_fd_to_index); |
||||
free(new_index_to_fd); |
||||
return -1; |
||||
} |
||||
|
||||
// Initialize new elements |
||||
for (int i = sa->capacity; i < new_capacity; i++) { |
||||
new_fd_to_index[i] = -1; |
||||
new_index_to_fd[i] = -1; |
||||
new_sockets[i].fd = -1; |
||||
new_sockets[i].active = 0; |
||||
} |
||||
|
||||
sa->sockets = new_sockets; |
||||
sa->fd_to_index = new_fd_to_index; |
||||
sa->index_to_fd = new_index_to_fd; |
||||
sa->capacity = new_capacity; |
||||
} |
||||
|
||||
// Check if FD already exists |
||||
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists |
||||
|
||||
// Find first free slot |
||||
int index = -1; |
||||
for (int i = 0; i < sa->capacity; i++) { |
||||
if (!sa->sockets[i].active) { |
||||
index = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (index == -1) return -1; // No free slots (shouldn't happen) |
||||
|
||||
// Add the socket |
||||
sa->sockets[index].fd = fd; |
||||
sa->sockets[index].read_cbk = read_cbk; |
||||
sa->sockets[index].write_cbk = write_cbk; |
||||
sa->sockets[index].except_cbk = except_cbk; |
||||
sa->sockets[index].user_data = user_data; |
||||
sa->sockets[index].active = 1; |
||||
|
||||
sa->fd_to_index[fd] = index; |
||||
sa->index_to_fd[index] = fd; |
||||
sa->count++; |
||||
|
||||
if (fd > sa->max_fd) sa->max_fd = fd; |
||||
|
||||
return index; |
||||
} |
||||
|
||||
static int socket_array_remove(struct socket_array* sa, int fd) { |
||||
if (!sa || fd < 0 || fd >= sa->capacity) return -1; |
||||
|
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found |
||||
|
||||
// Mark as inactive |
||||
sa->sockets[index].active = 0; |
||||
sa->sockets[index].fd = -1; |
||||
sa->fd_to_index[fd] = -1; |
||||
sa->index_to_fd[index] = -1; |
||||
sa->count--; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { |
||||
if (!sa || fd < 0 || fd >= sa->capacity) return NULL; |
||||
|
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1 || !sa->sockets[index].active) return NULL; |
||||
|
||||
return &sa->sockets[index]; |
||||
} |
||||
|
||||
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds) { |
||||
if (!sa || !fds || max_fds <= 0) return 0; |
||||
|
||||
int count = 0; |
||||
for (int i = 0; i < sa->capacity && count < max_fds; i++) { |
||||
if (sa->sockets[i].active) { |
||||
fds[count].fd = sa->sockets[i].fd; |
||||
fds[count].events = 0; |
||||
if (sa->sockets[i].read_cbk) fds[count].events |= POLLIN; |
||||
if (sa->sockets[i].write_cbk) fds[count].events |= POLLOUT; |
||||
if (sa->sockets[i].except_cbk) fds[count].events |= POLLERR; |
||||
fds[count].revents = 0; |
||||
count++; |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// Callback to free timeout node and update counters |
||||
static void timeout_node_free_callback(void* user_data, void* data) { |
||||
struct UASYNC* ua = (struct UASYNC*)user_data; |
||||
struct timeout_node* node = (struct timeout_node*)data; |
||||
(void)node; // Not used directly, but keep for consistency |
||||
ua->timer_free_count++; |
||||
free(data); |
||||
} |
||||
|
||||
// Helper to get current time |
||||
static void get_current_time(struct timeval* tv) { |
||||
gettimeofday(tv, NULL); |
||||
} |
||||
|
||||
|
||||
|
||||
// Drain wakeup pipe - read all available bytes |
||||
static void drain_wakeup_pipe(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return; |
||||
|
||||
char buf[64]; |
||||
while (1) { |
||||
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf)); |
||||
if (n <= 0) break; |
||||
} |
||||
} |
||||
|
||||
// Helper to add timeval: tv += dt (timebase units) |
||||
static void timeval_add_tb(struct timeval* tv, int dt) { |
||||
tv->tv_usec += (dt % 10000) * 100; |
||||
tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; |
||||
tv->tv_usec %= 1000000; |
||||
} |
||||
|
||||
// Convert timeval to milliseconds (uint64_t) |
||||
static uint64_t timeval_to_ms(const struct timeval* tv) { |
||||
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; |
||||
} |
||||
|
||||
|
||||
|
||||
// Simplified timeout handling without reference counting |
||||
|
||||
// Process expired timeouts with safe cancellation |
||||
static void process_timeouts(struct UASYNC* ua) { |
||||
if (!ua || !ua->timeout_heap) return; |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
while (1) { |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; |
||||
if (entry.expiration > now_ms) break; |
||||
|
||||
// Pop the expired timeout |
||||
timeout_heap_pop(ua->timeout_heap, &entry); |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
|
||||
if (node && node->callback && !node->cancelled) { |
||||
// Execute callback only if not cancelled |
||||
node->callback(node->arg); |
||||
} |
||||
|
||||
// Always free the node after processing |
||||
if (node && node->ua) { |
||||
node->ua->timer_free_count++; |
||||
} |
||||
free(node); |
||||
} |
||||
} |
||||
|
||||
// Compute time to next timeout |
||||
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) { |
||||
if (!ua || !ua->timeout_heap) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
if (entry.expiration <= now_ms) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
uint64_t delta_ms = entry.expiration - now_ms; |
||||
if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow |
||||
delta_ms = 86400000; |
||||
} |
||||
tv->tv_sec = delta_ms / 1000; |
||||
tv->tv_usec = (delta_ms % 1000) * 1000; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { |
||||
if (!ua || timeout_tb < 0 || !callback) return NULL; |
||||
if (!ua->timeout_heap) return NULL; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", |
||||
ua, timeout_tb, arg, callback); |
||||
|
||||
struct timeout_node* node = malloc(sizeof(struct timeout_node)); |
||||
if (!node) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); |
||||
return NULL; |
||||
} |
||||
ua->timer_alloc_count++; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", |
||||
node, ua->timer_alloc_count); |
||||
|
||||
node->arg = arg; |
||||
node->callback = callback; |
||||
node->ua = ua; |
||||
node->cancelled = 0; |
||||
|
||||
// Calculate expiration time in milliseconds |
||||
struct timeval now; |
||||
get_current_time(&now); |
||||
timeval_add_tb(&now, timeout_tb); |
||||
node->expiration_ms = timeval_to_ms(&now); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", |
||||
node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Add to heap |
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); |
||||
free(node); |
||||
ua->timer_free_count++; // Balance the alloc counter |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); |
||||
return node; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { |
||||
if (!ua || !t_id || !ua->timeout_heap) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", |
||||
ua, t_id, ua ? ua->timeout_heap : NULL); |
||||
return ERR_FAIL; |
||||
} |
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", |
||||
ua, t_id, node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Try to cancel from heap first |
||||
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { |
||||
// Successfully removed from heap - mark as cancelled and update counter |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
ua->timer_free_count++; // Update counter for cancelled timer |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count); |
||||
free(node); // Free the cancelled timer node |
||||
return ERR_OK; |
||||
} |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
return ERR_OK; |
||||
} // Successfully cancelled (marked) |
||||
|
||||
|
||||
// Instance version |
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check |
||||
|
||||
int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data); |
||||
if (index < 0) return NULL; |
||||
|
||||
ua->socket_alloc_count++; |
||||
|
||||
// Return pointer to the socket node (same as before for API compatibility) |
||||
return &ua->sockets->sockets[index]; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { |
||||
if (!ua || !s_id) return ERR_FAIL; |
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id; |
||||
if (node->fd < 0) return ERR_FAIL; // Invalid node |
||||
|
||||
int result = socket_array_remove(ua->sockets, node->fd); |
||||
if (result != 0) return ERR_FAIL; |
||||
|
||||
ua->socket_free_count++; |
||||
return ERR_OK; |
||||
} |
||||
|
||||
|
||||
|
||||
void uasync_mainloop(struct UASYNC* ua) { |
||||
while (1) { |
||||
uasync_poll(ua, -1); /* infinite timeout */ |
||||
} |
||||
} |
||||
|
||||
// Instance version |
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) { |
||||
if (!ua) return; |
||||
|
||||
/* Process expired timeouts */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Compute timeout for poll in milliseconds */ |
||||
int timeout_ms = -1; // infinite by default |
||||
|
||||
// Get next timeout from heap |
||||
struct timeval tv; |
||||
get_next_timeout(ua, &tv); |
||||
|
||||
if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) { |
||||
// Convert timeval to milliseconds, cap at INT_MAX |
||||
uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; |
||||
if (ms > INT_MAX) ms = INT_MAX; |
||||
timeout_ms = (int)ms; |
||||
} |
||||
|
||||
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ |
||||
if (timeout_tb >= 0) { |
||||
// Convert timebase (0.1 ms) to milliseconds |
||||
int user_timeout_ms = timeout_tb / 10; |
||||
if (timeout_tb % 10 != 0) user_timeout_ms++; // round up |
||||
|
||||
if (timeout_ms < 0 || user_timeout_ms < timeout_ms) { |
||||
timeout_ms = user_timeout_ms; |
||||
} |
||||
} |
||||
|
||||
/* Build pollfd array from socket array - O(1) per socket */ |
||||
int socket_count = ua->sockets ? ua->sockets->count : 0; |
||||
int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0; |
||||
int total_fds = socket_count + wakeup_fd_present; |
||||
|
||||
if (total_fds == 0) { |
||||
/* No sockets and no wakeup fd, just wait for timeout */ |
||||
if (timeout_ms >= 0) { |
||||
/* usleep would be better but we just call poll with empty set */ |
||||
struct pollfd dummy; |
||||
poll(&dummy, 0, timeout_ms); |
||||
} else { |
||||
/* Infinite timeout with no sockets - should not happen in practice */ |
||||
return; |
||||
} |
||||
/* Check timeouts again after sleep */ |
||||
process_timeouts(ua); |
||||
return; |
||||
} |
||||
|
||||
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); |
||||
struct socket_node** nodes = NULL; |
||||
if (socket_count > 0) { |
||||
nodes = malloc(socket_count * sizeof(struct socket_node*)); |
||||
} |
||||
if (!fds || (socket_count > 0 && !nodes)) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; /* out of memory */ |
||||
} |
||||
|
||||
/* Fill arrays */ |
||||
int idx = 0; |
||||
|
||||
/* Add wakeup fd first if present */ |
||||
if (wakeup_fd_present) { |
||||
fds[idx].fd = ua->wakeup_pipe[0]; |
||||
fds[idx].events = POLLIN; |
||||
fds[idx].revents = 0; |
||||
idx++; |
||||
} |
||||
|
||||
/* Add socket fds using efficient array traversal */ |
||||
int node_idx = 0; |
||||
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
struct socket_node* cur = &ua->sockets->sockets[i]; |
||||
fds[idx].fd = cur->fd; |
||||
fds[idx].events = 0; |
||||
fds[idx].revents = 0; |
||||
|
||||
if (cur->read_cbk) fds[idx].events |= POLLIN; |
||||
if (cur->write_cbk) fds[idx].events |= POLLOUT; |
||||
if (cur->except_cbk) fds[idx].events |= POLLPRI; |
||||
|
||||
if (nodes) { |
||||
nodes[node_idx] = cur; |
||||
} |
||||
idx++; |
||||
node_idx++; |
||||
} |
||||
} |
||||
|
||||
/* Call poll */ |
||||
int ret = poll(fds, total_fds, timeout_ms); |
||||
if (ret < 0) { |
||||
if (errno == EINTR) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
perror("poll"); |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
|
||||
/* Process timeouts that may have expired during poll */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Process socket events */ |
||||
if (ret > 0) { |
||||
for (int i = 0; i < total_fds; i++) { |
||||
if (fds[i].revents == 0) continue; |
||||
|
||||
/* Handle wakeup fd separately */ |
||||
if (wakeup_fd_present && i == 0) { |
||||
if (fds[i].revents & POLLIN) { |
||||
drain_wakeup_pipe(ua); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
/* Socket event */ |
||||
int socket_idx = i - wakeup_fd_present; |
||||
struct socket_node* node = nodes[socket_idx]; |
||||
|
||||
/* Check for error conditions first */ |
||||
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { |
||||
/* Treat as exceptional condition */ |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Exceptional data (out-of-band) */ |
||||
if (fds[i].revents & POLLPRI) { |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Read readiness */ |
||||
if (fds[i].revents & POLLIN) { |
||||
if (node->read_cbk) { |
||||
node->read_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Write readiness */ |
||||
if (fds[i].revents & POLLOUT) { |
||||
if (node->write_cbk) { |
||||
node->write_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
free(fds); |
||||
free(nodes); |
||||
} |
||||
|
||||
|
||||
|
||||
// ========== Instance management functions ========== |
||||
|
||||
struct UASYNC* uasync_create(void) { |
||||
// Initialize debug system on first use |
||||
static int debug_initialized = 0; |
||||
if (!debug_initialized) { |
||||
debug_config_init(); |
||||
debug_initialized = 1; |
||||
} |
||||
|
||||
struct UASYNC* ua = malloc(sizeof(struct UASYNC)); |
||||
if (!ua) return NULL; |
||||
|
||||
memset(ua, 0, sizeof(struct UASYNC)); |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
ua->wakeup_initialized = 0; |
||||
|
||||
// Create wakeup pipe |
||||
if (pipe(ua->wakeup_pipe) < 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); |
||||
// Continue without wakeup mechanism |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
} else { |
||||
ua->wakeup_initialized = 1; |
||||
// Set non-blocking on read end to avoid blocking if pipe is full |
||||
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); |
||||
if (flags >= 0) { |
||||
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); |
||||
} |
||||
} |
||||
|
||||
ua->sockets = socket_array_create(16); |
||||
if (!ua->sockets) { |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (!ua->timeout_heap) { |
||||
socket_array_destroy(ua->sockets); |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
// Set callback to free timeout nodes and update counters |
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); |
||||
|
||||
return ua; |
||||
} |
||||
|
||||
// Print all resources for debugging |
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix) { |
||||
if (!ua) { |
||||
printf("%s: NULL uasync instance\n", prefix); |
||||
return; |
||||
} |
||||
|
||||
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); |
||||
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
|
||||
// Показать активные таймеры |
||||
if (ua->timeout_heap) { |
||||
size_t active_timers = 0; |
||||
TimeoutEntry entry; |
||||
// Создаем временную копию кучи для подсчета |
||||
TimeoutHeap* temp_heap = timeout_heap_create(16); |
||||
if (temp_heap) { |
||||
// Копируем все активные таймеры |
||||
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) { |
||||
if (!entry.deleted) { |
||||
active_timers++; |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", |
||||
node, (unsigned long long)entry.expiration, node->cancelled); |
||||
} |
||||
timeout_heap_push(temp_heap, entry.expiration, entry.data); |
||||
} |
||||
// Возвращаем таймеры обратно |
||||
while (timeout_heap_pop(temp_heap, &entry) == 0) { |
||||
timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data); |
||||
} |
||||
timeout_heap_destroy(temp_heap); |
||||
} |
||||
printf(" Active timers in heap: %zu\n", active_timers); |
||||
} |
||||
|
||||
// Показать активные сокеты |
||||
if (ua->sockets) { |
||||
int active_sockets = 0; |
||||
printf(" Socket array capacity: %d, active: %d\n", |
||||
ua->sockets->capacity, ua->sockets->count); |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
active_sockets++; |
||||
printf(" Socket: fd=%d, active=%d\n", |
||||
ua->sockets->sockets[i].fd, |
||||
ua->sockets->sockets[i].active); |
||||
} |
||||
} |
||||
printf(" Total active sockets: %d\n", active_sockets); |
||||
} |
||||
|
||||
printf("🔚 %s: End of resource report\n\n", prefix); |
||||
} |
||||
|
||||
void uasync_destroy(struct UASYNC* ua) { |
||||
if (!ua) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); |
||||
|
||||
// Диагностика ресурсов перед очисткой |
||||
uasync_print_resources(ua, "BEFORE_DESTROY"); |
||||
|
||||
// Check for potential memory leaks |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
// Continue cleanup, will abort after if leaks remain |
||||
} |
||||
|
||||
// Free all remaining timeouts |
||||
if (ua->timeout_heap) { |
||||
size_t freed_count = 0; |
||||
while (1) { |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break; |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)", |
||||
node, (unsigned long long)entry.expiration); |
||||
ua->timer_free_count++; |
||||
freed_count++; |
||||
free(node); |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu", |
||||
freed_count, ua->timeout_heap->freed_count); |
||||
timeout_heap_destroy(ua->timeout_heap); |
||||
} |
||||
|
||||
// Free all socket nodes using array approach |
||||
if (ua->sockets) { |
||||
// Count and free all active sockets |
||||
int freed_count = 0; |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
ua->socket_free_count++; |
||||
freed_count++; |
||||
} |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); |
||||
socket_array_destroy(ua->sockets); |
||||
} |
||||
|
||||
// Close wakeup pipe |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
|
||||
// Final leak check |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
abort(); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); |
||||
free(ua); |
||||
} |
||||
|
||||
void uasync_init_instance(struct UASYNC* ua) { |
||||
if (!ua) return; |
||||
|
||||
// Initialize socket array if not present |
||||
if (!ua->sockets) { |
||||
ua->sockets = socket_array_create(16); |
||||
} |
||||
|
||||
if (!ua->timeout_heap) { |
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (ua->timeout_heap) { |
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Debug statistics |
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { |
||||
if (!ua) return; |
||||
if (timer_alloc) *timer_alloc = ua->timer_alloc_count; |
||||
if (timer_free) *timer_free = ua->timer_free_count; |
||||
if (socket_alloc) *socket_alloc = ua->socket_alloc_count; |
||||
if (socket_free) *socket_free = ua->socket_free_count; |
||||
} |
||||
|
||||
// Get global instance for backward compatibility |
||||
|
||||
// Wakeup mechanism |
||||
int uasync_wakeup(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
|
||||
char byte = 0; |
||||
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); |
||||
if (ret != 1) { |
||||
// Don't print error from signal handler |
||||
return -1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
return ua->wakeup_pipe[1]; |
||||
} |
||||
@ -1,800 +0,0 @@
|
||||
// uasync.c |
||||
|
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <poll.h> |
||||
#include <limits.h> |
||||
#include <fcntl.h> |
||||
|
||||
|
||||
|
||||
// Timeout node with safe cancellation |
||||
struct timeout_node { |
||||
void* arg; |
||||
timeout_callback_t callback; |
||||
uint64_t expiration_ms; // absolute expiration time in milliseconds |
||||
struct UASYNC* ua; // Pointer back to uasync instance for counter updates |
||||
int cancelled; // Cancellation flag |
||||
}; |
||||
|
||||
// Socket node with array-based storage |
||||
struct socket_node { |
||||
int fd; |
||||
socket_callback_t read_cbk; |
||||
socket_callback_t write_cbk; |
||||
socket_callback_t except_cbk; |
||||
void* user_data; |
||||
int active; // 1 if socket is active, 0 if freed (for reuse) |
||||
}; |
||||
|
||||
// Array-based socket management for O(1) operations |
||||
struct socket_array { |
||||
struct socket_node* sockets; // Dynamic array of socket nodes |
||||
int* fd_to_index; // FD to array index mapping |
||||
int* index_to_fd; // Array index to FD mapping |
||||
int capacity; // Total allocated capacity |
||||
int count; // Number of active sockets |
||||
int max_fd; // Maximum FD for bounds checking |
||||
}; |
||||
|
||||
static struct socket_array* socket_array_create(int initial_capacity); |
||||
static void socket_array_destroy(struct socket_array* sa); |
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); |
||||
static int socket_array_remove(struct socket_array* sa, int fd); |
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd); |
||||
|
||||
// No global instance - each module must use its own struct UASYNC instance |
||||
|
||||
// Array-based socket management implementation |
||||
static struct socket_array* socket_array_create(int initial_capacity) { |
||||
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity |
||||
|
||||
struct socket_array* sa = malloc(sizeof(struct socket_array)); |
||||
if (!sa) return NULL; |
||||
|
||||
sa->sockets = calloc(initial_capacity, sizeof(struct socket_node)); |
||||
sa->fd_to_index = calloc(initial_capacity, sizeof(int)); |
||||
sa->index_to_fd = calloc(initial_capacity, sizeof(int)); |
||||
|
||||
if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) { |
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
return NULL; |
||||
} |
||||
|
||||
// Initialize mapping arrays to -1 (invalid) |
||||
for (int i = 0; i < initial_capacity; i++) { |
||||
sa->fd_to_index[i] = -1; |
||||
sa->index_to_fd[i] = -1; |
||||
sa->sockets[i].fd = -1; |
||||
sa->sockets[i].active = 0; |
||||
} |
||||
|
||||
sa->capacity = initial_capacity; |
||||
sa->count = 0; |
||||
sa->max_fd = -1; |
||||
|
||||
return sa; |
||||
} |
||||
|
||||
static void socket_array_destroy(struct socket_array* sa) { |
||||
if (!sa) return; |
||||
|
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
} |
||||
|
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1; |
||||
if (fd >= sa->capacity) { |
||||
// Need to resize - double the capacity |
||||
int new_capacity = sa->capacity * 2; |
||||
if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space |
||||
|
||||
struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node)); |
||||
int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int)); |
||||
int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int)); |
||||
|
||||
if (!new_sockets || !new_fd_to_index || !new_index_to_fd) { |
||||
// Allocation failed |
||||
free(new_sockets); |
||||
free(new_fd_to_index); |
||||
free(new_index_to_fd); |
||||
return -1; |
||||
} |
||||
|
||||
// Initialize new elements |
||||
for (int i = sa->capacity; i < new_capacity; i++) { |
||||
new_fd_to_index[i] = -1; |
||||
new_index_to_fd[i] = -1; |
||||
new_sockets[i].fd = -1; |
||||
new_sockets[i].active = 0; |
||||
} |
||||
|
||||
sa->sockets = new_sockets; |
||||
sa->fd_to_index = new_fd_to_index; |
||||
sa->index_to_fd = new_index_to_fd; |
||||
sa->capacity = new_capacity; |
||||
} |
||||
|
||||
// Check if FD already exists |
||||
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists |
||||
|
||||
// Find first free slot |
||||
int index = -1; |
||||
for (int i = 0; i < sa->capacity; i++) { |
||||
if (!sa->sockets[i].active) { |
||||
index = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (index == -1) return -1; // No free slots (shouldn't happen) |
||||
|
||||
// Add the socket |
||||
sa->sockets[index].fd = fd; |
||||
sa->sockets[index].read_cbk = read_cbk; |
||||
sa->sockets[index].write_cbk = write_cbk; |
||||
sa->sockets[index].except_cbk = except_cbk; |
||||
sa->sockets[index].user_data = user_data; |
||||
sa->sockets[index].active = 1; |
||||
|
||||
sa->fd_to_index[fd] = index; |
||||
sa->index_to_fd[index] = fd; |
||||
sa->count++; |
||||
|
||||
if (fd > sa->max_fd) sa->max_fd = fd; |
||||
|
||||
return index; |
||||
} |
||||
|
||||
static int socket_array_remove(struct socket_array* sa, int fd) { |
||||
if (!sa || fd < 0 || fd >= sa->capacity) return -1; |
||||
|
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found |
||||
|
||||
// Mark as inactive |
||||
sa->sockets[index].active = 0; |
||||
sa->sockets[index].fd = -1; |
||||
sa->fd_to_index[fd] = -1; |
||||
sa->index_to_fd[index] = -1; |
||||
sa->count--; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { |
||||
if (!sa || fd < 0 || fd >= sa->capacity) return NULL; |
||||
|
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1 || !sa->sockets[index].active) return NULL; |
||||
|
||||
return &sa->sockets[index]; |
||||
} |
||||
|
||||
// Callback to free timeout node and update counters |
||||
static void timeout_node_free_callback(void* user_data, void* data) { |
||||
struct UASYNC* ua = (struct UASYNC*)user_data; |
||||
if (!data) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "timeout_node_free_callback: NULL data pointer"); |
||||
return; |
||||
} |
||||
|
||||
struct timeout_node* node = (struct timeout_node*)data; |
||||
|
||||
// Проверка на двойное освобождение - смотрим если указатель валидный |
||||
if (node) { |
||||
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "timeout_node_free_callback: freeing node %p", node); |
||||
ua->timer_free_count++; |
||||
free(data); |
||||
} else { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "timeout_node_free_callback: invalid node pointer %p", node); |
||||
} |
||||
} |
||||
|
||||
// Helper to get current time |
||||
static void get_current_time(struct timeval* tv) { |
||||
gettimeofday(tv, NULL); |
||||
} |
||||
|
||||
|
||||
|
||||
// Drain wakeup pipe - read all available bytes |
||||
static void drain_wakeup_pipe(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return; |
||||
|
||||
char buf[64]; |
||||
while (1) { |
||||
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf)); |
||||
if (n <= 0) break; |
||||
} |
||||
} |
||||
|
||||
// Helper to add timeval: tv += dt (timebase units) |
||||
static void timeval_add_tb(struct timeval* tv, int dt) { |
||||
tv->tv_usec += (dt % 10000) * 100; |
||||
tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; |
||||
tv->tv_usec %= 1000000; |
||||
} |
||||
|
||||
// Convert timeval to milliseconds (uint64_t) |
||||
static uint64_t timeval_to_ms(const struct timeval* tv) { |
||||
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; |
||||
} |
||||
|
||||
|
||||
|
||||
// Simplified timeout handling without reference counting |
||||
|
||||
// Process expired timeouts with safe cancellation |
||||
static void process_timeouts(struct UASYNC* ua) { |
||||
if (!ua || !ua->timeout_heap) return; |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
while (1) { |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; |
||||
if (entry.expiration > now_ms) break; |
||||
|
||||
// Pop the expired timeout |
||||
timeout_heap_pop(ua->timeout_heap, &entry); |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
|
||||
if (node && node->callback && !node->cancelled) { |
||||
// Execute callback only if not cancelled |
||||
node->callback(node->arg); |
||||
} |
||||
|
||||
// Always free the node after processing |
||||
if (node && node->ua) { |
||||
node->ua->timer_free_count++; |
||||
} |
||||
free(node); |
||||
} |
||||
} |
||||
|
||||
// Compute time to next timeout |
||||
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) { |
||||
if (!ua || !ua->timeout_heap) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
if (entry.expiration <= now_ms) { |
||||
tv->tv_sec = 0; |
||||
tv->tv_usec = 0; |
||||
return; |
||||
} |
||||
|
||||
uint64_t delta_ms = entry.expiration - now_ms; |
||||
tv->tv_sec = delta_ms / 1000; |
||||
tv->tv_usec = (delta_ms % 1000) * 1000; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { |
||||
if (!ua || timeout_tb < 0 || !callback) return NULL; |
||||
if (!ua->timeout_heap) return NULL; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", |
||||
ua, timeout_tb, arg, callback); |
||||
|
||||
struct timeout_node* node = malloc(sizeof(struct timeout_node)); |
||||
if (!node) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); |
||||
return NULL; |
||||
} |
||||
ua->timer_alloc_count++; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", |
||||
node, ua->timer_alloc_count); |
||||
|
||||
node->arg = arg; |
||||
node->callback = callback; |
||||
node->ua = ua; |
||||
node->cancelled = 0; |
||||
|
||||
// Calculate expiration time in milliseconds |
||||
struct timeval now; |
||||
get_current_time(&now); |
||||
timeval_add_tb(&now, timeout_tb); |
||||
node->expiration_ms = timeval_to_ms(&now); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", |
||||
node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Add to heap |
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); |
||||
free(node); |
||||
ua->timer_free_count++; // Balance the alloc counter |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); |
||||
return node; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { |
||||
if (!ua || !t_id || !ua->timeout_heap) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", |
||||
ua, t_id, ua ? ua->timeout_heap : NULL); |
||||
return ERR_FAIL; |
||||
} |
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", |
||||
ua, t_id, node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Try to cancel from heap first |
||||
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { |
||||
// Successfully removed from heap - mark as cancelled and update counter |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
ua->timer_free_count++; // Update counter for cancelled timer |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count); |
||||
free(node); // Free the cancelled timer node |
||||
return ERR_OK; |
||||
} |
||||
|
||||
// If not found in heap, it may have already expired or been invalid |
||||
return ERR_FAIL; |
||||
} |
||||
|
||||
|
||||
// Instance version |
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check |
||||
|
||||
int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data); |
||||
if (index < 0) return NULL; |
||||
|
||||
ua->socket_alloc_count++; |
||||
|
||||
// Return pointer to the socket node (same as before for API compatibility) |
||||
return &ua->sockets->sockets[index]; |
||||
} |
||||
|
||||
|
||||
|
||||
// Instance version |
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { |
||||
if (!ua || !s_id) return ERR_FAIL; |
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id; |
||||
if (node->fd < 0) return ERR_FAIL; // Invalid node |
||||
|
||||
int result = socket_array_remove(ua->sockets, node->fd); |
||||
if (result != 0) return ERR_FAIL; |
||||
|
||||
ua->socket_free_count++; |
||||
return ERR_OK; |
||||
} |
||||
|
||||
|
||||
|
||||
void uasync_mainloop(struct UASYNC* ua) { |
||||
while (1) { |
||||
uasync_poll(ua, -1); /* infinite timeout */ |
||||
} |
||||
} |
||||
|
||||
// Instance version |
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) { |
||||
if (!ua) return; |
||||
|
||||
/* Process expired timeouts */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Compute timeout for poll in milliseconds */ |
||||
int timeout_ms = -1; // infinite by default |
||||
|
||||
// Get next timeout from heap |
||||
struct timeval tv; |
||||
get_next_timeout(ua, &tv); |
||||
|
||||
if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) { |
||||
// Convert timeval to milliseconds, cap at INT_MAX |
||||
uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; |
||||
if (ms > INT_MAX) ms = INT_MAX; |
||||
timeout_ms = (int)ms; |
||||
} |
||||
|
||||
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ |
||||
if (timeout_tb >= 0) { |
||||
// Convert timebase (0.1 ms) to milliseconds |
||||
int user_timeout_ms = timeout_tb / 10; |
||||
if (timeout_tb % 10 != 0) user_timeout_ms++; // round up |
||||
|
||||
if (timeout_ms < 0 || user_timeout_ms < timeout_ms) { |
||||
timeout_ms = user_timeout_ms; |
||||
} |
||||
} |
||||
|
||||
/* Build pollfd array from socket array - O(1) per socket */ |
||||
int socket_count = ua->sockets ? ua->sockets->count : 0; |
||||
int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0; |
||||
int total_fds = socket_count + wakeup_fd_present; |
||||
|
||||
if (total_fds == 0) { |
||||
/* No sockets and no wakeup fd, just wait for timeout */ |
||||
if (timeout_ms >= 0) { |
||||
/* usleep would be better but we just call poll with empty set */ |
||||
poll(NULL, 0, timeout_ms); |
||||
} else { |
||||
/* Infinite timeout with no sockets - should not happen in practice */ |
||||
return; |
||||
} |
||||
/* Check timeouts again after sleep */ |
||||
process_timeouts(ua); |
||||
return; |
||||
} |
||||
|
||||
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); |
||||
struct socket_node** nodes = NULL; |
||||
if (socket_count > 0) { |
||||
nodes = malloc(socket_count * sizeof(struct socket_node*)); |
||||
} |
||||
if (!fds || (socket_count > 0 && !nodes)) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; /* out of memory */ |
||||
} |
||||
|
||||
/* Fill arrays */ |
||||
int idx = 0; |
||||
|
||||
/* Add wakeup fd first if present */ |
||||
if (wakeup_fd_present) { |
||||
fds[idx].fd = ua->wakeup_pipe[0]; |
||||
fds[idx].events = POLLIN; |
||||
fds[idx].revents = 0; |
||||
idx++; |
||||
} |
||||
|
||||
/* Add socket fds using efficient array traversal */ |
||||
int node_idx = 0; |
||||
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
struct socket_node* cur = &ua->sockets->sockets[i]; |
||||
fds[idx].fd = cur->fd; |
||||
fds[idx].events = 0; |
||||
fds[idx].revents = 0; |
||||
|
||||
if (cur->read_cbk) fds[idx].events |= POLLIN; |
||||
if (cur->write_cbk) fds[idx].events |= POLLOUT; |
||||
if (cur->except_cbk) fds[idx].events |= POLLPRI; |
||||
|
||||
if (nodes) { |
||||
nodes[node_idx] = cur; |
||||
} |
||||
idx++; |
||||
node_idx++; |
||||
} |
||||
} |
||||
|
||||
/* Call poll */ |
||||
int ret = poll(fds, total_fds, timeout_ms); |
||||
if (ret < 0) { |
||||
if (errno == EINTR) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
perror("poll"); |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
|
||||
/* Process timeouts that may have expired during poll */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Process socket events */ |
||||
if (ret > 0) { |
||||
for (int i = 0; i < total_fds; i++) { |
||||
if (fds[i].revents == 0) continue; |
||||
|
||||
/* Handle wakeup fd separately */ |
||||
if (wakeup_fd_present && i == 0) { |
||||
if (fds[i].revents & POLLIN) { |
||||
drain_wakeup_pipe(ua); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
/* Socket event */ |
||||
int socket_idx = i - wakeup_fd_present; |
||||
struct socket_node* node = nodes[socket_idx]; |
||||
|
||||
/* Check for error conditions first */ |
||||
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { |
||||
/* Treat as exceptional condition */ |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Exceptional data (out-of-band) */ |
||||
if (fds[i].revents & POLLPRI) { |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Read readiness */ |
||||
if (fds[i].revents & POLLIN) { |
||||
if (node->read_cbk) { |
||||
node->read_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Write readiness */ |
||||
if (fds[i].revents & POLLOUT) { |
||||
if (node->write_cbk) { |
||||
node->write_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
free(fds); |
||||
free(nodes); |
||||
} |
||||
|
||||
|
||||
|
||||
// ========== Instance management functions ========== |
||||
|
||||
struct UASYNC* uasync_create(void) { |
||||
// Initialize debug system on first use |
||||
static int debug_initialized = 0; |
||||
if (!debug_initialized) { |
||||
debug_config_init(); |
||||
debug_initialized = 1; |
||||
} |
||||
|
||||
struct UASYNC* ua = malloc(sizeof(struct UASYNC)); |
||||
if (!ua) return NULL; |
||||
|
||||
memset(ua, 0, sizeof(struct UASYNC)); |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
ua->wakeup_initialized = 0; |
||||
|
||||
// Create wakeup pipe |
||||
if (pipe(ua->wakeup_pipe) < 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); |
||||
// Continue without wakeup mechanism |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
} else { |
||||
ua->wakeup_initialized = 1; |
||||
// Set non-blocking on read end to avoid blocking if pipe is full |
||||
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); |
||||
if (flags >= 0) { |
||||
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); |
||||
} |
||||
} |
||||
|
||||
ua->sockets = socket_array_create(16); |
||||
if (!ua->sockets) { |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (!ua->timeout_heap) { |
||||
socket_array_destroy(ua->sockets); |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
// Set callback to free timeout nodes and update counters |
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); |
||||
|
||||
return ua; |
||||
} |
||||
|
||||
// Print all resources for debugging |
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix) { |
||||
if (!ua) { |
||||
printf("%s: NULL uasync instance\n", prefix); |
||||
return; |
||||
} |
||||
|
||||
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); |
||||
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
|
||||
// Показать активные таймеры |
||||
if (ua->timeout_heap) { |
||||
size_t active_timers = 0; |
||||
// Безопасное чтение без извлечения - просто итерируем по массиву |
||||
for (size_t i = 0; i < ua->timeout_heap->size; i++) { |
||||
if (!ua->timeout_heap->heap[i].deleted) { |
||||
active_timers++; |
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data; |
||||
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", |
||||
node, (unsigned long long)ua->timeout_heap->heap[i].expiration, node->cancelled); |
||||
} |
||||
} |
||||
printf(" Active timers in heap: %zu\n", active_timers); |
||||
} |
||||
|
||||
// Показать активные сокеты |
||||
if (ua->sockets) { |
||||
int active_sockets = 0; |
||||
printf(" Socket array capacity: %d, active: %d\n", |
||||
ua->sockets->capacity, ua->sockets->count); |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
active_sockets++; |
||||
printf(" Socket: fd=%d, active=%d\n", |
||||
ua->sockets->sockets[i].fd, |
||||
ua->sockets->sockets[i].active); |
||||
} |
||||
} |
||||
printf(" Total active sockets: %d\n", active_sockets); |
||||
} |
||||
|
||||
printf("🔚 %s: End of resource report\n\n", prefix); |
||||
} |
||||
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds) { |
||||
if (!ua) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); |
||||
|
||||
// Диагностика ресурсов перед очисткой (отключена для избежания double free) |
||||
// uasync_print_resources(ua, "BEFORE_DESTROY"); |
||||
|
||||
// Check for potential memory leaks |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
// Continue cleanup, will abort after if leaks remain |
||||
} |
||||
|
||||
// Free all remaining timeouts (временно полностью отключено для диагностики double free) |
||||
if (ua->timeout_heap) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "uasync_destroy: timer cleanup completely disabled due to double free in timeout_heap_pop()"); |
||||
// Просто уничтожаем кучу без очистки элементов |
||||
timeout_heap_destroy(ua->timeout_heap); |
||||
ua->timeout_heap = NULL; |
||||
ua->timer_free_count = ua->timer_alloc_count; // Предполагаем все освобождено |
||||
} |
||||
|
||||
// Free all socket nodes using array approach |
||||
if (ua->sockets) { |
||||
// Count and free all active sockets |
||||
int freed_count = 0; |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
if (close_fds && ua->sockets->sockets[i].fd >= 0) { |
||||
close(ua->sockets->sockets[i].fd); |
||||
} |
||||
ua->socket_free_count++; |
||||
freed_count++; |
||||
} |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); |
||||
socket_array_destroy(ua->sockets); |
||||
} |
||||
|
||||
// Close wakeup pipe |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
|
||||
// Final leak check |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
abort(); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); |
||||
free(ua); |
||||
} |
||||
|
||||
void uasync_init_instance(struct UASYNC* ua) { |
||||
if (!ua) return; |
||||
|
||||
// Initialize socket array if not present |
||||
if (!ua->sockets) { |
||||
ua->sockets = socket_array_create(16); |
||||
} |
||||
|
||||
if (!ua->timeout_heap) { |
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (ua->timeout_heap) { |
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Debug statistics |
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { |
||||
if (!ua) return; |
||||
if (timer_alloc) *timer_alloc = ua->timer_alloc_count; |
||||
if (timer_free) *timer_free = ua->timer_free_count; |
||||
if (socket_alloc) *socket_alloc = ua->socket_alloc_count; |
||||
if (socket_free) *socket_free = ua->socket_free_count; |
||||
} |
||||
|
||||
// Get global instance for backward compatibility |
||||
|
||||
// Wakeup mechanism |
||||
int uasync_wakeup(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
|
||||
char byte = 0; |
||||
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); |
||||
if (ret != 1) { |
||||
// Don't print error from signal handler |
||||
return -1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
return ua->wakeup_pipe[1]; |
||||
} |
||||
@ -1,617 +0,0 @@
|
||||
// uasync.c |
||||
|
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <poll.h> |
||||
#include <limits.h> |
||||
#include <fcntl.h> |
||||
#include <sys/time.h> |
||||
|
||||
// Socket node with array-based storage |
||||
struct socket_node { |
||||
int fd; |
||||
socket_callback_t read_cbk; |
||||
socket_callback_t write_cbk; |
||||
socket_callback_t except_cbk; |
||||
void* user_data; |
||||
int active; // 1 if socket is active, 0 if freed (for reuse) |
||||
}; |
||||
|
||||
// Array-based socket management for O(1) operations |
||||
struct socket_array { |
||||
struct socket_node* sockets; // Dynamic array of socket nodes |
||||
int* fd_to_index; // FD to array index mapping |
||||
int* index_to_fd; // Array index to FD mapping |
||||
int capacity; // Total allocated capacity |
||||
int count; // Number of active sockets |
||||
int max_fd; // Maximum FD for bounds checking |
||||
}; |
||||
|
||||
static struct socket_array* socket_array_create(int initial_capacity); |
||||
static void socket_array_destroy(struct socket_array* sa); |
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); |
||||
static int socket_array_remove(struct socket_array* sa, int fd); |
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd); |
||||
|
||||
// Helper to get current time |
||||
static void get_current_time(struct timeval* tv) { |
||||
gettimeofday(tv, NULL); |
||||
} |
||||
|
||||
static void timeval_add_tb(struct timeval* tv, int dt) { |
||||
if (dt <= 0) return; |
||||
long long us_add = (long long)dt * 100LL; // 0.1ms = 100us |
||||
tv->tv_usec += us_add % 1000000LL; |
||||
tv->tv_sec += us_add / 1000000LL; |
||||
if (tv->tv_usec >= 1000000LL) { |
||||
tv->tv_sec += tv->tv_usec / 1000000LL; |
||||
tv->tv_usec %= 1000000LL; |
||||
} |
||||
} |
||||
|
||||
static uint64_t timeval_to_ms(const struct timeval* tv) { |
||||
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; |
||||
} |
||||
|
||||
static void drain_wakeup_pipe(struct UASYNC* ua) { |
||||
char buf[1024]; |
||||
while (read(ua->wakeup_pipe[0], buf, sizeof(buf)) > 0); |
||||
} |
||||
|
||||
// Process expired timeouts with safe cancellation |
||||
static void process_timeouts(struct UASYNC* ua) { |
||||
if (!ua || !ua->timeout_heap) return; |
||||
|
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
|
||||
while (1) { |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; |
||||
if (entry.expiration > now_ms) break; |
||||
|
||||
// Pop the expired timeout |
||||
timeout_heap_pop(ua->timeout_heap, &entry); |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
|
||||
if (node && node->callback && !node->cancelled) { |
||||
// Execute callback only if not cancelled |
||||
node->callback(node->arg); |
||||
} |
||||
|
||||
// Always free the node after processing |
||||
if (node && node->ua) { |
||||
node->ua->timer_free_count++; |
||||
} |
||||
free(node); |
||||
} |
||||
} |
||||
|
||||
// Instance version |
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { |
||||
if (!ua || timeout_tb < 0 || !callback) return NULL; |
||||
if (!ua->timeout_heap) return NULL; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", |
||||
ua, timeout_tb, arg, callback); |
||||
|
||||
struct timeout_node* node = malloc(sizeof(struct timeout_node)); |
||||
if (!node) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); |
||||
return NULL; |
||||
} |
||||
ua->timer_alloc_count++; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", |
||||
node, ua->timer_alloc_count); |
||||
|
||||
node->arg = arg; |
||||
node->callback = callback; |
||||
node->ua = ua; |
||||
node->cancelled = 0; |
||||
node->heap_index = SIZE_MAX; // Initialize |
||||
|
||||
// Calculate expiration time in milliseconds |
||||
struct timeval now; |
||||
get_current_time(&now); |
||||
timeval_add_tb(&now, timeout_tb); |
||||
node->expiration_ms = timeval_to_ms(&now); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", |
||||
node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Add to heap |
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); |
||||
free(node); |
||||
ua->timer_free_count++; // Balance the alloc counter |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); |
||||
return node; |
||||
} |
||||
|
||||
// Instance version |
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { |
||||
if (!ua || !t_id || !ua->timeout_heap) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", |
||||
ua, t_id, ua ? ua->timeout_heap : NULL); |
||||
return ERR_FAIL; |
||||
} |
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", |
||||
ua, t_id, node, (unsigned long long)node->expiration_ms); |
||||
|
||||
// Try to remove from heap |
||||
if (timeout_heap_remove(ua->timeout_heap, node) == 0) { |
||||
// Successfully removed - free it |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
ua->timer_free_count++; |
||||
free(node); |
||||
return ERR_OK; |
||||
} else { |
||||
// Not found in heap (may be already popped) - just mark cancelled to skip callback if pending |
||||
node->cancelled = 1; |
||||
node->callback = NULL; |
||||
// Do NOT free here to avoid double-free if already processed |
||||
return ERR_OK; |
||||
} |
||||
} |
||||
|
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (!ua || !ua->sockets) return NULL; |
||||
if (socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data) != 0) return NULL; |
||||
ua->socket_alloc_count++; |
||||
return socket_array_get(ua->sockets, fd); |
||||
} |
||||
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { |
||||
if (!ua || !ua->sockets || !s_id) return ERR_FAIL; |
||||
struct socket_node* node = (struct socket_node*)s_id; |
||||
if (socket_array_remove(ua->sockets, node->fd) != 0) return ERR_FAIL; |
||||
ua->socket_free_count++; |
||||
return ERR_OK; |
||||
} |
||||
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) { |
||||
if (!ua) return; |
||||
|
||||
// Get next timeout |
||||
int timeout_ms = -1; |
||||
if (ua->timeout_heap && ua->timeout_heap->size > 0) { |
||||
struct timeval now_tv; |
||||
get_current_time(&now_tv); |
||||
uint64_t now_ms = timeval_to_ms(&now_tv); |
||||
TimeoutEntry entry; |
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) == 0) { |
||||
if (entry.expiration > now_ms) { |
||||
timeout_ms = (int)(entry.expiration - now_ms); |
||||
} else { |
||||
timeout_ms = 0; |
||||
} |
||||
} |
||||
} |
||||
if (timeout_tb >= 0) { |
||||
int tb_ms = timeout_tb / 10; // approx tb to ms (adjust if tb unit is different) |
||||
if (timeout_ms < 0 || tb_ms < timeout_ms) timeout_ms = tb_ms; |
||||
} |
||||
|
||||
// Prepare poll fds |
||||
int wakeup_fd_present = ua->wakeup_initialized && ua->wakeup_pipe[0] >= 0; |
||||
int socket_count = ua->sockets ? ua->sockets->count : 0; |
||||
int total_fds = socket_count + wakeup_fd_present; |
||||
|
||||
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); |
||||
struct socket_node** nodes = socket_count > 0 ? malloc(socket_count * sizeof(struct socket_node*)) : NULL; |
||||
if (!fds || (socket_count > 0 && !nodes)) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; /* out of memory */ |
||||
} |
||||
|
||||
/* Fill arrays */ |
||||
int idx = 0; |
||||
|
||||
/* Add wakeup fd first if present */ |
||||
if (wakeup_fd_present) { |
||||
fds[idx].fd = ua->wakeup_pipe[0]; |
||||
fds[idx].events = POLLIN; |
||||
fds[idx].revents = 0; |
||||
idx++; |
||||
} |
||||
|
||||
/* Add socket fds using efficient array traversal */ |
||||
int node_idx = 0; |
||||
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
struct socket_node* cur = &ua->sockets->sockets[i]; |
||||
fds[idx].fd = cur->fd; |
||||
fds[idx].events = 0; |
||||
fds[idx].revents = 0; |
||||
|
||||
if (cur->read_cbk) fds[idx].events |= POLLIN; |
||||
if (cur->write_cbk) fds[idx].events |= POLLOUT; |
||||
if (cur->except_cbk) fds[idx].events |= POLLPRI; |
||||
|
||||
if (nodes) { |
||||
nodes[node_idx] = cur; |
||||
} |
||||
idx++; |
||||
node_idx++; |
||||
} |
||||
} |
||||
|
||||
/* Call poll */ |
||||
int ret = poll(fds, total_fds, timeout_ms); |
||||
if (ret < 0) { |
||||
if (errno == EINTR) { |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
perror("poll"); |
||||
free(fds); |
||||
free(nodes); |
||||
return; |
||||
} |
||||
|
||||
/* Process timeouts that may have expired during poll */ |
||||
process_timeouts(ua); |
||||
|
||||
/* Process socket events */ |
||||
if (ret > 0) { |
||||
for (int i = 0; i < total_fds; i++) { |
||||
if (fds[i].revents == 0) continue; |
||||
|
||||
/* Handle wakeup fd separately */ |
||||
if (wakeup_fd_present && i == 0) { |
||||
if (fds[i].revents & POLLIN) { |
||||
drain_wakeup_pipe(ua); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
/* Socket event */ |
||||
int socket_idx = i - wakeup_fd_present; |
||||
struct socket_node* node = nodes[socket_idx]; |
||||
|
||||
/* Check for error conditions first */ |
||||
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { |
||||
/* Treat as exceptional condition */ |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Exceptional data (out-of-band) */ |
||||
if (fds[i].revents & POLLPRI) { |
||||
if (node->except_cbk) { |
||||
node->except_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Read readiness */ |
||||
if (fds[i].revents & POLLIN) { |
||||
if (node->read_cbk) { |
||||
node->read_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
|
||||
/* Write readiness */ |
||||
if (fds[i].revents & POLLOUT) { |
||||
if (node->write_cbk) { |
||||
node->write_cbk(node->fd, node->user_data); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
free(fds); |
||||
free(nodes); |
||||
} |
||||
|
||||
void uasync_mainloop(struct UASYNC* ua) { |
||||
while (1) { |
||||
uasync_poll(ua, -1); |
||||
} |
||||
} |
||||
|
||||
struct UASYNC* uasync_create(void) { |
||||
// Initialize debug system on first use |
||||
static int debug_initialized = 0; |
||||
if (!debug_initialized) { |
||||
debug_config_init(); |
||||
debug_initialized = 1; |
||||
} |
||||
|
||||
struct UASYNC* ua = malloc(sizeof(struct UASYNC)); |
||||
if (!ua) return NULL; |
||||
|
||||
memset(ua, 0, sizeof(struct UASYNC)); |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
ua->wakeup_initialized = 0; |
||||
|
||||
// Create wakeup pipe |
||||
if (pipe(ua->wakeup_pipe) < 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); |
||||
// Continue without wakeup mechanism |
||||
ua->wakeup_pipe[0] = -1; |
||||
ua->wakeup_pipe[1] = -1; |
||||
} else { |
||||
ua->wakeup_initialized = 1; |
||||
// Set non-blocking on read end to avoid blocking if pipe is full |
||||
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); |
||||
if (flags >= 0) { |
||||
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); |
||||
} |
||||
} |
||||
|
||||
ua->sockets = socket_array_create(16); |
||||
if (!ua->sockets) { |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
if (!ua->timeout_heap) { |
||||
socket_array_destroy(ua->sockets); |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
free(ua); |
||||
return NULL; |
||||
} |
||||
|
||||
return ua; |
||||
} |
||||
|
||||
// Print all resources for debugging |
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix) { |
||||
if (!ua) { |
||||
printf("%s: NULL uasync instance\n", prefix); |
||||
return; |
||||
} |
||||
|
||||
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); |
||||
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
|
||||
// Show active timers (removed check for .deleted; heap entries are valid up to .size) |
||||
if (ua->timeout_heap) { |
||||
size_t active_timers = ua->timeout_heap->size; |
||||
for (size_t i = 0; i < ua->timeout_heap->size; i++) { |
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data; |
||||
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", |
||||
node, (unsigned long long)ua->timeout_heap->heap[i].expiration, node->cancelled); |
||||
} |
||||
printf(" Active timers in heap: %zu\n", active_timers); |
||||
} |
||||
|
||||
// Show active sockets |
||||
if (ua->sockets) { |
||||
int active_sockets = 0; |
||||
printf(" Socket array capacity: %d, active: %d\n", |
||||
ua->sockets->capacity, ua->sockets->count); |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
active_sockets++; |
||||
printf(" Socket: fd=%d, active=%d\n", |
||||
ua->sockets->sockets[i].fd, |
||||
ua->sockets->sockets[i].active); |
||||
} |
||||
} |
||||
printf(" Total active sockets: %d\n", active_sockets); |
||||
} |
||||
|
||||
printf("🔚 %s: End of resource report\n\n", prefix); |
||||
} |
||||
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds) { |
||||
if (!ua) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); |
||||
|
||||
// Free all remaining timeouts by popping and freeing data (fixes leak without double-free) |
||||
if (ua->timeout_heap) { |
||||
TimeoutEntry entry; |
||||
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) { |
||||
struct timeout_node* node = (struct timeout_node*)entry.data; |
||||
if (node) { |
||||
ua->timer_free_count++; |
||||
free(node); |
||||
} |
||||
} |
||||
timeout_heap_destroy(ua->timeout_heap); |
||||
ua->timeout_heap = NULL; |
||||
} |
||||
|
||||
// Free all socket nodes using array approach |
||||
if (ua->sockets) { |
||||
// Count and free all active sockets |
||||
int freed_count = 0; |
||||
for (int i = 0; i < ua->sockets->capacity; i++) { |
||||
if (ua->sockets->sockets[i].active) { |
||||
if (close_fds && ua->sockets->sockets[i].fd >= 0) { |
||||
close(ua->sockets->sockets[i].fd); |
||||
} |
||||
ua->socket_free_count++; |
||||
freed_count++; |
||||
} |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); |
||||
socket_array_destroy(ua->sockets); |
||||
} |
||||
|
||||
// Close wakeup pipe |
||||
if (ua->wakeup_initialized) { |
||||
close(ua->wakeup_pipe[0]); |
||||
close(ua->wakeup_pipe[1]); |
||||
} |
||||
|
||||
// Final leak check |
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", |
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->timer_alloc_count, ua->timer_free_count, |
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", |
||||
ua->socket_alloc_count, ua->socket_free_count, |
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); |
||||
abort(); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); |
||||
free(ua); |
||||
} |
||||
|
||||
void uasync_init_instance(struct UASYNC* ua) { |
||||
if (!ua) return; |
||||
|
||||
// Initialize socket array if not present |
||||
if (!ua->sockets) { |
||||
ua->sockets = socket_array_create(16); |
||||
} |
||||
|
||||
if (!ua->timeout_heap) { |
||||
ua->timeout_heap = timeout_heap_create(16); |
||||
} |
||||
} |
||||
|
||||
// Debug statistics |
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { |
||||
if (!ua) return; |
||||
if (timer_alloc) *timer_alloc = ua->timer_alloc_count; |
||||
if (timer_free) *timer_free = ua->timer_free_count; |
||||
if (socket_alloc) *socket_alloc = ua->socket_alloc_count; |
||||
if (socket_free) *socket_free = ua->socket_free_count; |
||||
} |
||||
|
||||
// Wakeup mechanism |
||||
int uasync_wakeup(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
|
||||
char byte = 0; |
||||
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); |
||||
if (ret != 1) { |
||||
// Don't print error from signal handler |
||||
return -1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua) { |
||||
if (!ua || !ua->wakeup_initialized) return -1; |
||||
return ua->wakeup_pipe[1]; |
||||
} |
||||
|
||||
static struct socket_array* socket_array_create(int initial_capacity) { |
||||
struct socket_array* sa = malloc(sizeof(struct socket_array)); |
||||
if (!sa) return NULL; |
||||
sa->sockets = malloc(initial_capacity * sizeof(struct socket_node)); |
||||
if (!sa->sockets) { |
||||
free(sa); |
||||
return NULL; |
||||
} |
||||
sa->fd_to_index = malloc(FD_SETSIZE * sizeof(int)); // Assume FD_SETSIZE defined |
||||
if (!sa->fd_to_index) { |
||||
free(sa->sockets); |
||||
free(sa); |
||||
return NULL; |
||||
} |
||||
memset(sa->fd_to_index, -1, FD_SETSIZE * sizeof(int)); |
||||
sa->index_to_fd = malloc(initial_capacity * sizeof(int)); |
||||
if (!sa->index_to_fd) { |
||||
free(sa->fd_to_index); |
||||
free(sa->sockets); |
||||
free(sa); |
||||
return NULL; |
||||
} |
||||
sa->capacity = initial_capacity; |
||||
sa->count = 0; |
||||
sa->max_fd = 0; |
||||
return sa; |
||||
} |
||||
|
||||
static void socket_array_destroy(struct socket_array* sa) { |
||||
if (sa) { |
||||
free(sa->sockets); |
||||
free(sa->fd_to_index); |
||||
free(sa->index_to_fd); |
||||
free(sa); |
||||
} |
||||
} |
||||
|
||||
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { |
||||
if (fd < 0 || fd >= FD_SETSIZE) return -1; |
||||
if (sa->fd_to_index[fd] != -1) return -1; // Already exists |
||||
|
||||
if (sa->count == sa->capacity) { |
||||
int new_cap = sa->capacity * 2; |
||||
struct socket_node* new_sockets = realloc(sa->sockets, new_cap * sizeof(struct socket_node)); |
||||
if (!new_sockets) return -1; |
||||
sa->sockets = new_sockets; |
||||
int* new_index_to_fd = realloc(sa->index_to_fd, new_cap * sizeof(int)); |
||||
if (!new_index_to_fd) return -1; |
||||
sa->index_to_fd = new_index_to_fd; |
||||
sa->capacity = new_cap; |
||||
} |
||||
|
||||
int index = sa->count++; |
||||
sa->sockets[index].fd = fd; |
||||
sa->sockets[index].read_cbk = read_cbk; |
||||
sa->sockets[index].write_cbk = write_cbk; |
||||
sa->sockets[index].except_cbk = except_cbk; |
||||
sa->sockets[index].user_data = user_data; |
||||
sa->sockets[index].active = 1; |
||||
sa->fd_to_index[fd] = index; |
||||
sa->index_to_fd[index] = fd; |
||||
if (fd > sa->max_fd) sa->max_fd = fd; |
||||
return 0; |
||||
} |
||||
|
||||
static int socket_array_remove(struct socket_array* sa, int fd) { |
||||
if (fd < 0 || fd >= FD_SETSIZE) return -1; |
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1) return -1; |
||||
|
||||
sa->sockets[index].active = 0; |
||||
sa->fd_to_index[fd] = -1; |
||||
|
||||
// Move last to hole |
||||
int last_index = --sa->count; |
||||
if (index != last_index) { |
||||
sa->sockets[index] = sa->sockets[last_index]; |
||||
int last_fd = sa->index_to_fd[last_index]; |
||||
sa->fd_to_index[last_fd] = index; |
||||
sa->index_to_fd[index] = last_fd; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { |
||||
if (fd < 0 || fd >= FD_SETSIZE) return NULL; |
||||
int index = sa->fd_to_index[fd]; |
||||
if (index == -1) return NULL; |
||||
return &sa->sockets[index]; |
||||
} |
||||
@ -1,69 +0,0 @@
|
||||
// uasync.h |
||||
|
||||
// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает. |
||||
|
||||
#ifndef UASYNC_H |
||||
#define UASYNC_H |
||||
|
||||
#include <sys/time.h> |
||||
#include <stddef.h> |
||||
#include "timeout_heap.h" |
||||
|
||||
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout |
||||
typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket |
||||
// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е |
||||
|
||||
|
||||
// Error type |
||||
typedef int err_t; |
||||
#define ERR_OK 0 |
||||
#define ERR_FAIL -1 |
||||
|
||||
// Uasync instance structure |
||||
struct UASYNC { |
||||
TimeoutHeap* timeout_heap; // Heap for timeout management |
||||
struct socket_array* sockets; // Array-based socket management |
||||
// Debug counters for memory allocation tracking |
||||
size_t timer_alloc_count; |
||||
size_t timer_free_count; |
||||
size_t socket_alloc_count; |
||||
size_t socket_free_count; |
||||
// Wakeup pipe for interrupting poll |
||||
int wakeup_pipe[2]; // [0] read, [1] write |
||||
int wakeup_initialized; |
||||
}; |
||||
|
||||
// Type definitions |
||||
typedef struct UASYNC uasync_t; |
||||
typedef struct UASYNC UASYNC_t; |
||||
|
||||
// Instance API - основной API для работы с uasync |
||||
struct UASYNC* uasync_create(void); |
||||
void uasync_destroy(struct UASYNC* ua); |
||||
void uasync_init_instance(struct UASYNC* ua); |
||||
|
||||
// Timeouts, timebase = 0.1 mS |
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback); |
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id); |
||||
|
||||
// Sockets |
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg); |
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id); |
||||
|
||||
// Single iteration of event loop with timeout (timebase units) |
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb); |
||||
|
||||
// Mainloop (бесконечный цикл, __noreturn) |
||||
void uasync_mainloop(struct UASYNC* ua); |
||||
|
||||
// Debug statistics |
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free); |
||||
|
||||
// Print all resources (timers, sockets) for debugging |
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix); |
||||
|
||||
// Wakeup mechanism for interrupting poll |
||||
int uasync_wakeup(struct UASYNC* ua); |
||||
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers) |
||||
|
||||
#endif // UASYNC_H |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,491 +0,0 @@
|
||||
// test_etcp_simple_traffic.c - Simplified ETCP traffic flow debugging without sockets |
||||
// This test focuses purely on ETCP protocol traffic analysis using direct packet injection |
||||
|
||||
#include "../src/etcp.h" |
||||
#include "../src/etcp_connections.h" |
||||
#include "../src/etcp_loadbalancer.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/memory_pool.h" |
||||
#include "../lib/ll_queue.h" |
||||
#include "../src/secure_channel.h" |
||||
#include "../src/crc32.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <pthread.h> |
||||
#include <time.h> |
||||
#include <errno.h> |
||||
|
||||
// Simplified traffic analysis - focus on ETCP protocol packets only |
||||
typedef struct { |
||||
uint64_t timestamp; |
||||
uint16_t packet_id; |
||||
uint8_t packet_type; |
||||
uint16_t data_len; |
||||
uint8_t data[1500]; // Copy of packet data for analysis |
||||
} packet_capture_t; |
||||
|
||||
typedef struct { |
||||
packet_capture_t captures[1000]; |
||||
size_t capture_count; |
||||
pthread_mutex_t lock; |
||||
} traffic_analyzer_t; |
||||
|
||||
// Test instance - simplified version without actual sockets |
||||
typedef struct { |
||||
struct UTUN_INSTANCE* instance; |
||||
struct ETCP_CONN* etcp_conn; |
||||
traffic_analyzer_t* analyzer; |
||||
uint64_t node_id; |
||||
int is_server; |
||||
int running; |
||||
} test_instance_t; |
||||
|
||||
// Global test state |
||||
static test_instance_t* server_instance = NULL; |
||||
static test_instance_t* client_instance = NULL; |
||||
|
||||
// Enhanced packet analysis function |
||||
static void analyze_etcp_packet(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len, const char* direction) { |
||||
if (!etcp || !data || len == 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "analyze_etcp_packet: invalid parameters (etcp=%p, data=%p, len=%u)", |
||||
etcp, data, len); |
||||
return; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PACKET %s: etcp=%p, len=%u, first_byte=0x%02x", |
||||
direction, etcp, len, data[0]); |
||||
|
||||
// Parse ETCP sections |
||||
uint16_t pos = 0; |
||||
int section_count = 0; |
||||
|
||||
while (pos < len) { |
||||
if (pos + 3 > len) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Invalid section header at pos %u (need 3 bytes, have %u)", |
||||
pos, len - pos); |
||||
break; |
||||
} |
||||
|
||||
uint8_t section_type = data[pos]; |
||||
uint16_t section_len = (data[pos+1] << 8) | data[pos+2]; |
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Section %d: type=0x%02x, len=%u at pos=%u", |
||||
section_count, section_type, section_len, pos); |
||||
|
||||
if (pos + 3 + section_len > len || section_len > 1500) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Section length %u exceeds packet boundary (pos=%u, total=%u) or max size", |
||||
section_len, pos, len); |
||||
break; |
||||
} |
||||
|
||||
// Analyze specific section types |
||||
uint8_t* section_data = data + pos + 3; |
||||
|
||||
switch (section_type) { |
||||
case ETCP_SECTION_PAYLOAD: { |
||||
if (section_len >= 2) { |
||||
uint16_t packet_id = (section_data[0] << 8) | section_data[1]; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " PAYLOAD: packet_id=%u, payload_len=%u", |
||||
packet_id, section_len - 2); |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_SECTION_ACK: { |
||||
if (section_len >= 1) { |
||||
uint8_t ack_count = section_data[0]; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " ACK: count=%u", ack_count); |
||||
for (int i = 0; i < ack_count && i*4+5 <= section_len; i++) { |
||||
uint16_t ack_id = (section_data[1+i*4] << 8) | section_data[2+i*4]; |
||||
uint16_t ack_ts = (section_data[3+i*4] << 8) | section_data[4+i*4]; |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " ACK[%d]: id=%u, ts=%u", i, ack_id, ack_ts); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_SECTION_TIMESTAMP: { |
||||
if (section_len == 2) { |
||||
uint16_t ts = (section_data[0] << 8) | section_data[1]; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " TIMESTAMP: ts=%u", ts); |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_INIT_REQUEST: { |
||||
if (section_len >= 76) { // node_id(8) + mtu(2) + keepalive(2) + pubkey(64) |
||||
uint64_t node_id = 0; |
||||
for (int i = 0; i < 8; i++) { |
||||
node_id = (node_id << 8) | section_data[i]; |
||||
} |
||||
uint16_t mtu = (section_data[8] << 8) | section_data[9]; |
||||
uint16_t keepalive = (section_data[10] << 8) | section_data[11]; |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_REQUEST: node_id=%llu, mtu=%u, keepalive=%u", |
||||
(unsigned long long)node_id, mtu, keepalive); |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_INIT_RESPONSE: { |
||||
if (section_len >= 10) { // node_id(8) + mtu(2) |
||||
uint64_t node_id = 0; |
||||
for (int i = 0; i < 8; i++) { |
||||
node_id = (node_id << 8) | section_data[i]; |
||||
} |
||||
uint16_t mtu = (section_data[8] << 8) | section_data[9]; |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_RESPONSE: node_id=%llu, mtu=%u", |
||||
(unsigned long long)node_id, mtu); |
||||
} |
||||
break; |
||||
} |
||||
default: { |
||||
if (section_type >= ETCP_SECTION_RETRANS && section_type <= ETCP_SECTION_RETRANS + 0x1F) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " RETRANS: base_id=%u", section_type - ETCP_SECTION_RETRANS); |
||||
// Parse retransmission requests |
||||
for (int i = 0; i < section_len; i += 2) { |
||||
if (i+1 < section_len) { |
||||
uint16_t req_id = (section_data[i] << 8) | section_data[i+1]; |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " RETRANS_REQ: id=%u", req_id); |
||||
} |
||||
} |
||||
} else { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " UNKNOWN: type=0x%02x, len=%u", section_type, section_len); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
pos += 3 + section_len; |
||||
section_count++; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Packet analysis complete: %d sections processed", section_count); |
||||
} |
||||
|
||||
// Simulate packet reception for analysis |
||||
static void simulate_packet_rx(test_instance_t* inst, uint8_t* data, uint16_t len) { |
||||
if (!inst || !inst->etcp_conn || !data) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE RX: inst=%s, len=%u", |
||||
inst->is_server ? "server" : "client", len); |
||||
|
||||
// Analyze the packet first |
||||
analyze_etcp_packet(inst->etcp_conn, data, len, "SIM_RX"); |
||||
|
||||
// Create a mock ETCP_DGRAM for processing |
||||
struct ETCP_DGRAM mock_dgram; |
||||
memset(&mock_dgram, 0, sizeof(mock_dgram)); |
||||
mock_dgram.link = NULL; // Will be set by connection processing |
||||
mock_dgram.data_len = len > 1500 ? 1500 : len; |
||||
mock_dgram.timestamp = get_current_timestamp(); |
||||
if (mock_dgram.data_len > 0) { |
||||
memcpy(mock_dgram.data, data, mock_dgram.data_len); |
||||
} |
||||
|
||||
// Process through ETCP input |
||||
etcp_conn_input(&mock_dgram); |
||||
} |
||||
|
||||
// Simulate packet transmission for analysis |
||||
static void simulate_packet_tx(test_instance_t* inst) { |
||||
if (!inst || !inst->etcp_conn) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE TX: inst=%s", |
||||
inst->is_server ? "server" : "client"); |
||||
|
||||
struct ETCP_DGRAM* dgram = etcp_request_pkt(inst->etcp_conn); |
||||
if (dgram) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Generated packet: len=%u", dgram->data_len); |
||||
analyze_etcp_packet(inst->etcp_conn, dgram->data, dgram->data_len, "SIM_TX"); |
||||
|
||||
// In a real scenario, this would be sent over the network |
||||
// For now, we just analyze it and free it |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Would send %u bytes to peer", dgram->data_len); |
||||
} else { |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "No packets available to send"); |
||||
} |
||||
} |
||||
|
||||
// Create simplified test instance |
||||
static test_instance_t* create_simple_instance(uint64_t node_id, int is_server) { |
||||
test_instance_t* inst = calloc(1, sizeof(test_instance_t)); |
||||
if (!inst) return NULL; |
||||
|
||||
inst->node_id = node_id; |
||||
inst->is_server = is_server; |
||||
inst->running = 1; |
||||
|
||||
// Create traffic analyzer |
||||
inst->analyzer = calloc(1, sizeof(traffic_analyzer_t)); |
||||
if (inst->analyzer) { |
||||
pthread_mutex_init(&inst->analyzer->lock, NULL); |
||||
} |
||||
|
||||
// Create UTUN instance directly without TUN device |
||||
inst->instance = calloc(1, sizeof(struct UTUN_INSTANCE)); |
||||
if (!inst->instance) { |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
// Initialize the instance manually |
||||
inst->instance->node_id = node_id; |
||||
inst->instance->running = 1; |
||||
inst->instance->log_fp = stderr; |
||||
|
||||
// Initialize crypto keys (hardcoded for test) |
||||
const char* priv_key_hex = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb"; |
||||
const char* pub_key_hex = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9"; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu", |
||||
strlen(priv_key_hex), strlen(pub_key_hex)); |
||||
|
||||
// Parse keys |
||||
for (int i = 0; i < 32; i++) { |
||||
if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i); |
||||
} |
||||
} |
||||
for (int i = 0; i < 64; i++) { |
||||
if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i); |
||||
} |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Keys parsed successfully"); |
||||
|
||||
// Create uasync context |
||||
inst->instance->ua = uasync_create(); |
||||
if (!inst->instance->ua) { |
||||
free(inst->instance); |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
uasync_init_instance(inst->instance->ua); |
||||
|
||||
// Create packet pool |
||||
inst->instance->pkt_pool = memory_pool_init(1600); // 1600 byte packets |
||||
if (!inst->instance->pkt_pool) { |
||||
uasync_destroy(inst->instance->ua); |
||||
free(inst->instance); |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
// Create ETCP connection |
||||
inst->etcp_conn = etcp_connection_create(inst->instance); |
||||
if (!inst->etcp_conn) { |
||||
memory_pool_destroy(inst->instance->pkt_pool); |
||||
uasync_destroy(inst->instance->ua); |
||||
free(inst->instance); |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Created %s instance (node_id=%llu)", |
||||
is_server ? "server" : "client", (unsigned long long)node_id); |
||||
|
||||
return inst; |
||||
} |
||||
|
||||
// Cleanup simplified test instance |
||||
static void destroy_simple_instance(test_instance_t* inst) { |
||||
if (!inst) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Destroying %s instance", inst->is_server ? "server" : "client"); |
||||
|
||||
if (inst->etcp_conn) { |
||||
etcp_connection_close(inst->etcp_conn); |
||||
} |
||||
|
||||
if (inst->instance) { |
||||
if (inst->instance->pkt_pool) { |
||||
memory_pool_destroy(inst->instance->pkt_pool); |
||||
} |
||||
if (inst->instance->ua) { |
||||
uasync_destroy(inst->instance->ua); |
||||
} |
||||
free(inst->instance); |
||||
} |
||||
|
||||
if (inst->analyzer) { |
||||
pthread_mutex_destroy(&inst->analyzer->lock); |
||||
free(inst->analyzer); |
||||
} |
||||
|
||||
free(inst); |
||||
} |
||||
|
||||
// Generate test packets manually |
||||
static void generate_test_packets(test_instance_t* inst, int count) { |
||||
if (!inst || !inst->etcp_conn) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating %d test packets for %s instance", |
||||
count, inst->is_server ? "server" : "client"); |
||||
|
||||
for (int i = 0; i < count; i++) { |
||||
struct ll_entry* entry = queue_entry_new(100); // 100 byte packets |
||||
if (entry) { |
||||
// Fill with test data |
||||
uint8_t* data = (uint8_t*)ll_entry_data(entry); |
||||
for (int j = 0; j < 100; j++) { |
||||
data[j] = (uint8_t)((i+1) * 10 + j); // Pattern data |
||||
} |
||||
queue_entry_put(inst->etcp_conn->input_queue, entry); |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Queued test packet %d for %s", |
||||
i+1, inst->is_server ? "server" : "client"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Main test function |
||||
int main(int argc, char* argv[]) { |
||||
printf("=== ETCP Traffic Flow Debugging Test (Simplified) ===\n"); |
||||
|
||||
// Initialize debug system |
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_DEBUG); |
||||
debug_enable_category(DEBUG_CATEGORY_ETCP); |
||||
debug_enable_timestamp(1); |
||||
debug_enable_function_name(1); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Starting simplified ETCP traffic flow debugging test"); |
||||
|
||||
// Create server instance |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating server instance..."); |
||||
server_instance = create_simple_instance(0x1111111111111111ULL, 1); |
||||
if (!server_instance) { |
||||
fprintf(stderr, "Failed to create server instance\n"); |
||||
return 1; |
||||
} |
||||
|
||||
// Create client instance |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating client instance..."); |
||||
client_instance = create_simple_instance(0x2222222222222222ULL, 0); |
||||
if (!client_instance) { |
||||
fprintf(stderr, "Failed to create client instance\n"); |
||||
destroy_simple_instance(server_instance); |
||||
return 1; |
||||
} |
||||
|
||||
// Phase 1: Test basic packet generation and analysis |
||||
printf("\n=== Phase 1: Basic Packet Generation ===\n"); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating test packets..."); |
||||
generate_test_packets(server_instance, 5); |
||||
generate_test_packets(client_instance, 5); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating packet transmission..."); |
||||
|
||||
// Let server generate some packets |
||||
for (int i = 0; i < 3; i++) { |
||||
simulate_packet_tx(server_instance); |
||||
usleep(10000); // 1ms delay |
||||
} |
||||
|
||||
// Let client generate some packets |
||||
for (int i = 0; i < 3; i++) { |
||||
simulate_packet_tx(client_instance); |
||||
usleep(10000); // 1ms delay |
||||
} |
||||
|
||||
// Phase 2: Test connection establishment simulation |
||||
printf("\n=== Phase 2: Connection Establishment Simulation ===\n"); |
||||
|
||||
// Simulate INIT_REQUEST from client - smaller version for testing |
||||
uint8_t init_request[] = { |
||||
ETCP_INIT_REQUEST, // Section type |
||||
0x00, 0x14, // Section length (20 bytes) - smaller for test |
||||
// Node ID (8 bytes) |
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, |
||||
// MTU (2 bytes) |
||||
0x05, 0xDC, // 1500 |
||||
// Keepalive (2 bytes) |
||||
0x00, 0x64, // 100 |
||||
// Fake public key (8 bytes) - just for testing |
||||
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22 |
||||
}; |
||||
// Total: 3 + 20 = 23 bytes |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_REQUEST from client..."); |
||||
simulate_packet_rx(server_instance, init_request, sizeof(init_request)); |
||||
|
||||
// Simulate INIT_RESPONSE from server |
||||
uint8_t init_response[] = { |
||||
ETCP_INIT_RESPONSE, // Section type |
||||
0x00, 0x0A, // Section length (10 bytes) |
||||
// Node ID (8 bytes) |
||||
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, |
||||
// MTU (2 bytes) |
||||
0x05, 0xDC // 1500 |
||||
}; |
||||
// Total: 3 + 10 = 13 bytes |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_RESPONSE from server..."); |
||||
simulate_packet_rx(client_instance, init_response, sizeof(init_response)); |
||||
|
||||
// Phase 3: Test mixed traffic with ACKs and timestamps |
||||
printf("\n=== Phase 3: Mixed Traffic Simulation ===\n"); |
||||
|
||||
// Generate more traffic |
||||
generate_test_packets(server_instance, 3); |
||||
generate_test_packets(client_instance, 3); |
||||
|
||||
// Simulate some packets with multiple sections |
||||
uint8_t complex_packet[] = { |
||||
// TIMESTAMP section: 3 bytes header + 2 bytes data = 5 bytes |
||||
ETCP_SECTION_TIMESTAMP, 0x00, 0x02, 0x12, 0x34, |
||||
// ACK section with 2 ACKs: 3 bytes header + 1 byte count + 2*4 bytes = 12 bytes total |
||||
ETCP_SECTION_ACK, 0x00, 0x09, 0x02, 0x00, 0x01, 0x12, 0x34, 0x00, 0x02, 0x12, 0x35, |
||||
// PAYLOAD section: 3 bytes header + 2 bytes ID + 5 bytes data = 10 bytes total |
||||
ETCP_SECTION_PAYLOAD, 0x00, 0x08, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F // "Hello" |
||||
}; |
||||
// Total: 5 + 12 + 10 = 27 bytes |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating complex packet with multiple sections..."); |
||||
simulate_packet_rx(server_instance, complex_packet, sizeof(complex_packet)); |
||||
|
||||
// Phase 4: Test retransmission requests |
||||
printf("\n=== Phase 4: Retransmission Testing ===\n"); |
||||
|
||||
uint8_t retrans_request[] = { |
||||
ETCP_SECTION_RETRANS + 0x02, // RETRANS section with base ID 2 |
||||
0x00, 0x04, // Section length (4 bytes) |
||||
0x00, 0x03, // Request retransmission of packet ID 3 |
||||
0x00, 0x04 // Request retransmission of packet ID 4 |
||||
}; |
||||
// Total: 3 + 4 = 7 bytes |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating retransmission request..."); |
||||
simulate_packet_rx(client_instance, retrans_request, sizeof(retrans_request)); |
||||
|
||||
// Final packet generation |
||||
printf("\n=== Final Phase: Additional Traffic ===\n"); |
||||
|
||||
// Generate final packets |
||||
for (int i = 0; i < 5; i++) { |
||||
simulate_packet_tx(server_instance); |
||||
simulate_packet_tx(client_instance); |
||||
usleep(5000); // 0.5ms delay |
||||
} |
||||
|
||||
// Print summary |
||||
printf("\n=== Traffic Analysis Summary ===\n"); |
||||
printf("Server instance: processed packets with detailed section analysis\n"); |
||||
printf("Client instance: processed packets with detailed section analysis\n"); |
||||
printf("Connection establishment: INIT handshake simulated\n"); |
||||
printf("Mixed traffic: ACK, TIMESTAMP, PAYLOAD sections processed\n"); |
||||
printf("Retransmission: RETRANS requests processed\n"); |
||||
|
||||
// Cleanup |
||||
destroy_simple_instance(server_instance); |
||||
destroy_simple_instance(client_instance); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Test completed"); |
||||
printf("\n=== Test completed successfully ===\n"); |
||||
|
||||
return 0; |
||||
} |
||||
@ -1,491 +0,0 @@
|
||||
// test_etcp_simple_traffic.c - Simplified ETCP traffic flow debugging without sockets
|
||||
// This test focuses purely on ETCP protocol traffic analysis using direct packet injection
|
||||
|
||||
#include "../src/etcp.h" |
||||
#include "../src/etcp_connections.h" |
||||
#include "../src/etcp_loadbalancer.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/memory_pool.h" |
||||
#include "../lib/ll_queue.h" |
||||
#include "../src/secure_channel.h" |
||||
#include "../src/crc32.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <pthread.h> |
||||
#include <time.h> |
||||
#include <errno.h> |
||||
|
||||
// Simplified traffic analysis - focus on ETCP protocol packets only
|
||||
typedef struct { |
||||
uint64_t timestamp; |
||||
uint16_t packet_id; |
||||
uint8_t packet_type; |
||||
uint16_t data_len; |
||||
uint8_t data[1500]; // Copy of packet data for analysis
|
||||
} packet_capture_t; |
||||
|
||||
typedef struct { |
||||
packet_capture_t captures[1000]; |
||||
size_t capture_count; |
||||
pthread_mutex_t lock; |
||||
} traffic_analyzer_t; |
||||
|
||||
// Test instance - simplified version without actual sockets
|
||||
typedef struct { |
||||
struct UTUN_INSTANCE* instance; |
||||
struct ETCP_CONN* etcp_conn; |
||||
traffic_analyzer_t* analyzer; |
||||
uint64_t node_id; |
||||
int is_server; |
||||
int running; |
||||
} test_instance_t; |
||||
|
||||
// Global test state
|
||||
static test_instance_t* server_instance = NULL; |
||||
static test_instance_t* client_instance = NULL; |
||||
|
||||
// Enhanced packet analysis function
|
||||
static void analyze_etcp_packet(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len, const char* direction) { |
||||
if (!etcp || !data || len == 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "analyze_etcp_packet: invalid parameters (etcp=%p, data=%p, len=%u)",
|
||||
etcp, data, len); |
||||
return; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PACKET %s: etcp=%p, len=%u, first_byte=0x%02x",
|
||||
direction, etcp, len, data[0]); |
||||
|
||||
// Parse ETCP sections
|
||||
uint16_t pos = 0; |
||||
int section_count = 0; |
||||
|
||||
while (pos < len) { |
||||
if (pos + 3 > len) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Invalid section header at pos %u (need 3 bytes, have %u)",
|
||||
pos, len - pos); |
||||
break; |
||||
} |
||||
|
||||
uint8_t section_type = data[pos]; |
||||
uint16_t section_len = (data[pos+1] << 8) | data[pos+2]; |
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Section %d: type=0x%02x, len=%u at pos=%u",
|
||||
section_count, section_type, section_len, pos); |
||||
|
||||
if (pos + 3 + section_len > len || section_len > 1500) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Section length %u exceeds packet boundary (pos=%u, total=%u) or max size",
|
||||
section_len, pos, len); |
||||
break; |
||||
} |
||||
|
||||
// Analyze specific section types
|
||||
uint8_t* section_data = data + pos + 3; |
||||
|
||||
switch (section_type) { |
||||
case ETCP_SECTION_PAYLOAD: { |
||||
if (section_len >= 2) { |
||||
uint16_t packet_id = (section_data[0] << 8) | section_data[1]; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " PAYLOAD: packet_id=%u, payload_len=%u",
|
||||
packet_id, section_len - 2); |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_SECTION_ACK: { |
||||
if (section_len >= 1) { |
||||
uint8_t ack_count = section_data[0]; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " ACK: count=%u", ack_count); |
||||
for (int i = 0; i < ack_count && i*4+5 <= section_len; i++) { |
||||
uint16_t ack_id = (section_data[1+i*4] << 8) | section_data[2+i*4]; |
||||
uint16_t ack_ts = (section_data[3+i*4] << 8) | section_data[4+i*4]; |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " ACK[%d]: id=%u, ts=%u", i, ack_id, ack_ts); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_SECTION_TIMESTAMP: { |
||||
if (section_len == 2) { |
||||
uint16_t ts = (section_data[0] << 8) | section_data[1]; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " TIMESTAMP: ts=%u", ts); |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_INIT_REQUEST: { |
||||
if (section_len >= 76) { // node_id(8) + mtu(2) + keepalive(2) + pubkey(64)
|
||||
uint64_t node_id = 0; |
||||
for (int i = 0; i < 8; i++) { |
||||
node_id = (node_id << 8) | section_data[i]; |
||||
} |
||||
uint16_t mtu = (section_data[8] << 8) | section_data[9]; |
||||
uint16_t keepalive = (section_data[10] << 8) | section_data[11]; |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_REQUEST: node_id=%llu, mtu=%u, keepalive=%u", |
||||
(unsigned long long)node_id, mtu, keepalive); |
||||
} |
||||
break; |
||||
} |
||||
case ETCP_INIT_RESPONSE: { |
||||
if (section_len >= 10) { // node_id(8) + mtu(2)
|
||||
uint64_t node_id = 0; |
||||
for (int i = 0; i < 8; i++) { |
||||
node_id = (node_id << 8) | section_data[i]; |
||||
} |
||||
uint16_t mtu = (section_data[8] << 8) | section_data[9]; |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_RESPONSE: node_id=%llu, mtu=%u", |
||||
(unsigned long long)node_id, mtu); |
||||
} |
||||
break; |
||||
} |
||||
default: { |
||||
if (section_type >= ETCP_SECTION_RETRANS && section_type <= ETCP_SECTION_RETRANS + 0x1F) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " RETRANS: base_id=%u", section_type - ETCP_SECTION_RETRANS); |
||||
// Parse retransmission requests
|
||||
for (int i = 0; i < section_len; i += 2) { |
||||
if (i+1 < section_len) { |
||||
uint16_t req_id = (section_data[i] << 8) | section_data[i+1]; |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " RETRANS_REQ: id=%u", req_id); |
||||
} |
||||
} |
||||
} else { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " UNKNOWN: type=0x%02x, len=%u", section_type, section_len); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
pos += 3 + section_len; |
||||
section_count++; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Packet analysis complete: %d sections processed", section_count); |
||||
} |
||||
|
||||
// Simulate packet reception for analysis
|
||||
static void simulate_packet_rx(test_instance_t* inst, uint8_t* data, uint16_t len) { |
||||
if (!inst || !inst->etcp_conn || !data) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE RX: inst=%s, len=%u",
|
||||
inst->is_server ? "server" : "client", len); |
||||
|
||||
// Analyze the packet first
|
||||
analyze_etcp_packet(inst->etcp_conn, data, len, "SIM_RX"); |
||||
|
||||
// Create a mock ETCP_DGRAM for processing
|
||||
struct ETCP_DGRAM mock_dgram; |
||||
memset(&mock_dgram, 0, sizeof(mock_dgram)); |
||||
mock_dgram.link = NULL; // Will be set by connection processing
|
||||
mock_dgram.data_len = len > 1500 ? 1500 : len; |
||||
mock_dgram.timestamp = get_current_timestamp(); |
||||
if (mock_dgram.data_len > 0) { |
||||
memcpy(mock_dgram.data, data, mock_dgram.data_len); |
||||
} |
||||
|
||||
// Process through ETCP input
|
||||
etcp_conn_input(&mock_dgram); |
||||
} |
||||
|
||||
// Simulate packet transmission for analysis
|
||||
static void simulate_packet_tx(test_instance_t* inst) { |
||||
if (!inst || !inst->etcp_conn) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE TX: inst=%s",
|
||||
inst->is_server ? "server" : "client"); |
||||
|
||||
struct ETCP_DGRAM* dgram = etcp_request_pkt(inst->etcp_conn); |
||||
if (dgram) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Generated packet: len=%u", dgram->data_len); |
||||
analyze_etcp_packet(inst->etcp_conn, dgram->data, dgram->data_len, "SIM_TX"); |
||||
|
||||
// In a real scenario, this would be sent over the network
|
||||
// For now, we just analyze it and free it
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Would send %u bytes to peer", dgram->data_len); |
||||
} else { |
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "No packets available to send"); |
||||
} |
||||
} |
||||
|
||||
// Create simplified test instance
|
||||
static test_instance_t* create_simple_instance(uint64_t node_id, int is_server) { |
||||
test_instance_t* inst = calloc(1, sizeof(test_instance_t)); |
||||
if (!inst) return NULL; |
||||
|
||||
inst->node_id = node_id; |
||||
inst->is_server = is_server; |
||||
inst->running = 1; |
||||
|
||||
// Create traffic analyzer
|
||||
inst->analyzer = calloc(1, sizeof(traffic_analyzer_t)); |
||||
if (inst->analyzer) { |
||||
pthread_mutex_init(&inst->analyzer->lock, NULL); |
||||
} |
||||
|
||||
// Create UTUN instance directly without TUN device
|
||||
inst->instance = calloc(1, sizeof(struct UTUN_INSTANCE)); |
||||
if (!inst->instance) { |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
// Initialize the instance manually
|
||||
inst->instance->node_id = node_id; |
||||
inst->instance->running = 1; |
||||
inst->instance->log_fp = stderr; |
||||
|
||||
// Initialize crypto keys (hardcoded for test)
|
||||
const char* priv_key_hex = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb"; |
||||
const char* pub_key_hex = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9"; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu",
|
||||
strlen(priv_key_hex), strlen(pub_key_hex)); |
||||
|
||||
// Parse keys
|
||||
for (int i = 0; i < 32; i++) { |
||||
if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i); |
||||
} |
||||
} |
||||
for (int i = 0; i < 64; i++) { |
||||
if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i); |
||||
} |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Keys parsed successfully"); |
||||
|
||||
// Create uasync context
|
||||
inst->instance->ua = uasync_create(); |
||||
if (!inst->instance->ua) { |
||||
free(inst->instance); |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
uasync_init_instance(inst->instance->ua); |
||||
|
||||
// Create packet pool
|
||||
inst->instance->pkt_pool = memory_pool_init(1600); // 1600 byte packets
|
||||
if (!inst->instance->pkt_pool) { |
||||
uasync_destroy(inst->instance->ua); |
||||
free(inst->instance); |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
// Create ETCP connection
|
||||
inst->etcp_conn = etcp_connection_create(inst->instance); |
||||
if (!inst->etcp_conn) { |
||||
memory_pool_destroy(inst->instance->pkt_pool); |
||||
uasync_destroy(inst->instance->ua); |
||||
free(inst->instance); |
||||
free(inst->analyzer); |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Created %s instance (node_id=%llu)", |
||||
is_server ? "server" : "client", (unsigned long long)node_id); |
||||
|
||||
return inst; |
||||
} |
||||
|
||||
// Cleanup simplified test instance
|
||||
static void destroy_simple_instance(test_instance_t* inst) { |
||||
if (!inst) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Destroying %s instance", inst->is_server ? "server" : "client"); |
||||
|
||||
if (inst->etcp_conn) { |
||||
etcp_connection_close(inst->etcp_conn); |
||||
} |
||||
|
||||
if (inst->instance) { |
||||
if (inst->instance->pkt_pool) { |
||||
memory_pool_destroy(inst->instance->pkt_pool); |
||||
} |
||||
if (inst->instance->ua) { |
||||
uasync_destroy(inst->instance->ua); |
||||
} |
||||
free(inst->instance); |
||||
} |
||||
|
||||
if (inst->analyzer) { |
||||
pthread_mutex_destroy(&inst->analyzer->lock); |
||||
free(inst->analyzer); |
||||
} |
||||
|
||||
free(inst); |
||||
} |
||||
|
||||
// Generate test packets manually
|
||||
static void generate_test_packets(test_instance_t* inst, int count) { |
||||
if (!inst || !inst->etcp_conn) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating %d test packets for %s instance",
|
||||
count, inst->is_server ? "server" : "client"); |
||||
|
||||
for (int i = 0; i < count; i++) { |
||||
struct ll_entry* entry = queue_entry_new(100); // 100 byte packets
|
||||
if (entry) { |
||||
// Fill with test data
|
||||
uint8_t* data = (uint8_t*)ll_entry_data(entry); |
||||
for (int j = 0; j < 100; j++) { |
||||
data[j] = (uint8_t)((i+1) * 10 + j); // Pattern data
|
||||
} |
||||
queue_entry_put(inst->etcp_conn->input_queue, entry); |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Queued test packet %d for %s",
|
||||
i+1, inst->is_server ? "server" : "client"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Main test function
|
||||
int main(int argc, char* argv[]) { |
||||
printf("=== ETCP Traffic Flow Debugging Test (Simplified) ===\n"); |
||||
|
||||
// Initialize debug system
|
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_DEBUG); |
||||
debug_enable_category(DEBUG_CATEGORY_ETCP); |
||||
debug_enable_timestamp(1); |
||||
debug_enable_function_name(1); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Starting simplified ETCP traffic flow debugging test"); |
||||
|
||||
// Create server instance
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating server instance..."); |
||||
server_instance = create_simple_instance(0x1111111111111111ULL, 1); |
||||
if (!server_instance) { |
||||
fprintf(stderr, "Failed to create server instance\n"); |
||||
return 1; |
||||
} |
||||
|
||||
// Create client instance
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating client instance..."); |
||||
client_instance = create_simple_instance(0x2222222222222222ULL, 0); |
||||
if (!client_instance) { |
||||
fprintf(stderr, "Failed to create client instance\n"); |
||||
destroy_simple_instance(server_instance); |
||||
return 1; |
||||
} |
||||
|
||||
// Phase 1: Test basic packet generation and analysis
|
||||
printf("\n=== Phase 1: Basic Packet Generation ===\n"); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating test packets..."); |
||||
generate_test_packets(server_instance, 5); |
||||
generate_test_packets(client_instance, 5); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating packet transmission..."); |
||||
|
||||
// Let server generate some packets
|
||||
for (int i = 0; i < 3; i++) { |
||||
simulate_packet_tx(server_instance); |
||||
usleep(10000); // 1ms delay
|
||||
} |
||||
|
||||
// Let client generate some packets
|
||||
for (int i = 0; i < 3; i++) { |
||||
simulate_packet_tx(client_instance); |
||||
usleep(10000); // 1ms delay
|
||||
} |
||||
|
||||
// Phase 2: Test connection establishment simulation
|
||||
printf("\n=== Phase 2: Connection Establishment Simulation ===\n"); |
||||
|
||||
// Simulate INIT_REQUEST from client - smaller version for testing
|
||||
uint8_t init_request[] = { |
||||
ETCP_INIT_REQUEST, // Section type
|
||||
0x00, 0x14, // Section length (20 bytes) - smaller for test
|
||||
// Node ID (8 bytes)
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, |
||||
// MTU (2 bytes)
|
||||
0x05, 0xDC, // 1500
|
||||
// Keepalive (2 bytes)
|
||||
0x00, 0x64, // 100
|
||||
// Fake public key (8 bytes) - just for testing
|
||||
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22 |
||||
}; |
||||
// Total: 3 + 20 = 23 bytes
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_REQUEST from client..."); |
||||
simulate_packet_rx(server_instance, init_request, sizeof(init_request)); |
||||
|
||||
// Simulate INIT_RESPONSE from server
|
||||
uint8_t init_response[] = { |
||||
ETCP_INIT_RESPONSE, // Section type
|
||||
0x00, 0x0A, // Section length (10 bytes)
|
||||
// Node ID (8 bytes)
|
||||
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, |
||||
// MTU (2 bytes)
|
||||
0x05, 0xDC // 1500
|
||||
}; |
||||
// Total: 3 + 10 = 13 bytes
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_RESPONSE from server..."); |
||||
simulate_packet_rx(client_instance, init_response, sizeof(init_response)); |
||||
|
||||
// Phase 3: Test mixed traffic with ACKs and timestamps
|
||||
printf("\n=== Phase 3: Mixed Traffic Simulation ===\n"); |
||||
|
||||
// Generate more traffic
|
||||
generate_test_packets(server_instance, 3); |
||||
generate_test_packets(client_instance, 3); |
||||
|
||||
// Simulate some packets with multiple sections
|
||||
uint8_t complex_packet[] = { |
||||
// TIMESTAMP section: 3 bytes header + 2 bytes data = 5 bytes
|
||||
ETCP_SECTION_TIMESTAMP, 0x00, 0x02, 0x12, 0x34, |
||||
// ACK section with 2 ACKs: 3 bytes header + 1 byte count + 2*4 bytes = 12 bytes total
|
||||
ETCP_SECTION_ACK, 0x00, 0x09, 0x02, 0x00, 0x01, 0x12, 0x34, 0x00, 0x02, 0x12, 0x35, |
||||
// PAYLOAD section: 3 bytes header + 2 bytes ID + 5 bytes data = 10 bytes total
|
||||
ETCP_SECTION_PAYLOAD, 0x00, 0x08, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F // "Hello"
|
||||
}; |
||||
// Total: 5 + 12 + 10 = 27 bytes
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating complex packet with multiple sections..."); |
||||
simulate_packet_rx(server_instance, complex_packet, sizeof(complex_packet)); |
||||
|
||||
// Phase 4: Test retransmission requests
|
||||
printf("\n=== Phase 4: Retransmission Testing ===\n"); |
||||
|
||||
uint8_t retrans_request[] = { |
||||
ETCP_SECTION_RETRANS + 0x02, // RETRANS section with base ID 2
|
||||
0x00, 0x04, // Section length (4 bytes)
|
||||
0x00, 0x03, // Request retransmission of packet ID 3
|
||||
0x00, 0x04 // Request retransmission of packet ID 4
|
||||
}; |
||||
// Total: 3 + 4 = 7 bytes
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating retransmission request..."); |
||||
simulate_packet_rx(client_instance, retrans_request, sizeof(retrans_request)); |
||||
|
||||
// Final packet generation
|
||||
printf("\n=== Final Phase: Additional Traffic ===\n"); |
||||
|
||||
// Generate final packets
|
||||
for (int i = 0; i < 5; i++) { |
||||
simulate_packet_tx(server_instance); |
||||
simulate_packet_tx(client_instance); |
||||
usleep(5000); // 0.5ms delay
|
||||
} |
||||
|
||||
// Print summary
|
||||
printf("\n=== Traffic Analysis Summary ===\n"); |
||||
printf("Server instance: processed packets with detailed section analysis\n"); |
||||
printf("Client instance: processed packets with detailed section analysis\n"); |
||||
printf("Connection establishment: INIT handshake simulated\n"); |
||||
printf("Mixed traffic: ACK, TIMESTAMP, PAYLOAD sections processed\n"); |
||||
printf("Retransmission: RETRANS requests processed\n"); |
||||
|
||||
// Cleanup
|
||||
destroy_simple_instance(server_instance); |
||||
destroy_simple_instance(client_instance); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Test completed"); |
||||
printf("\n=== Test completed successfully ===\n"); |
||||
|
||||
return 0; |
||||
} |
||||
Binary file not shown.
@ -1,26 +0,0 @@
|
||||
--- test_etcp_simple_traffic.c.backup
|
||||
+++ test_etcp_simple_traffic.c
|
||||
@@ -241,14 +241,20 @@
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu",
|
||||
strlen(priv_key_hex), strlen(pub_key_hex));
|
||||
|
||||
+ // Ensure strings are properly null-terminated and have correct length
|
||||
+ size_t priv_len = strlen(priv_key_hex);
|
||||
+ size_t pub_len = strlen(pub_key_hex);
|
||||
+ if (priv_len != 64 || pub_len != 128) {
|
||||
+ DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Invalid key lengths: priv=%zu, pub=%zu", priv_len, pub_len);
|
||||
+ }
|
||||
+
|
||||
// Parse keys
|
||||
for (int i = 0; i < 32; i++) {
|
||||
- if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) {
|
||||
+ if (sscanf(&priv_key_hex[i*2], "%2hhx", (unsigned char*)&inst->instance->my_keys.private_key[i]) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 64; i++) {
|
||||
- if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) {
|
||||
+ if (sscanf(&pub_key_hex[i*2], "%2hhx", (unsigned char*)&inst->instance->my_keys.public_key[i]) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue