Browse Source

Refactor uasync to instance-based API with memory leak detection

v2_dev
jek 3 months ago
parent
commit
9eab53179d
  1. 3
      AGENTS.md
  2. 4
      Makefile
  3. 48
      changelog.txt
  4. 36
      connection.c
  5. 6
      connection.h
  6. 40
      etcp.c
  7. 5
      etcp.h
  8. 7
      ll_queue.c
  9. 5
      ll_queue.h
  10. 89
      pkt_normalizer.c
  11. 14
      pkt_normalizer.h
  12. 72
      tests/simple_uasync.c
  13. 21
      tests/test_etcp.c
  14. 14
      tests/test_etcp_simple.c
  15. 33
      tests/test_etcp_stress.c
  16. 53
      tests/test_new_features.c
  17. 17
      tests/test_pkt_normalizer.c
  18. 15
      tests/test_udp_secure.c
  19. 20
      timeout_heap.c
  20. 26
      timeout_heap.h
  21. 1
      todo.txt
  22. 271
      u_async.c
  23. 304
      u_async.c.backup
  24. 27
      u_async.h
  25. 29
      utun.c
  26. 1
      utun_state.h

3
AGENTS.md

@ -1,12 +1,13 @@
# AGENTS.md - uTun Development Guide
This document provides essential information for agentic coding assistants working on the uTun VPN tunnel project.
Совместимость со станым при доработках сохранять не надо. вместо этого надо доработать остальной код чтобы работало
Совместимость со станым при доработках сохранять не надо. Вместо этого надо доработать остальной код чтобы работало. Важно своевременно чистить код от старых хвостов и проверять что ничего не сломалось. Т.е. код плохо нагромождать - надо стремиться к краткости, логичности и убирать то что стало неактуальным и ненужным.
Если не работает - добавляй отладочную информацию чтобы быстрее найти проблемное место и не рушить работающий код.
если отладочная информация будет флудить сделай ее отключаемой или подумай как ограничить ее вывод по возможности сохраняя информативность.
Добавляй в код структуры для статистики (например суммируй число ошибок, вызовов и других потенциально нужных для анализа при ошибках метрик).
добавляй в тест подсчет времени сколько каждфй этап длился для лучшего понимания оптимизации
перед запуском тестов не забывай их пересобирать
веди changelog.txt: дата время: что поменялось

4
Makefile

@ -23,7 +23,7 @@ ETCP_OBJS := etcp.o
CONFIG_PARSER_OBJS := config_parser.o
TUN_IF_OBJS := tun_if.o
all: utun $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork
all: utun $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork
$(TEST_DIR)/test_pkt_normalizer: $(TEST_DIR)/test_pkt_normalizer.o $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
@ -71,7 +71,7 @@ $(TEST_DIR)/test_etcp: $(TEST_DIR)/test_etcp.o $(ETCP_OBJS) $(LL_QUEUE_OBJS) $(U
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
rm -f utun $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork \
rm -f utun $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork \
*.o tinycrypt/lib/source/*.o $(TEST_DIR)/*.o
.PHONY: all clean

48
changelog.txt

@ -0,0 +1,48 @@
Thu Jan 15 2026 05:13: Обновление uasync для поддержки инстансов
- Обновлен модуль u_async для поддержки инстансов (структура uasync_s, функции с суффиксом _instance)
- Сохранена обратная совместимость: глобальные функции используют глобальный инстанс
- Добавлены поля uasync_t* ua в структуры conn_handle, epkt, ll_queue
- Обновлены вызовы uasync_set_timeout и uasync_cancel_timeout на инстансные версии в connection.c, etcp.c, ll_queue.c
- Обновлен mock simple_uasync.c для поддержки нового API
- Исправлена ошибка отсутствия поля stats в conn_handle (добавлено поле stats)
Thu Jan 15 2026 12:24: Настройка NTP и таймзоны GMT+3
- Установлена таймзона Etc/GMT-3
- Включена синхронизация NTP через systemd-timesyncd
- Проверена работа автозапуска NTP сервиса
Thu Jan 15 2026 14:30: Удаление глобального инстанса uasync
- Убраны глобальные функции uasync_init, uasync_get_global_instance
- Переименованы инстансные функции (убраны суффиксы _instance)
- Обновлены ll_queue, connection, etcp, pkt_normalizer для передачи uasync_t*
- Основной код компилируется, тесты требуют доработки
Thu Jan 15 2026 18:45: Завершение рефакторинга uasync на инстансную архитектуру
- Исправлены оставшиеся вызовы uasync_poll в test_utun_integration.c
- Обновлен основной приложение utun.c для использования instance-based API:
* Добавлено поле uasync_t* ua в utun_state_t
* Создание и уничтожение uasync инстанса в main и cleanup
* Передача инстанса в conn_create
* Добавлен вызов uasync_poll в event_loop
- Все тесты компилируются и проходят (кроме интеграционного, требующего root)
- Архитектура глобального инстанса полностью устранена
Thu Jan 15 2026 19:30: Исправление double-free в uasync_destroy
- Добавлена отладочная печать в u_async.c и etcp.c для отслеживания таймеров
- Обнаружена проблема с ленивым удалением таймеров в timeout_heap
- Внесены изменения в uasync_cancel_timeout и uasync_destroy для избежания double-free
- Тест test_new_features все еще падает из-за double-free, требуется дальнейшее исследование
Thu Jan 15 2026 19:45: Полное исправление double-free в uasync
- Изменена логика uasync_cancel_timeout: не освобождает память, только помечает callback как NULL
- Обновлены process_timeouts и uasync_destroy для корректного освобождения памяти
- Убраны отладочные печати из рабочего кода
- Все тесты проходят успешно, включая test_new_features
Thu Jan 15 2026 21:30: Добавление детектора утечек памяти и исправление подсчета освобождений
- Добавлены счетчики аллокаций и освобождений таймеров и сокетов в uasync_t
- Добавлен callback в timeout_heap для обновления счетчиков при освобождении отмененных таймеров
- Добавлена проверка утечек в uasync_destroy с аварийным завершением при обнаружении неосвобожденных ресурсов после очистки
- Исправлен подсчет освобождений: теперь все таймеры учитываются правильно
- Все тесты проходят, утечки не обнаруживаются после очистки

36
connection.c

@ -48,12 +48,14 @@ struct conn_handle {
/* Состояние */
uint8_t is_closing;
uint8_t is_destroying;
conn_stats_t stats; /* Статистика подключения */
/* Таймеры */
void* socket_read_timer;
/* Async instance */
uasync_t* ua;
/* Статистика */
conn_stats_t stats;
};
/* Внутренние функции */
@ -69,28 +71,22 @@ static void app_input_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
static void packer_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
static void etcp_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
/* Глобальная инициализация uasync (вызывается один раз) */
static int uasync_initialized = 0;
/* ==================== Публичные функции ==================== */
conn_handle_t* conn_create(void)
conn_handle_t* conn_create(uasync_t* ua)
{
/* Инициализация uasync глобально */
if (!uasync_initialized) {
uasync_init();
uasync_initialized = 1;
}
/* Выделение памяти */
conn_handle_t* conn = calloc(1, sizeof(conn_handle_t));
if (!conn) {
return NULL;
}
/* Установка инстанса uasync */
conn->ua = ua;
/* Инициализация очередей приложения */
conn->app_input_queue = queue_new();
conn->app_output_queue = queue_new();
conn->app_input_queue = queue_new(ua);
conn->app_output_queue = queue_new(ua);
if (!conn->app_input_queue || !conn->app_output_queue) {
if (conn->app_input_queue) queue_free(conn->app_input_queue);
if (conn->app_output_queue) queue_free(conn->app_output_queue);
@ -201,7 +197,7 @@ int conn_connect(conn_handle_t* conn,
}
/* Инициализация нормализатора пакетов */
conn->normalizer = pkt_normalizer_pair_init();
conn->normalizer = pkt_normalizer_pair_init(conn->ua);
if (!conn->normalizer) {
close(conn->sockfd);
conn->sockfd = -1;
@ -209,7 +205,7 @@ int conn_connect(conn_handle_t* conn,
}
/* Инициализация ETCP */
conn->etcp = etcp_init();
conn->etcp = etcp_init(conn->ua);
if (!conn->etcp) {
pkt_normalizer_pair_deinit(conn->normalizer);
close(conn->sockfd);
@ -249,7 +245,7 @@ int conn_connect(conn_handle_t* conn,
}
/* Запуск таймера чтения сокета */
conn->socket_read_timer = uasync_set_timeout(1, conn, socket_read_callback);
conn->socket_read_timer = uasync_set_timeout(conn->ua, 1, conn, socket_read_callback);
conn->socket_connected = 1;
return 0;
@ -312,7 +308,7 @@ void conn_close(conn_handle_t* conn)
/* Отмена таймеров */
if (conn->socket_read_timer) {
uasync_cancel_timeout(conn->socket_read_timer);
uasync_cancel_timeout(conn->ua, conn->socket_read_timer);
conn->socket_read_timer = NULL;
}
@ -632,7 +628,7 @@ static void socket_read_callback(void* arg)
/* Перезапуск таймера чтения */
if (!conn->is_closing) {
conn->socket_read_timer = uasync_set_timeout(1, conn, socket_read_callback);
conn->socket_read_timer = uasync_set_timeout(conn->ua, 1, conn, socket_read_callback);
}
}

6
connection.h

@ -12,6 +12,9 @@ extern "C" {
/* Непрозрачный дескриптор подключения */
typedef struct conn_handle conn_handle_t;
/* Forward declaration для uasync */
typedef struct uasync_s uasync_t;
/* Режим подключения */
typedef enum {
CONN_MODE_CLIENT, /* Инициируем подключение к указанному удаленному адресу */
@ -26,9 +29,10 @@ typedef void (*conn_recv_callback_t)(conn_handle_t* conn,
/*
* Создание дескриптора подключения (только выделение памяти).
* ua - экземпляр uasync для таймеров (обязательный параметр)
* Возвращает NULL при ошибке.
*/
conn_handle_t* conn_create(void);
conn_handle_t* conn_create(uasync_t* ua);
/*
* Установка криптографических ключей.

40
etcp.c

@ -57,13 +57,15 @@ static void etcp_send_reset_ack(epkt_t* epkt);
static void reset_timer_callback(void* arg);
// Initialize new ETCP instance
epkt_t* etcp_init(void) {
epkt_t* etcp_init(uasync_t* ua) {
epkt_t* epkt = calloc(1, sizeof(epkt_t));
if (!epkt) return NULL;
epkt->ua = ua;
// Create queues
epkt->tx_queue = queue_new();
epkt->output_queue = queue_new();
epkt->tx_queue = queue_new(ua);
epkt->output_queue = queue_new(ua);
if (!epkt->tx_queue || !epkt->output_queue) {
if (epkt->tx_queue) queue_free(epkt->tx_queue);
if (epkt->output_queue) queue_free(epkt->output_queue);
@ -138,10 +140,16 @@ void etcp_free(epkt_t* epkt) {
// Cancel timers
if (epkt->next_tx_timer) {
uasync_cancel_timeout(epkt->next_tx_timer);
uasync_cancel_timeout(epkt->ua, epkt->next_tx_timer);
epkt->next_tx_timer = NULL;
}
if (epkt->retransmit_timer) {
uasync_cancel_timeout(epkt->retransmit_timer);
uasync_cancel_timeout(epkt->ua, epkt->retransmit_timer);
epkt->retransmit_timer = NULL;
}
if (epkt->reset_timer) {
uasync_cancel_timeout(epkt->ua, epkt->reset_timer);
epkt->reset_timer = NULL;
}
// Free queues
@ -223,11 +231,11 @@ void etcp_reset(epkt_t* epkt) {
// Cancel timers
if (epkt->next_tx_timer) {
uasync_cancel_timeout(epkt->next_tx_timer);
uasync_cancel_timeout(epkt->ua, epkt->next_tx_timer);
epkt->next_tx_timer = NULL;
}
if (epkt->retransmit_timer) {
uasync_cancel_timeout(epkt->retransmit_timer);
uasync_cancel_timeout(epkt->ua, epkt->retransmit_timer);
epkt->retransmit_timer = NULL;
}
@ -456,7 +464,7 @@ static void tx_process(epkt_t* epkt) {
if (epkt->bytes_allowed == 0) {
// Schedule next attempt
if (!epkt->next_tx_timer) {
epkt->next_tx_timer = uasync_set_timeout(1, epkt, tx_timer_callback);
epkt->next_tx_timer = uasync_set_timeout(epkt->ua, 1, epkt, tx_timer_callback);
}
return;
}
@ -488,7 +496,7 @@ static void tx_process(epkt_t* epkt) {
queue_entry_put_first(epkt->tx_queue, entry);
// Schedule check when window might open (after retransmission timer)
if (!epkt->next_tx_timer) {
epkt->next_tx_timer = uasync_set_timeout(epkt->retrans_timer_period, epkt, tx_timer_callback);
epkt->next_tx_timer = uasync_set_timeout(epkt->ua, epkt->retrans_timer_period, epkt, tx_timer_callback);
}
return;
}
@ -521,7 +529,7 @@ static void tx_process(epkt_t* epkt) {
if (!epkt->next_tx_timer) {
uint16_t wait_time = (packet_size - epkt->bytes_allowed) / epkt->bandwidth;
if (wait_time < 1) wait_time = 1;
epkt->next_tx_timer = uasync_set_timeout(wait_time, epkt, tx_timer_callback);
epkt->next_tx_timer = uasync_set_timeout(epkt->ua, wait_time, epkt, tx_timer_callback);
}
return;
}
@ -657,7 +665,7 @@ static void tx_process(epkt_t* epkt) {
// Schedule retransmit check if not already scheduled
if (!epkt->retransmit_timer && epkt->retrans_timer_period > 0) {
epkt->retransmit_timer = uasync_set_timeout(epkt->retrans_timer_period, epkt, retransmit_timer_callback);
epkt->retransmit_timer = uasync_set_timeout(epkt->ua, epkt->retrans_timer_period, epkt, retransmit_timer_callback);
}
// Resume queue callback for next packet
@ -713,7 +721,7 @@ static void retransmit_check(epkt_t* epkt) {
// Reschedule check with updated period
if (epkt->retrans_timer_period > 0) {
epkt->retransmit_timer = uasync_set_timeout(epkt->retrans_timer_period, epkt, retransmit_timer_callback);
epkt->retransmit_timer = uasync_set_timeout(epkt->ua, epkt->retrans_timer_period, epkt, retransmit_timer_callback);
} else {
epkt->retransmit_timer = NULL;
}
@ -890,7 +898,7 @@ int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len) {
epkt->reset_ack_received = 1;
epkt->reset_pending = 0;
if (epkt->reset_timer) {
uasync_cancel_timeout(epkt->reset_timer);
uasync_cancel_timeout(epkt->ua, epkt->reset_timer);
epkt->reset_timer = NULL;
}
}
@ -1162,7 +1170,7 @@ static void reset_timer_callback(void* arg) {
etcp_send_reset(epkt);
// Schedule next retry in 100ms (1000 timebase units)
epkt->reset_timer = uasync_set_timeout(1000, epkt, reset_timer_callback);
epkt->reset_timer = uasync_set_timeout(epkt->ua, 1000, epkt, reset_timer_callback);
}
static void etcp_send_reset(epkt_t* epkt) {
@ -1208,7 +1216,7 @@ void etcp_reset_connection(epkt_t* epkt) {
// Cancel any existing reset timer
if (epkt->reset_timer) {
uasync_cancel_timeout(epkt->reset_timer);
uasync_cancel_timeout(epkt->ua, epkt->reset_timer);
epkt->reset_timer = NULL;
}
@ -1220,7 +1228,7 @@ void etcp_reset_connection(epkt_t* epkt) {
etcp_send_reset(epkt);
// Start retry timer (100ms = 1000 timebase units)
epkt->reset_timer = uasync_set_timeout(1000, epkt, reset_timer_callback);
epkt->reset_timer = uasync_set_timeout(epkt->ua, 1000, epkt, reset_timer_callback);
}
// ==================== Queue Callbacks ====================

5
etcp.h

@ -5,6 +5,7 @@
#include <stdint.h>
#include <stddef.h>
#include "ll_queue.h"
#include "u_async.h"
// Отладочное логирование
#ifdef ETCP_DEBUG
@ -69,6 +70,7 @@ struct epkt {
// Таймеры
void* next_tx_timer; // Таймер для следующей передачи
void* retransmit_timer; // Таймер для повторных передач
uasync_t* ua; // Экземпляр uasync для таймеров
// Обратный вызов
etcp_tx_callback_t tx_callback;
@ -112,9 +114,10 @@ struct epkt {
/**
* @brief Инициализировать новый экземпляр ETCP
* @param ua Экземпляр uasync для таймеров (обязательный параметр)
* @return Указатель на новый экземпляр или NULL в случае ошибки
*/
epkt_t* etcp_init(void);
epkt_t* etcp_init(uasync_t* ua);
/**
* @brief Освободить экземпляр ETCP и все связанные ресурсы

7
ll_queue.c

@ -36,7 +36,7 @@ static void check_waiters(ll_queue_t* q) {
// ==================== Управление очередью ====================
ll_queue_t* queue_new(void) {
ll_queue_t* queue_new(uasync_t* ua) {
ll_queue_t* q = calloc(1, sizeof(ll_queue_t));
if (!q) return NULL;
@ -49,6 +49,7 @@ ll_queue_t* queue_new(void) {
q->callback_arg = NULL;
q->callback_suspended = 0; // Коллбэки разрешены изначально
q->resume_timeout_id = NULL;
q->ua = ua;
q->waiters = NULL;
return q;
@ -75,7 +76,7 @@ void queue_free(ll_queue_t* q) {
// Отменить отложенное возобновление если запланировано
if (q->resume_timeout_id) {
uasync_cancel_timeout(q->resume_timeout_id);
uasync_cancel_timeout(q->ua, q->resume_timeout_id);
}
free(q);
@ -115,7 +116,7 @@ void queue_resume_callback(ll_queue_t* q) {
}
// Запланировать отложенное возобновление через uasync
q->resume_timeout_id = uasync_set_timeout(0, q, queue_resume_timeout_cb);
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb);
}
void queue_set_size_limit(ll_queue_t* q, int lim) {

5
ll_queue.h

@ -5,6 +5,7 @@
// Предварительные объявления
typedef struct ll_queue ll_queue_t;
typedef struct uasync_s uasync_t;
typedef struct ll_entry ll_entry_t;
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
@ -42,6 +43,7 @@ struct ll_queue {
int callback_suspended; // 1 если коллбэки приостановлены (во время обработки)
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
uasync_t* ua; // Экземпляр uasync для таймеров
queue_waiter_t* waiters; // Список ожидающих коллбэков
};
@ -49,8 +51,9 @@ struct ll_queue {
// ==================== Управление очередью ====================
// Создать новую пустую очередь
// ua - экземпляр uasync для таймеров (обязательный параметр)
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти
ll_queue_t* queue_new(void);
ll_queue_t* queue_new(uasync_t* ua);
// Освободить очередь и все её элементы
// Также отменяет отложенное возобновление если оно запланировано

89
pkt_normalizer.c

@ -13,32 +13,33 @@ static int get_header(uint8_t* header, size_t L);
/* Calculate maximum regular block size that fits in max_fragment_size */
pn_struct* pkt_normalizer_init(int is_packer) {
pn_struct* pn = malloc(sizeof(pn_struct));
if (!pn) return NULL;
pn->input = queue_new();
if (!pn->input) {
free(pn);
return NULL;
}
pn->output = queue_new();
if (!pn->output) {
queue_free(pn->input);
free(pn);
return NULL;
}
pn->is_packer = is_packer;
if (is_packer) {
pn->u.packer.cap = settings.max_fragment_size;
pn->u.packer.buf = malloc(pn->u.packer.cap);
if (!pn->u.packer.buf) {
queue_free(pn->input);
queue_free(pn->output);
free(pn);
return NULL;
}
pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer) {
pn_struct* pn = malloc(sizeof(pn_struct));
if (!pn) return NULL;
pn->ua = ua;
pn->input = queue_new(ua);
if (!pn->input) {
free(pn);
return NULL;
}
pn->output = queue_new(ua);
if (!pn->output) {
queue_free(pn->input);
free(pn);
return NULL;
}
pn->is_packer = is_packer;
if (is_packer) {
pn->u.packer.cap = settings.max_fragment_size;
pn->u.packer.buf = malloc(pn->u.packer.cap);
if (!pn->u.packer.buf) {
queue_free(pn->input);
queue_free(pn->output);
free(pn);
return NULL;
}
pn->u.packer.len = 0;
pn->u.packer.error_count = 0;
queue_set_callback(pn->input, packer_handler, pn);
@ -56,8 +57,8 @@ pn_struct* pkt_normalizer_init(int is_packer) {
pn->u.unpacker.in_service = 0;
queue_set_callback(pn->input, unpacker_handler, pn);
}
return pn;
}
return pn;
}
void pkt_normalizer_deinit(pn_struct* pn) {
if (!pn) return;
@ -72,22 +73,22 @@ void pkt_normalizer_deinit(pn_struct* pn) {
free(pn);
}
pkt_normalizer_pair* pkt_normalizer_pair_init(void) {
pkt_normalizer_pair* pair = malloc(sizeof(pkt_normalizer_pair));
if (!pair) return NULL;
pair->packer = pkt_normalizer_init(1);
if (!pair->packer) {
free(pair);
return NULL;
}
pair->unpacker = pkt_normalizer_init(0);
if (!pair->unpacker) {
pkt_normalizer_deinit(pair->packer);
free(pair);
return NULL;
}
return pair;
pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua) {
pkt_normalizer_pair* pair = malloc(sizeof(pkt_normalizer_pair));
if (!pair) return NULL;
pair->packer = pkt_normalizer_init(ua, 1);
if (!pair->packer) {
free(pair);
return NULL;
}
pair->unpacker = pkt_normalizer_init(ua, 0);
if (!pair->unpacker) {
pkt_normalizer_deinit(pair->packer);
free(pair);
return NULL;
}
return pair;
}
void pkt_normalizer_pair_deinit(pkt_normalizer_pair* pair) {

14
pkt_normalizer.h

@ -3,6 +3,7 @@
#define PKT_NORMALIZER_H
#include "ll_queue.h"
#include "u_async.h"
#include <stdint.h>
/* Default fragment reassembly timeout in uasync timebase units (0.1 ms) */
@ -16,10 +17,11 @@ typedef struct pkt_normalizer_pair pkt_normalizer_pair;
/* Service packet callback type */
typedef void (*pkt_normalizer_service_callback_t)(void* user_data, uint8_t type, const uint8_t* data, size_t len);
struct pn_struct {
ll_queue_t* input;
ll_queue_t* output;
int is_packer;
struct pn_struct {
ll_queue_t* input;
ll_queue_t* output;
uasync_t* ua;
int is_packer;
union {
struct {
uint8_t* buf;
@ -47,10 +49,10 @@ struct pn_struct {
void* service_callback_user_data;
};
pn_struct* pkt_normalizer_init(int is_packer); // 1 for packer, 0 for unpacker
pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer); // 1 for packer, 0 for unpacker
void pkt_normalizer_deinit(pn_struct* pn);
pkt_normalizer_pair* pkt_normalizer_pair_init(void);
pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua);
void pkt_normalizer_pair_deinit(pkt_normalizer_pair* pair);
/* Error handling */

72
tests/simple_uasync.c

@ -1,5 +1,5 @@
// simple_uasync.c - Minimal uasync implementation for tests
#include "u_async.h"
#include "../u_async.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
@ -24,6 +24,12 @@ typedef struct socket_entry {
struct socket_entry* next;
} socket_entry_t;
// Dummy uasync instance structure
struct uasync_s {
int dummy;
};
static uasync_t g_ua = {0};
// Global state
static timer_entry_t* timer_list = NULL;
static socket_entry_t* socket_list = NULL;
@ -42,14 +48,37 @@ void uasync_init(void) {
current_time = 0;
}
// Mainloop - not used in tests
void uasync_mainloop(void) {
// Should not be called in tests
while (1) {}
// Instance API
uasync_t* uasync_create(void) {
uasync_t* ua = malloc(sizeof(uasync_t));
if (ua) {
ua->dummy = 0;
}
return ua;
}
void uasync_destroy(uasync_t* ua) {
free(ua);
}
// Set timeout
void* uasync_set_timeout(int timeout_tb, void* user_arg, timeout_callback_t callback) {
void uasync_init_instance(uasync_t* ua) {
(void)ua;
}
void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* user_arg, timeout_callback_t callback) {
(void)ua;
timer_entry_t* timer = malloc(sizeof(timer_entry_t));
if (!timer) return NULL;
@ -63,8 +92,8 @@ void* uasync_set_timeout(int timeout_tb, void* user_arg, timeout_callback_t call
return timer->id;
}
// Cancel timeout
err_t uasync_cancel_timeout(void* t_id) {
err_t uasync_cancel_timeout(uasync_t* ua, void* t_id) {
(void)ua;
timer_entry_t** pp = &timer_list;
while (*pp) {
if ((*pp)->id == t_id) {
@ -78,21 +107,34 @@ err_t uasync_cancel_timeout(void* t_id) {
return ERR_FAIL;
}
// Add socket (not implemented)
void* uasync_add_socket(int fd, socket_callback_t read_cbk,
void* uasync_add_socket(uasync_t* ua, int fd, socket_callback_t read_cbk,
socket_callback_t write_cbk,
socket_callback_t except_cbk,
void* user_arg) {
(void)fd; (void)read_cbk; (void)write_cbk; (void)except_cbk; (void)user_arg;
(void)ua; (void)fd; (void)read_cbk; (void)write_cbk; (void)except_cbk; (void)user_arg;
return NULL;
}
// Remove socket (not implemented)
err_t uasync_remove_socket(void* s_id) {
(void)s_id;
err_t uasync_remove_socket(uasync_t* ua, void* s_id) {
(void)ua; (void)s_id;
return ERR_FAIL;
}
void uasync_poll(uasync_t* ua, int timeout_tb) {
(void)ua;
(void)timeout_tb;
}
void uasync_mainloop(uasync_t* ua) {
(void)ua;
// Should not be called in tests
while (1) {}
}
uasync_t* uasync_get_global_instance(void) {
return &g_ua;
}
// Test helper: advance time and process expired timers
void simple_uasync_advance_time(uint32_t delta_tb) {
current_time += delta_tb;

21
tests/test_etcp.c

@ -28,6 +28,7 @@ typedef struct {
#define MAX_MOCK_PACKETS 100
static mock_packet_t mock_packets[MAX_MOCK_PACKETS];
static int mock_packet_count = 0;
static uasync_t* test_ua = NULL;
static void reset_mock_packets(void) {
for (int i = 0; i < mock_packet_count; i++) {
@ -54,7 +55,7 @@ static void mock_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* ar
int test_init_free(void) {
printf("\n=== Test 1: Initialization and cleanup ===\n");
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init returns non-NULL");
TEST_ASSERT(epkt->tx_queue != NULL, "tx_queue created");
TEST_ASSERT(epkt->output_queue != NULL, "output_queue created");
@ -69,7 +70,7 @@ int test_init_free(void) {
int test_set_callback(void) {
printf("\n=== Test 2: Set callback ===\n");
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init");
etcp_set_callback(epkt, mock_tx_callback, NULL);
@ -83,7 +84,7 @@ int test_set_callback(void) {
int test_tx_put(void) {
printf("\n=== Test 3: TX queue put ===\n");
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init");
uint8_t test_data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
@ -104,7 +105,7 @@ int test_simple_tx(void) {
reset_mock_packets();
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init");
// Set high bandwidth to avoid limiting
@ -130,7 +131,7 @@ int test_simple_tx(void) {
int test_rx_input(void) {
printf("\n=== Test 5: RX input parsing ===\n");
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init");
// Create a simple packet: id=1, timestamp=100, hdr=0, payload "test"
@ -171,7 +172,7 @@ int test_rx_input(void) {
int test_reordering(void) {
printf("\n=== Test 6: Packet reordering ===\n");
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init");
// Create packets with IDs 1, 2, 3
@ -230,7 +231,7 @@ int test_reordering(void) {
int test_metrics(void) {
printf("\n=== Test 7: Metrics ===\n");
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp_init");
// Initial metrics should be zero
@ -249,7 +250,9 @@ int main(void) {
printf("Starting ETCP tests...\n");
// Initialize uasync for timers
uasync_init();
uasync_t* test_ua = uasync_create();
TEST_ASSERT(test_ua != NULL, "create uasync instance");
uasync_init_instance(test_ua);
int failures = 0;
@ -270,5 +273,7 @@ int main(void) {
reset_mock_packets();
uasync_destroy(test_ua);
return failures == 0 ? 0 : 1;
}

14
tests/test_etcp_simple.c

@ -31,12 +31,17 @@ static void receiver_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void
int main(void) {
printf("Simple ETCP sender-receiver test\n");
// Initialize uasync (mock)
uasync_init();
// Initialize uasync instance
uasync_t* ua = uasync_create();
if (!ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(ua);
// Create ETCP instances
epkt_t* sender = etcp_init();
epkt_t* receiver = etcp_init();
epkt_t* sender = etcp_init(ua);
epkt_t* receiver = etcp_init(ua);
if (!sender || !receiver) {
printf("ERROR: Failed to create ETCP instances\n");
@ -112,6 +117,7 @@ int main(void) {
queue_entry_free(entry);
etcp_free(sender);
etcp_free(receiver);
uasync_destroy(ua);
return 0;
}

33
tests/test_etcp_stress.c

@ -43,7 +43,6 @@ typedef struct {
} network_emulator_t;
// Forward declarations
static void network_timer_callback(void* arg);
static void sender_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* arg);
static void receiver_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* arg);
static void deliver_packets(network_emulator_t* net);
@ -60,22 +59,7 @@ static double random_double(void) {
return (double)random_next() / (double)UINT32_MAX;
}
// Network timer callback - advances time and delivers packets
static void network_timer_callback(void* arg) {
network_emulator_t* net = (network_emulator_t*)arg;
if (!net || !net->running) return;
// Advance time by 1ms (10 timebase units)
net->current_time += 10;
// Deliver any packets whose time has come
deliver_packets(net);
// Reschedule timer if still running
if (net->running) {
net->timer_id = uasync_set_timeout(10, net, network_timer_callback);
}
}
// Sender's TX callback - called when ETCP wants to send a packet
static void sender_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* arg) {
@ -237,8 +221,13 @@ int main(void) {
// Seed random number generator
random_state = (uint32_t)time(NULL);
// Initialize uasync
uasync_init();
// Initialize uasync instance
uasync_t* ua = uasync_create();
if (!ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(ua);
// Create network emulator
network_emulator_t net = {0};
@ -246,8 +235,8 @@ int main(void) {
net.current_time = 0;
// Create ETCP instances
net.sender = etcp_init();
net.receiver = etcp_init();
net.sender = etcp_init(ua);
net.receiver = etcp_init(ua);
if (!net.sender || !net.receiver) {
printf("ERROR: Failed to create ETCP instances\n");
@ -263,7 +252,6 @@ int main(void) {
etcp_set_bandwidth(net.receiver, 50000);
// Don't start network timer - we'll advance time manually
// net.timer_id = uasync_set_timeout(10, &net, network_timer_callback);
printf("\nGenerating and sending %d packets...\n", NUM_PACKETS);
@ -410,6 +398,7 @@ int main(void) {
// Cleanup
etcp_free(net.sender);
etcp_free(net.receiver);
uasync_destroy(ua);
printf("\nStress test completed.\n");

53
tests/test_new_features.c

@ -33,6 +33,7 @@ static uint8_t last_service_type = 0;
static uint8_t* last_service_data = NULL;
static size_t last_service_len = 0;
static int fragments_forwarded = 0;
static uasync_t* test_ua = NULL;
// Callback для сервисных пакетов
static void service_packet_callback(void* user_data, uint8_t type, const uint8_t* data, size_t len) {
@ -157,6 +158,7 @@ static void process_queues_pair(pkt_normalizer_pair* pair) {
int test_fragmentation_fix(void) {
printf("\n=== Test 1: Fragmentation Fix (Last Fragment Split) ===\n");
printf("DEBUG: test_fragmentation_fix start\n");
// Сохраняем оригинальный размер фрагмента
int original_fragment_size = settings.max_fragment_size;
@ -164,7 +166,7 @@ int test_fragmentation_fix(void) {
// Устанавливаем маленький размер фрагмента для тестирования edge case
settings.max_fragment_size = 200; // 200 байт
pkt_normalizer_pair* pair = pkt_normalizer_pair_init();
pkt_normalizer_pair* pair = pkt_normalizer_pair_init(test_ua);
TEST_ASSERT(pair != NULL, "pair initialization");
// Set up forwarding: packer output -> unpacker input
@ -243,6 +245,7 @@ int test_fragmentation_fix(void) {
free(test_data);
pkt_normalizer_pair_deinit(pair);
printf("DEBUG: test_fragmentation_fix end\n");
return 0;
}
@ -250,11 +253,12 @@ int test_fragmentation_fix(void) {
int test_service_packets(void) {
printf("\n=== Test 2: Service Packets (0xFC/0xFD) ===\n");
printf("DEBUG: test_service_packets start\n");
reset_test_state();
// Используем пару для связи packer и unpacker
pkt_normalizer_pair* pair = pkt_normalizer_pair_init();
pkt_normalizer_pair* pair = pkt_normalizer_pair_init(test_ua);
TEST_ASSERT(pair != NULL, "pair initialization");
// Set up forwarding: packer output -> unpacker input
@ -339,6 +343,7 @@ int test_service_packets(void) {
pkt_normalizer_pair_deinit(pair);
printf("DEBUG: test_service_packets end\n");
return 0;
}
@ -363,18 +368,23 @@ static int uasync_initialized = 0;
int test_etcp_reset(void) {
printf("\n=== Test 3: ETCP Reset Connection (0x02/0x03) ===\n");
printf("DEBUG: test_etcp_reset start\n");
// Инициализируем uasync для таймеров
if (!uasync_initialized) {
uasync_init();
uasync_initialized = 1;
if (!test_ua) {
test_ua = uasync_create();
if (!test_ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(test_ua);
}
epkt_t* epkt = etcp_init();
epkt_t* epkt = etcp_init(test_ua);
TEST_ASSERT(epkt != NULL, "etcp initialization");
// Создаем очередь для приема отправленных пакетов
ll_queue_t* tx_queue = queue_new();
ll_queue_t* tx_queue = queue_new(test_ua);
TEST_ASSERT(tx_queue != NULL, "create tx queue");
// Устанавливаем callback для отправки
@ -453,6 +463,7 @@ int test_etcp_reset(void) {
queue_free(tx_queue);
etcp_free(epkt);
printf("DEBUG: test_etcp_reset end\n");
return 0;
}
@ -460,11 +471,12 @@ int test_etcp_reset(void) {
int test_conn_reset(void) {
printf("\n=== Test 4: conn_reset() Full Chain ===\n");
printf("DEBUG: test_conn_reset start\n");
// Этот тест требует больше интеграции
// Для простоты проверим, что функция существует и может быть вызвана
conn_handle_t* conn = conn_create();
conn_handle_t* conn = conn_create(test_ua);
TEST_ASSERT(conn != NULL, "connection creation");
// Вызываем conn_reset (должен работать даже без установленного соединения)
@ -474,19 +486,25 @@ int test_conn_reset(void) {
// Очистка
conn_destroy(conn);
printf("DEBUG: test_conn_reset end\n");
return 0;
}
// ==================== Основная функция ====================
int main(void) {
setvbuf(stdout, NULL, _IONBF, 0); // disable buffering
printf("=== Testing New Features ===\n");
int result = 0;
// Инициализируем uasync глобально
uasync_init();
uasync_initialized = 1;
test_ua = uasync_create();
if (!test_ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(test_ua);
// Запускаем тесты
result |= test_fragmentation_fix();
@ -505,6 +523,21 @@ int main(void) {
free(last_service_data);
last_service_data = NULL;
}
if (test_ua) {
// Проверка статистики памяти
size_t timer_alloc, timer_free, socket_alloc, socket_free;
uasync_get_stats(test_ua, &timer_alloc, &timer_free, &socket_alloc, &socket_free);
printf("Uasync stats: timers alloc=%zu free=%zu, sockets alloc=%zu free=%zu\n",
timer_alloc, timer_free, socket_alloc, socket_free);
if (timer_alloc != timer_free || socket_alloc != socket_free) {
printf("WARNING: Memory leaks detected!\n");
}
printf("DEBUG: calling uasync_destroy(%p)\n", test_ua);
uasync_destroy(test_ua);
test_ua = NULL;
printf("DEBUG: uasync_destroy completed\n");
}
return result;
}

17
tests/test_pkt_normalizer.c

@ -1,6 +1,7 @@
#include "u_async.h"
#include "ll_queue.h"
#include "pkt_normalizer.h"
#include "u_async.h"
#include "settings.h"
#include <stdio.h>
#include <string.h>
@ -30,6 +31,7 @@ static test_packet_t sent_packets[MAX_TEST_PACKETS];
static test_packet_t received_packets[MAX_TEST_PACKETS];
static int sent_count = 0;
static int received_count = 0;
static uasync_t* test_ua = NULL;
static void reset_test_data(void) {
for (int i = 0; i < sent_count; i++) {
@ -691,7 +693,7 @@ static void test_callback(ll_queue_t* q_arg, void* arg) {
static int test_async_wait(void) {
printf("\n--- Test async wait: threshold waiter ---\n");
ll_queue_t* q = queue_new();
ll_queue_t* q = queue_new(test_ua);
TEST_ASSERT(q != NULL, "create queue for async wait test");
int callback_called = 0;
@ -726,13 +728,13 @@ static int test_async_wait(void) {
static int test_lifecycle(void) {
printf("\n--- Test 5: lifecycle ---\n");
pkt_normalizer_pair* pair = pkt_normalizer_pair_init();
pkt_normalizer_pair* pair = pkt_normalizer_pair_init(test_ua);
TEST_ASSERT(pair != NULL, "pair initialization");
TEST_ASSERT(pair->packer != NULL, "packer initialization");
TEST_ASSERT(pair->unpacker != NULL, "unpacker initialization");
/* Создать тестовые очереди */
ll_queue_t* test_queue = queue_new();
ll_queue_t* test_queue = queue_new(test_ua);
TEST_ASSERT(test_queue != NULL, "test queue creation");
/* Отправить один пакет */
@ -757,9 +759,14 @@ int main(void) {
printf("=== Packet Normalizer Unit Test ===\n");
srand((unsigned int)time(NULL));
uasync_init();
test_ua = uasync_create();
if (!test_ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(test_ua);
pkt_normalizer_pair* pair = pkt_normalizer_pair_init();
pkt_normalizer_pair* pair = pkt_normalizer_pair_init(test_ua);
if (!pair) {
fprintf(stderr, "Failed to initialize packet normalizer pair\n");
return 1;

15
tests/test_udp_secure.c

@ -158,7 +158,12 @@ int main(void) {
uECC_set_rng(&default_CSPRNG);
// Initialize async library
uasync_init();
uasync_t* ua = uasync_create();
if (!ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(ua);
// Create UDP sockets
client_a.sockfd = create_udp_socket(PORT_A, &client_a.addr);
@ -184,18 +189,18 @@ int main(void) {
"session keys match");
// Register sockets with async library
uasync_add_socket(client_a.sockfd, client_a_read_callback, NULL, NULL, &client_a);
uasync_add_socket(client_b.sockfd, client_b_read_callback, NULL, NULL, &client_b);
uasync_add_socket(ua, client_a.sockfd, client_a_read_callback, NULL, NULL, &client_a);
uasync_add_socket(ua, client_b.sockfd, client_b_read_callback, NULL, NULL, &client_b);
// Set timeout for test completion (2 seconds)
uasync_set_timeout(20000, NULL, timeout_callback); // timebase 0.1 ms, 20000 = 2 sec
uasync_set_timeout(ua, 20000, NULL, timeout_callback); // timebase 0.1 ms, 20000 = 2 sec
// Send encrypted message from A to B
send_encrypted_message(&client_a, &client_b);
// Run async mainloop (will exit via callback or timeout)
printf("Starting async mainloop...\n");
uasync_mainloop(); // This will not return
uasync_mainloop(ua); // This will not return
// Should not reach here
return 1;

20
timeout_heap.c

@ -1,4 +1,4 @@
// timeout_heap.c
// timeout_heap.c
#include "timeout_heap.h"
#include <stdlib.h>
@ -19,6 +19,9 @@ TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
}
h->size = 0;
h->capacity = initial_capacity;
h->freed_count = 0;
h->user_data = NULL;
h->free_callback = NULL;
return h;
}
@ -112,9 +115,16 @@ int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
if (h->size == 0) return -1;
// Skip deleted
// Skip deleted and free their data
while (h->size > 0 && h->heap[0].deleted) {
void *data_to_free = h->heap[0].data;
remove_root(h);
if (h->free_callback) {
h->free_callback(h->user_data, data_to_free);
} else {
free(data_to_free);
}
h->freed_count++;
}
if (h->size == 0) return -1;
@ -131,4 +141,10 @@ int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
}
}
return -1; // Not found
}
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
if (!h) return;
h->user_data = user_data;
h->free_callback = callback;
}

26
timeout_heap.h

@ -1,4 +1,4 @@
// timeout_heap.h
// timeout_heap.h
#ifndef TIMEOUT_HEAP_H
#define TIMEOUT_HEAP_H
@ -14,11 +14,16 @@ typedef struct {
int deleted; // 0 = active, 1 = deleted
} TimeoutEntry;
typedef struct {
typedef struct TimeoutHeap TimeoutHeap;
struct TimeoutHeap {
TimeoutEntry *heap; // Dynamic array
size_t size; // Current number of elements
size_t capacity; // Allocated size
} TimeoutHeap;
size_t freed_count; // Number of freed timer nodes
void* user_data; // User data for free callback
void (*free_callback)(void* user_data, void* data); // Callback to free data
};
/**
* Create a new timeout heap with initial capacity.
@ -33,6 +38,14 @@ TimeoutHeap *timeout_heap_create(size_t initial_capacity);
*/
void timeout_heap_destroy(TimeoutHeap *h);
/**
* Set a callback function to free data when deleted nodes are removed.
* @param h The heap.
* @param user_data User data passed to callback.
* @param callback Callback function (if NULL, data is freed with free()).
*/
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data));
/**
* Insert a new timeout into the heap.
* @param h The heap.
@ -69,4 +82,11 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
*/
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
/**
* Get the number of freed timer nodes.
* @param h The heap.
* @return Count of freed timer nodes.
*/
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
#endif // TIMEOUT_HEAP_H

1
todo.txt

@ -0,0 +1 @@


271
u_async.c

@ -17,6 +17,7 @@ struct timeout_node {
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
uasync_t* ua; // Pointer back to uasync instance for counter updates
};
// Socket node
@ -29,18 +30,32 @@ struct socket_node {
struct socket_node* next;
};
// Global state
static TimeoutHeap* timeout_heap = NULL; // Heap for timeout management
static struct socket_node* socket_head = NULL;
static int max_fd = -1;
// Uasync instance structure
struct uasync_s {
TimeoutHeap* timeout_heap; // Heap for timeout management
struct socket_node* socket_head;
int max_fd;
fd_set master_readfds;
fd_set master_writefds;
fd_set master_exceptfds;
struct socket_node* fd_to_node[FD_SETSIZE];
// 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;
};
// New: Persistent master fd_sets, updated only on add/remove
static fd_set master_readfds;
static fd_set master_writefds;
static fd_set master_exceptfds;
// No global instance - each module must use its own uasync_t instance
// New: FD-to-node map for faster post-select lookup (addresses point 2)
static struct socket_node* fd_to_node[FD_SETSIZE];
// Callback to free timeout node and update counters
static void timeout_node_free_callback(void* user_data, void* data) {
uasync_t* ua = (uasync_t*)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) {
@ -64,8 +79,8 @@ static uint64_t timeval_to_ms(const struct timeval* tv) {
// Process expired timeouts
static void process_timeouts() {
if (!timeout_heap) return;
static void process_timeouts(struct uasync_s* ua) {
if (!ua || !ua->timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
@ -73,29 +88,30 @@ static void process_timeouts() {
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(timeout_heap, &entry) != 0) break;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(timeout_heap, &entry);
timeout_heap_pop(ua->timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) {
node->callback(node->arg);
}
ua->timer_free_count++;
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct timeval* tv) {
if (!timeout_heap) {
static void get_next_timeout(struct uasync_s* ua, struct timeval* tv) {
if (!ua || !ua->timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
TimeoutEntry entry;
if (timeout_heap_peek(timeout_heap, &entry) != 0) {
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
@ -119,26 +135,20 @@ static void get_next_timeout(struct timeval* tv) {
tv->tv_usec = (delta_ms % 1000) * 1000;
}
void uasync_init(void) {
FD_ZERO(&master_readfds);
FD_ZERO(&master_writefds);
FD_ZERO(&master_exceptfds);
memset(fd_to_node, 0, sizeof(fd_to_node)); // Init map to NULL
if (!timeout_heap) {
timeout_heap = timeout_heap_create(16); // initial capacity 16
}
}
void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback) {
if (timeout_tb < 0 || !callback) return NULL;
if (!timeout_heap) return NULL;
// Instance version
void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) return NULL;
ua->timer_alloc_count++;
node->arg = arg;
node->callback = callback;
node->ua = ua;
// Calculate expiration time in milliseconds
struct timeval now;
@ -147,7 +157,8 @@ void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback)
node->expiration_ms = timeval_to_ms(&now);
// Insert into heap
if (timeout_heap_push(timeout_heap, node->expiration_ms, node) != 0) {
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
ua->timer_free_count++;
free(node);
return NULL;
}
@ -155,54 +166,65 @@ void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback)
return node;
}
err_t uasync_cancel_timeout(void* t_id) {
if (!t_id || !timeout_heap) return ERR_FAIL;
// Instance version
err_t uasync_cancel_timeout(uasync_t* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) return ERR_FAIL;
struct timeout_node* node = (struct timeout_node*)t_id;
// Try to cancel from heap
if (timeout_heap_cancel(timeout_heap, node->expiration_ms, node) == 0) {
free(node);
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Mark as cancelled by clearing callback - memory will be freed later
node->callback = NULL;
return ERR_OK;
}
// If not found in heap (maybe already expired and removed), still free
free(node);
// If not found in heap (maybe already expired and removed), do NOT free
// because node was already freed in process_timeouts
return ERR_FAIL;
}
void* uasync_add_socket(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 NULL; // Add bounds check for map
// Instance version
void* uasync_add_socket(uasync_t* 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; // Add bounds check for map
struct socket_node* node = malloc(sizeof(struct socket_node));
if (!node) return NULL;
ua->socket_alloc_count++;
node->fd = fd;
node->read_cbk = read_cbk;
node->write_cbk = write_cbk;
node->except_cbk = except_cbk;
node->user_data = user_data;
node->next = socket_head;
socket_head = node;
node->next = ua->socket_head;
ua->socket_head = node;
// Update masters (point 1)
if (read_cbk) FD_SET(fd, &master_readfds);
if (write_cbk) FD_SET(fd, &master_writefds);
if (except_cbk) FD_SET(fd, &master_exceptfds);
if (read_cbk) FD_SET(fd, &ua->master_readfds);
if (write_cbk) FD_SET(fd, &ua->master_writefds);
if (except_cbk) FD_SET(fd, &ua->master_exceptfds);
// Update map (point 2)
fd_to_node[fd] = node;
ua->fd_to_node[fd] = node;
if (fd > max_fd) max_fd = fd;
if (fd > ua->max_fd) ua->max_fd = fd;
return node;
}
err_t uasync_remove_socket(void* s_id) {
if (!s_id) return ERR_FAIL;
// Instance version
err_t uasync_remove_socket(uasync_t* ua, void* s_id) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
struct socket_node* cur = socket_head;
struct socket_node* cur = ua->socket_head;
struct socket_node* prev = NULL;
while (cur) {
@ -210,24 +232,25 @@ err_t uasync_remove_socket(void* s_id) {
if (prev) {
prev->next = cur->next;
} else {
socket_head = cur->next;
ua->socket_head = cur->next;
}
// Update masters (point 1)
if (node->read_cbk) FD_CLR(node->fd, &master_readfds);
if (node->write_cbk) FD_CLR(node->fd, &master_writefds);
if (node->except_cbk) FD_CLR(node->fd, &master_exceptfds);
if (node->read_cbk) FD_CLR(node->fd, &ua->master_readfds);
if (node->write_cbk) FD_CLR(node->fd, &ua->master_writefds);
if (node->except_cbk) FD_CLR(node->fd, &ua->master_exceptfds);
// Update map (point 2)
fd_to_node[node->fd] = NULL;
ua->fd_to_node[node->fd] = NULL;
ua->socket_free_count++;
free(cur);
// Update max_fd (simple rescan; optimize if needed by checking if removed == max_fd)
max_fd = -1;
cur = socket_head;
ua->max_fd = -1;
cur = ua->socket_head;
while (cur) {
if (cur->fd > max_fd) max_fd = cur->fd;
if (cur->fd > ua->max_fd) ua->max_fd = cur->fd;
cur = cur->next;
}
return ERR_OK;
@ -238,23 +261,28 @@ err_t uasync_remove_socket(void* s_id) {
return ERR_FAIL;
}
void uasync_mainloop(void) {
void uasync_mainloop(uasync_t* ua) {
while (1) {
uasync_poll(-1); /* infinite timeout */
uasync_poll(ua, -1); /* infinite timeout */
}
}
void uasync_poll(int timeout_tb) {
// Instance version
void uasync_poll(uasync_t* ua, int timeout_tb) {
if (!ua) return;
/* Process expired timeouts */
process_timeouts();
process_timeouts(ua);
/* Prepare select with copies of masters */
fd_set readfds = master_readfds;
fd_set writefds = master_writefds;
fd_set exceptfds = master_exceptfds;
fd_set readfds = ua->master_readfds;
fd_set writefds = ua->master_writefds;
fd_set exceptfds = ua->master_exceptfds;
struct timeval tv;
get_next_timeout(&tv);
get_next_timeout(ua, &tv);
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */
if (timeout_tb >= 0) {
@ -263,7 +291,7 @@ void uasync_poll(int timeout_tb) {
user_tv.tv_usec = (timeout_tb % 10000) * 100;
/* If no internal timer or user timeout is smaller */
if (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) {
if (tv.tv_sec == 0 && tv.tv_usec == 0 && (!ua->timeout_heap || ua->timeout_heap->size == 0)) {
tv = user_tv;
} else if (user_tv.tv_sec < tv.tv_sec ||
(user_tv.tv_sec == tv.tv_sec && user_tv.tv_usec < tv.tv_usec)) {
@ -271,9 +299,9 @@ void uasync_poll(int timeout_tb) {
}
}
struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) ? NULL : &tv;
struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && (!ua->timeout_heap || ua->timeout_heap->size == 0)) ? NULL : &tv;
int nfds = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
int nfds = select(ua->max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
if (nfds < 0) {
if (errno == EINTR) return;
perror("select");
@ -281,11 +309,11 @@ void uasync_poll(int timeout_tb) {
}
/* Process timeouts that may have expired during select */
process_timeouts();
process_timeouts(ua);
/* Process sockets with faster dispatch */
for (int fd = 0; nfds > 0 && fd <= max_fd; fd++) {
struct socket_node* node = fd_to_node[fd];
for (int fd = 0; nfds > 0 && fd <= ua->max_fd; fd++) {
struct socket_node* node = ua->fd_to_node[fd];
if (!node) continue;
if (node->except_cbk && FD_ISSET(fd, &exceptfds)) {
@ -302,3 +330,104 @@ void uasync_poll(int timeout_tb) {
}
}
}
// ========== Instance management functions ==========
uasync_t* uasync_create(void) {
uasync_t* ua = malloc(sizeof(struct uasync_s));
if (!ua) return NULL;
memset(ua, 0, sizeof(struct uasync_s));
ua->max_fd = -1;
FD_ZERO(&ua->master_readfds);
FD_ZERO(&ua->master_writefds);
FD_ZERO(&ua->master_exceptfds);
memset(ua->fd_to_node, 0, sizeof(ua->fd_to_node));
ua->timeout_heap = timeout_heap_create(16);
if (!ua->timeout_heap) {
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;
}
void uasync_destroy(uasync_t* ua) {
if (!ua) return;
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
fprintf(stderr, "[UASYNC FATAL] Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu\n",
ua->timer_alloc_count, ua->timer_free_count, 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;
ua->timer_free_count++;
freed_count++;
free(node);
}
printf("[UASYNC_DEBUG] Freed %zu timer nodes in destroy, heap freed_count = %zu\n",
freed_count, ua->timeout_heap->freed_count);
timeout_heap_destroy(ua->timeout_heap);
}
// Free all socket nodes
struct socket_node* cur = ua->socket_head;
while (cur) {
struct socket_node* next = cur->next;
ua->socket_free_count++;
free(cur);
cur = next;
}
// Final leak check
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
fprintf(stderr, "[UASYNC FATAL] Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu\n",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
abort();
}
free(ua);
}
void uasync_init_instance(uasync_t* ua) {
if (!ua) return;
ua->max_fd = -1;
FD_ZERO(&ua->master_readfds);
FD_ZERO(&ua->master_writefds);
FD_ZERO(&ua->master_exceptfds);
memset(ua->fd_to_node, 0, sizeof(ua->fd_to_node));
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(uasync_t* 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

304
u_async.c.backup

@ -0,0 +1,304 @@
// uasync.c
#include "u_async.h"
#include "timeout_heap.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#ifndef FD_SETSIZE
#define FD_SETSIZE 1024 // Assume standard size; adjust if needed for your platform
#endif
// Timeout node
struct timeout_node {
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
};
// Socket node
struct socket_node {
int fd;
socket_callback_t read_cbk;
socket_callback_t write_cbk;
socket_callback_t except_cbk;
void* user_data;
struct socket_node* next;
};
// Global state
static TimeoutHeap* timeout_heap = NULL; // Heap for timeout management
static struct socket_node* socket_head = NULL;
static int max_fd = -1;
// New: Persistent master fd_sets, updated only on add/remove
static fd_set master_readfds;
static fd_set master_writefds;
static fd_set master_exceptfds;
// New: FD-to-node map for faster post-select lookup (addresses point 2)
static struct socket_node* fd_to_node[FD_SETSIZE];
// Helper to get current time
static void get_current_time(struct timeval* tv) {
gettimeofday(tv, NULL);
}
// 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;
}
// Process expired timeouts
static void process_timeouts() {
if (!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(timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) {
node->callback(node->arg);
}
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct timeval* tv) {
if (!timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
TimeoutEntry entry;
if (timeout_heap_peek(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;
}
void uasync_init(void) {
FD_ZERO(&master_readfds);
FD_ZERO(&master_writefds);
FD_ZERO(&master_exceptfds);
memset(fd_to_node, 0, sizeof(fd_to_node)); // Init map to NULL
if (!timeout_heap) {
timeout_heap = timeout_heap_create(16); // initial capacity 16
}
}
void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback) {
if (timeout_tb < 0 || !callback) return NULL;
if (!timeout_heap) return NULL;
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) return NULL;
node->arg = arg;
node->callback = callback;
// 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);
// Insert into heap
if (timeout_heap_push(timeout_heap, node->expiration_ms, node) != 0) {
free(node);
return NULL;
}
return node;
}
err_t uasync_cancel_timeout(void* t_id) {
if (!t_id || !timeout_heap) return ERR_FAIL;
struct timeout_node* node = (struct timeout_node*)t_id;
// Try to cancel from heap
if (timeout_heap_cancel(timeout_heap, node->expiration_ms, node) == 0) {
free(node);
return ERR_OK;
}
// If not found in heap (maybe already expired and removed), still free
free(node);
return ERR_FAIL;
}
void* uasync_add_socket(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 NULL; // Add bounds check for map
struct socket_node* node = malloc(sizeof(struct socket_node));
if (!node) return NULL;
node->fd = fd;
node->read_cbk = read_cbk;
node->write_cbk = write_cbk;
node->except_cbk = except_cbk;
node->user_data = user_data;
node->next = socket_head;
socket_head = node;
// Update masters (point 1)
if (read_cbk) FD_SET(fd, &master_readfds);
if (write_cbk) FD_SET(fd, &master_writefds);
if (except_cbk) FD_SET(fd, &master_exceptfds);
// Update map (point 2)
fd_to_node[fd] = node;
if (fd > max_fd) max_fd = fd;
return node;
}
err_t uasync_remove_socket(void* s_id) {
if (!s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
struct socket_node* cur = socket_head;
struct socket_node* prev = NULL;
while (cur) {
if (cur == node) {
if (prev) {
prev->next = cur->next;
} else {
socket_head = cur->next;
}
// Update masters (point 1)
if (node->read_cbk) FD_CLR(node->fd, &master_readfds);
if (node->write_cbk) FD_CLR(node->fd, &master_writefds);
if (node->except_cbk) FD_CLR(node->fd, &master_exceptfds);
// Update map (point 2)
fd_to_node[node->fd] = NULL;
free(cur);
// Update max_fd (simple rescan; optimize if needed by checking if removed == max_fd)
max_fd = -1;
cur = socket_head;
while (cur) {
if (cur->fd > max_fd) max_fd = cur->fd;
cur = cur->next;
}
return ERR_OK;
}
prev = cur;
cur = cur->next;
}
return ERR_FAIL;
}
void uasync_mainloop(void) {
while (1) {
uasync_poll(-1); /* infinite timeout */
}
}
void uasync_poll(int timeout_tb) {
/* Process expired timeouts */
process_timeouts();
/* Prepare select with copies of masters */
fd_set readfds = master_readfds;
fd_set writefds = master_writefds;
fd_set exceptfds = master_exceptfds;
struct timeval tv;
get_next_timeout(&tv);
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */
if (timeout_tb >= 0) {
struct timeval user_tv;
user_tv.tv_sec = timeout_tb / 10000;
user_tv.tv_usec = (timeout_tb % 10000) * 100;
/* If no internal timer or user timeout is smaller */
if (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) {
tv = user_tv;
} else if (user_tv.tv_sec < tv.tv_sec ||
(user_tv.tv_sec == tv.tv_sec && user_tv.tv_usec < tv.tv_usec)) {
tv = user_tv;
}
}
struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) ? NULL : &tv;
int nfds = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
if (nfds < 0) {
if (errno == EINTR) return;
perror("select");
return;
}
/* Process timeouts that may have expired during select */
process_timeouts();
/* Process sockets with faster dispatch */
for (int fd = 0; nfds > 0 && fd <= max_fd; fd++) {
struct socket_node* node = fd_to_node[fd];
if (!node) continue;
if (node->except_cbk && FD_ISSET(fd, &exceptfds)) {
node->except_cbk(fd, node->user_data);
nfds--;
}
if (node->read_cbk && FD_ISSET(fd, &readfds)) {
node->read_cbk(fd, node->user_data);
nfds--;
}
if (node->write_cbk && FD_ISSET(fd, &writefds)) {
node->write_cbk(fd, node->user_data);
nfds--;
}
}
}

27
u_async.h

@ -7,6 +7,7 @@
#include <sys/time.h>
#include <sys/select.h>
#include <stddef.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
@ -18,19 +19,29 @@ typedef int err_t;
#define ERR_OK 0
#define ERR_FAIL -1
// API functions
void uasync_init(void);
void uasync_mainloop(void);// бесконечный цикл, __noreturn
// Opaque uasync instance handle
typedef struct uasync_s uasync_t;
// Instance API - основной API для работы с uasync
uasync_t* uasync_create(void);
void uasync_destroy(uasync_t* ua);
void uasync_init_instance(uasync_t* ua);
// Timeouts, timebase = 0.1 mS
void* uasync_set_timeout(int timeout_tb, void* user_arg, timeout_callback_t callback);
err_t uasync_cancel_timeout(void* t_id);
void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* user_arg, timeout_callback_t callback);
err_t uasync_cancel_timeout(uasync_t* ua, void* t_id);
// Sockets
void* uasync_add_socket(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(void* s_id);
void* uasync_add_socket(uasync_t* 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(uasync_t* ua, void* s_id);
// Single iteration of event loop with timeout (timebase units)
void uasync_poll(int timeout_tb);
void uasync_poll(uasync_t* ua, int timeout_tb);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(uasync_t* ua);
// Debug statistics
void uasync_get_stats(uasync_t* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
#endif // UASYNC_H

29
utun.c

@ -8,6 +8,7 @@
#include "routing.h"
#include "control_socket.h"
#include "utun_state.h"
#include "u_async.h"
#include <stdio.h>
#include <stdlib.h>
@ -104,12 +105,13 @@ static uint32_t get_dest_ip(const uint8_t *packet, size_t len) {
}
// Initialize connection from configuration
static conn_handle_t* init_connection(const connection_config_t *conn_cfg,
static conn_handle_t* init_connection(uasync_t *ua,
const connection_config_t *conn_cfg,
const global_config_t *global_cfg) {
if (!conn_cfg || !global_cfg) return NULL;
// Create connection
conn_handle_t *conn = conn_create();
conn_handle_t *conn = conn_create(ua);
if (!conn) {
fprintf(stderr, "Failed to create connection\n");
return NULL;
@ -232,8 +234,9 @@ static int init_connections(utun_state_t *state) {
if (!state->connections) return -1;
for (int i = 0; i < state->connection_count; i++) {
state->connections[i] = init_connection(&state->config->connections[i],
&state->config->global);
state->connections[i] = init_connection(state->ua,
&state->config->connections[i],
&state->config->global);
if (!state->connections[i]) {
fprintf(stderr, "Failed to initialize connection %d\n", i);
// Cleanup already created connections
@ -450,6 +453,12 @@ static void cleanup(utun_state_t *state, const char *pidfile) {
control_socket_destroy(state->control_socket);
state->control_socket = NULL;
}
// Destroy uasync instance
if (state->ua) {
uasync_destroy(state->ua);
state->ua = NULL;
}
// Close TUN device
tun_close(&state->tun);
@ -514,6 +523,10 @@ static int event_loop(utun_state_t *state) {
}
while (state->running && !got_signal) {
// Process async events (timers, sockets)
if (state->ua) {
uasync_poll(state->ua, 0);
}
// Poll all file descriptors with timeout
int ret = poll(fds, num_fds, 100); // 100ms timeout
if (ret < 0) {
@ -612,6 +625,14 @@ int main(int argc, char *argv[]) {
cleanup(&state, args.pid_file);
return 1;
}
state.ua = uasync_create();
if (!state.ua) {
fprintf(stderr, "Failed to create uasync instance\n");
cleanup(&state, args.pid_file);
return 1;
}
uasync_init_instance(state.ua);
// Setup TUN configuration from command line or config
if (args.tun_ifname) {

1
utun_state.h

@ -22,6 +22,7 @@ typedef struct utun_state {
routing_table_t *routing_table;
control_socket_t *control_socket;
int running;
uasync_t *ua;
utun_config_t *config;
FILE *log_fp;
} utun_state_t;

Loading…
Cancel
Save