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

4
Makefile

@ -23,7 +23,7 @@ ETCP_OBJS := etcp.o
CONFIG_PARSER_OBJS := config_parser.o CONFIG_PARSER_OBJS := config_parser.o
TUN_IF_OBJS := tun_if.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) $(TEST_DIR)/test_pkt_normalizer: $(TEST_DIR)/test_pkt_normalizer.o $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(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 $@ $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean: 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 *.o tinycrypt/lib/source/*.o $(TEST_DIR)/*.o
.PHONY: all clean .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_closing;
uint8_t is_destroying; uint8_t is_destroying;
conn_stats_t stats; /* Статистика подключения */
/* Таймеры */ /* Таймеры */
void* socket_read_timer; 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 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); 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)); conn_handle_t* conn = calloc(1, sizeof(conn_handle_t));
if (!conn) { if (!conn) {
return NULL; return NULL;
} }
/* Установка инстанса uasync */
conn->ua = ua;
/* Инициализация очередей приложения */ /* Инициализация очередей приложения */
conn->app_input_queue = queue_new(); conn->app_input_queue = queue_new(ua);
conn->app_output_queue = queue_new(); conn->app_output_queue = queue_new(ua);
if (!conn->app_input_queue || !conn->app_output_queue) { if (!conn->app_input_queue || !conn->app_output_queue) {
if (conn->app_input_queue) queue_free(conn->app_input_queue); if (conn->app_input_queue) queue_free(conn->app_input_queue);
if (conn->app_output_queue) queue_free(conn->app_output_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) { if (!conn->normalizer) {
close(conn->sockfd); close(conn->sockfd);
conn->sockfd = -1; conn->sockfd = -1;
@ -209,7 +205,7 @@ int conn_connect(conn_handle_t* conn,
} }
/* Инициализация ETCP */ /* Инициализация ETCP */
conn->etcp = etcp_init(); conn->etcp = etcp_init(conn->ua);
if (!conn->etcp) { if (!conn->etcp) {
pkt_normalizer_pair_deinit(conn->normalizer); pkt_normalizer_pair_deinit(conn->normalizer);
close(conn->sockfd); 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; conn->socket_connected = 1;
return 0; return 0;
@ -312,7 +308,7 @@ void conn_close(conn_handle_t* conn)
/* Отмена таймеров */ /* Отмена таймеров */
if (conn->socket_read_timer) { 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; conn->socket_read_timer = NULL;
} }
@ -632,7 +628,7 @@ static void socket_read_callback(void* arg)
/* Перезапуск таймера чтения */ /* Перезапуск таймера чтения */
if (!conn->is_closing) { 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; typedef struct conn_handle conn_handle_t;
/* Forward declaration для uasync */
typedef struct uasync_s uasync_t;
/* Режим подключения */ /* Режим подключения */
typedef enum { typedef enum {
CONN_MODE_CLIENT, /* Инициируем подключение к указанному удаленному адресу */ CONN_MODE_CLIENT, /* Инициируем подключение к указанному удаленному адресу */
@ -26,9 +29,10 @@ typedef void (*conn_recv_callback_t)(conn_handle_t* conn,
/* /*
* Создание дескриптора подключения (только выделение памяти). * Создание дескриптора подключения (только выделение памяти).
* ua - экземпляр uasync для таймеров (обязательный параметр)
* Возвращает NULL при ошибке. * Возвращает 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); static void reset_timer_callback(void* arg);
// Initialize new ETCP instance // Initialize new ETCP instance
epkt_t* etcp_init(void) { epkt_t* etcp_init(uasync_t* ua) {
epkt_t* epkt = calloc(1, sizeof(epkt_t)); epkt_t* epkt = calloc(1, sizeof(epkt_t));
if (!epkt) return NULL; if (!epkt) return NULL;
epkt->ua = ua;
// Create queues // Create queues
epkt->tx_queue = queue_new(); epkt->tx_queue = queue_new(ua);
epkt->output_queue = queue_new(); epkt->output_queue = queue_new(ua);
if (!epkt->tx_queue || !epkt->output_queue) { if (!epkt->tx_queue || !epkt->output_queue) {
if (epkt->tx_queue) queue_free(epkt->tx_queue); if (epkt->tx_queue) queue_free(epkt->tx_queue);
if (epkt->output_queue) queue_free(epkt->output_queue); if (epkt->output_queue) queue_free(epkt->output_queue);
@ -138,10 +140,16 @@ void etcp_free(epkt_t* epkt) {
// Cancel timers // Cancel timers
if (epkt->next_tx_timer) { 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) { 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 // Free queues
@ -223,11 +231,11 @@ void etcp_reset(epkt_t* epkt) {
// Cancel timers // Cancel timers
if (epkt->next_tx_timer) { 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; epkt->next_tx_timer = NULL;
} }
if (epkt->retransmit_timer) { if (epkt->retransmit_timer) {
uasync_cancel_timeout(epkt->retransmit_timer); uasync_cancel_timeout(epkt->ua, epkt->retransmit_timer);
epkt->retransmit_timer = NULL; epkt->retransmit_timer = NULL;
} }
@ -456,7 +464,7 @@ static void tx_process(epkt_t* epkt) {
if (epkt->bytes_allowed == 0) { if (epkt->bytes_allowed == 0) {
// Schedule next attempt // Schedule next attempt
if (!epkt->next_tx_timer) { 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; return;
} }
@ -488,7 +496,7 @@ static void tx_process(epkt_t* epkt) {
queue_entry_put_first(epkt->tx_queue, entry); queue_entry_put_first(epkt->tx_queue, entry);
// Schedule check when window might open (after retransmission timer) // Schedule check when window might open (after retransmission timer)
if (!epkt->next_tx_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; return;
} }
@ -521,7 +529,7 @@ static void tx_process(epkt_t* epkt) {
if (!epkt->next_tx_timer) { if (!epkt->next_tx_timer) {
uint16_t wait_time = (packet_size - epkt->bytes_allowed) / epkt->bandwidth; uint16_t wait_time = (packet_size - epkt->bytes_allowed) / epkt->bandwidth;
if (wait_time < 1) wait_time = 1; 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; return;
} }
@ -657,7 +665,7 @@ static void tx_process(epkt_t* epkt) {
// Schedule retransmit check if not already scheduled // Schedule retransmit check if not already scheduled
if (!epkt->retransmit_timer && epkt->retrans_timer_period > 0) { 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 // Resume queue callback for next packet
@ -713,7 +721,7 @@ static void retransmit_check(epkt_t* epkt) {
// Reschedule check with updated period // Reschedule check with updated period
if (epkt->retrans_timer_period > 0) { 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 { } else {
epkt->retransmit_timer = NULL; 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_ack_received = 1;
epkt->reset_pending = 0; epkt->reset_pending = 0;
if (epkt->reset_timer) { if (epkt->reset_timer) {
uasync_cancel_timeout(epkt->reset_timer); uasync_cancel_timeout(epkt->ua, epkt->reset_timer);
epkt->reset_timer = NULL; epkt->reset_timer = NULL;
} }
} }
@ -1162,7 +1170,7 @@ static void reset_timer_callback(void* arg) {
etcp_send_reset(epkt); etcp_send_reset(epkt);
// Schedule next retry in 100ms (1000 timebase units) // 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) { static void etcp_send_reset(epkt_t* epkt) {
@ -1208,7 +1216,7 @@ void etcp_reset_connection(epkt_t* epkt) {
// Cancel any existing reset timer // Cancel any existing reset timer
if (epkt->reset_timer) { if (epkt->reset_timer) {
uasync_cancel_timeout(epkt->reset_timer); uasync_cancel_timeout(epkt->ua, epkt->reset_timer);
epkt->reset_timer = NULL; epkt->reset_timer = NULL;
} }
@ -1220,7 +1228,7 @@ void etcp_reset_connection(epkt_t* epkt) {
etcp_send_reset(epkt); etcp_send_reset(epkt);
// Start retry timer (100ms = 1000 timebase units) // 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 ==================== // ==================== Queue Callbacks ====================

5
etcp.h

@ -5,6 +5,7 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include "ll_queue.h" #include "ll_queue.h"
#include "u_async.h"
// Отладочное логирование // Отладочное логирование
#ifdef ETCP_DEBUG #ifdef ETCP_DEBUG
@ -69,6 +70,7 @@ struct epkt {
// Таймеры // Таймеры
void* next_tx_timer; // Таймер для следующей передачи void* next_tx_timer; // Таймер для следующей передачи
void* retransmit_timer; // Таймер для повторных передач void* retransmit_timer; // Таймер для повторных передач
uasync_t* ua; // Экземпляр uasync для таймеров
// Обратный вызов // Обратный вызов
etcp_tx_callback_t tx_callback; etcp_tx_callback_t tx_callback;
@ -112,9 +114,10 @@ struct epkt {
/** /**
* @brief Инициализировать новый экземпляр ETCP * @brief Инициализировать новый экземпляр ETCP
* @param ua Экземпляр uasync для таймеров (обязательный параметр)
* @return Указатель на новый экземпляр или NULL в случае ошибки * @return Указатель на новый экземпляр или NULL в случае ошибки
*/ */
epkt_t* etcp_init(void); epkt_t* etcp_init(uasync_t* ua);
/** /**
* @brief Освободить экземпляр ETCP и все связанные ресурсы * @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)); ll_queue_t* q = calloc(1, sizeof(ll_queue_t));
if (!q) return NULL; if (!q) return NULL;
@ -49,6 +49,7 @@ ll_queue_t* queue_new(void) {
q->callback_arg = NULL; q->callback_arg = NULL;
q->callback_suspended = 0; // Коллбэки разрешены изначально q->callback_suspended = 0; // Коллбэки разрешены изначально
q->resume_timeout_id = NULL; q->resume_timeout_id = NULL;
q->ua = ua;
q->waiters = NULL; q->waiters = NULL;
return q; return q;
@ -75,7 +76,7 @@ void queue_free(ll_queue_t* q) {
// Отменить отложенное возобновление если запланировано // Отменить отложенное возобновление если запланировано
if (q->resume_timeout_id) { if (q->resume_timeout_id) {
uasync_cancel_timeout(q->resume_timeout_id); uasync_cancel_timeout(q->ua, q->resume_timeout_id);
} }
free(q); free(q);
@ -115,7 +116,7 @@ void queue_resume_callback(ll_queue_t* q) {
} }
// Запланировать отложенное возобновление через uasync // Запланировать отложенное возобновление через 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) { 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 ll_queue ll_queue_t;
typedef struct uasync_s uasync_t;
typedef struct ll_entry ll_entry_t; typedef struct ll_entry ll_entry_t;
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки // Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
@ -42,6 +43,7 @@ struct ll_queue {
int callback_suspended; // 1 если коллбэки приостановлены (во время обработки) int callback_suspended; // 1 если коллбэки приостановлены (во время обработки)
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
uasync_t* ua; // Экземпляр uasync для таймеров
queue_waiter_t* waiters; // Список ожидающих коллбэков queue_waiter_t* waiters; // Список ожидающих коллбэков
}; };
@ -49,8 +51,9 @@ struct ll_queue {
// ==================== Управление очередью ==================== // ==================== Управление очередью ====================
// Создать новую пустую очередь // Создать новую пустую очередь
// ua - экземпляр uasync для таймеров (обязательный параметр)
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти // Возвращает: указатель на очередь или 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 */ /* Calculate maximum regular block size that fits in max_fragment_size */
pn_struct* pkt_normalizer_init(int is_packer) { pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer) {
pn_struct* pn = malloc(sizeof(pn_struct)); pn_struct* pn = malloc(sizeof(pn_struct));
if (!pn) return NULL; if (!pn) return NULL;
pn->input = queue_new(); pn->ua = ua;
if (!pn->input) { pn->input = queue_new(ua);
free(pn); if (!pn->input) {
return NULL; free(pn);
} return NULL;
pn->output = queue_new(); }
if (!pn->output) { pn->output = queue_new(ua);
queue_free(pn->input); if (!pn->output) {
free(pn); queue_free(pn->input);
return NULL; free(pn);
} return NULL;
pn->is_packer = is_packer; }
pn->is_packer = is_packer;
if (is_packer) {
pn->u.packer.cap = settings.max_fragment_size; if (is_packer) {
pn->u.packer.buf = malloc(pn->u.packer.cap); pn->u.packer.cap = settings.max_fragment_size;
if (!pn->u.packer.buf) { pn->u.packer.buf = malloc(pn->u.packer.cap);
queue_free(pn->input); if (!pn->u.packer.buf) {
queue_free(pn->output); queue_free(pn->input);
free(pn); queue_free(pn->output);
return NULL; free(pn);
} return NULL;
}
pn->u.packer.len = 0; pn->u.packer.len = 0;
pn->u.packer.error_count = 0; pn->u.packer.error_count = 0;
queue_set_callback(pn->input, packer_handler, pn); 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; pn->u.unpacker.in_service = 0;
queue_set_callback(pn->input, unpacker_handler, pn); queue_set_callback(pn->input, unpacker_handler, pn);
} }
return pn; return pn;
} }
void pkt_normalizer_deinit(pn_struct* pn) { void pkt_normalizer_deinit(pn_struct* pn) {
if (!pn) return; if (!pn) return;
@ -72,22 +73,22 @@ void pkt_normalizer_deinit(pn_struct* pn) {
free(pn); free(pn);
} }
pkt_normalizer_pair* pkt_normalizer_pair_init(void) { pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua) {
pkt_normalizer_pair* pair = malloc(sizeof(pkt_normalizer_pair)); pkt_normalizer_pair* pair = malloc(sizeof(pkt_normalizer_pair));
if (!pair) return NULL; if (!pair) return NULL;
pair->packer = pkt_normalizer_init(1); pair->packer = pkt_normalizer_init(ua, 1);
if (!pair->packer) { if (!pair->packer) {
free(pair); free(pair);
return NULL; return NULL;
} }
pair->unpacker = pkt_normalizer_init(0); pair->unpacker = pkt_normalizer_init(ua, 0);
if (!pair->unpacker) { if (!pair->unpacker) {
pkt_normalizer_deinit(pair->packer); pkt_normalizer_deinit(pair->packer);
free(pair); free(pair);
return NULL; return NULL;
} }
return pair; return pair;
} }
void pkt_normalizer_pair_deinit(pkt_normalizer_pair* pair) { void pkt_normalizer_pair_deinit(pkt_normalizer_pair* pair) {

14
pkt_normalizer.h

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

72
tests/simple_uasync.c

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

21
tests/test_etcp.c

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

33
tests/test_etcp_stress.c

@ -43,7 +43,6 @@ typedef struct {
} network_emulator_t; } network_emulator_t;
// Forward declarations // 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 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 receiver_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* arg);
static void deliver_packets(network_emulator_t* net); static void deliver_packets(network_emulator_t* net);
@ -60,22 +59,7 @@ static double random_double(void) {
return (double)random_next() / (double)UINT32_MAX; 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 // 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) { 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 // Seed random number generator
random_state = (uint32_t)time(NULL); random_state = (uint32_t)time(NULL);
// Initialize uasync // Initialize uasync instance
uasync_init(); uasync_t* ua = uasync_create();
if (!ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(ua);
// Create network emulator // Create network emulator
network_emulator_t net = {0}; network_emulator_t net = {0};
@ -246,8 +235,8 @@ int main(void) {
net.current_time = 0; net.current_time = 0;
// Create ETCP instances // Create ETCP instances
net.sender = etcp_init(); net.sender = etcp_init(ua);
net.receiver = etcp_init(); net.receiver = etcp_init(ua);
if (!net.sender || !net.receiver) { if (!net.sender || !net.receiver) {
printf("ERROR: Failed to create ETCP instances\n"); printf("ERROR: Failed to create ETCP instances\n");
@ -263,7 +252,6 @@ int main(void) {
etcp_set_bandwidth(net.receiver, 50000); etcp_set_bandwidth(net.receiver, 50000);
// Don't start network timer - we'll advance time manually // 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); printf("\nGenerating and sending %d packets...\n", NUM_PACKETS);
@ -410,6 +398,7 @@ int main(void) {
// Cleanup // Cleanup
etcp_free(net.sender); etcp_free(net.sender);
etcp_free(net.receiver); etcp_free(net.receiver);
uasync_destroy(ua);
printf("\nStress test completed.\n"); 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 uint8_t* last_service_data = NULL;
static size_t last_service_len = 0; static size_t last_service_len = 0;
static int fragments_forwarded = 0; static int fragments_forwarded = 0;
static uasync_t* test_ua = NULL;
// Callback для сервисных пакетов // Callback для сервисных пакетов
static void service_packet_callback(void* user_data, uint8_t type, const uint8_t* data, size_t len) { 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) { int test_fragmentation_fix(void) {
printf("\n=== Test 1: Fragmentation Fix (Last Fragment Split) ===\n"); 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; int original_fragment_size = settings.max_fragment_size;
@ -164,7 +166,7 @@ int test_fragmentation_fix(void) {
// Устанавливаем маленький размер фрагмента для тестирования edge case // Устанавливаем маленький размер фрагмента для тестирования edge case
settings.max_fragment_size = 200; // 200 байт 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"); TEST_ASSERT(pair != NULL, "pair initialization");
// Set up forwarding: packer output -> unpacker input // Set up forwarding: packer output -> unpacker input
@ -243,6 +245,7 @@ int test_fragmentation_fix(void) {
free(test_data); free(test_data);
pkt_normalizer_pair_deinit(pair); pkt_normalizer_pair_deinit(pair);
printf("DEBUG: test_fragmentation_fix end\n");
return 0; return 0;
} }
@ -250,11 +253,12 @@ int test_fragmentation_fix(void) {
int test_service_packets(void) { int test_service_packets(void) {
printf("\n=== Test 2: Service Packets (0xFC/0xFD) ===\n"); printf("\n=== Test 2: Service Packets (0xFC/0xFD) ===\n");
printf("DEBUG: test_service_packets start\n");
reset_test_state(); reset_test_state();
// Используем пару для связи packer и unpacker // Используем пару для связи 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"); TEST_ASSERT(pair != NULL, "pair initialization");
// Set up forwarding: packer output -> unpacker input // Set up forwarding: packer output -> unpacker input
@ -339,6 +343,7 @@ int test_service_packets(void) {
pkt_normalizer_pair_deinit(pair); pkt_normalizer_pair_deinit(pair);
printf("DEBUG: test_service_packets end\n");
return 0; return 0;
} }
@ -363,18 +368,23 @@ static int uasync_initialized = 0;
int test_etcp_reset(void) { int test_etcp_reset(void) {
printf("\n=== Test 3: ETCP Reset Connection (0x02/0x03) ===\n"); printf("\n=== Test 3: ETCP Reset Connection (0x02/0x03) ===\n");
printf("DEBUG: test_etcp_reset start\n");
// Инициализируем uasync для таймеров // Инициализируем uasync для таймеров
if (!uasync_initialized) { if (!test_ua) {
uasync_init(); test_ua = uasync_create();
uasync_initialized = 1; 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"); 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"); TEST_ASSERT(tx_queue != NULL, "create tx queue");
// Устанавливаем callback для отправки // Устанавливаем callback для отправки
@ -453,6 +463,7 @@ int test_etcp_reset(void) {
queue_free(tx_queue); queue_free(tx_queue);
etcp_free(epkt); etcp_free(epkt);
printf("DEBUG: test_etcp_reset end\n");
return 0; return 0;
} }
@ -460,11 +471,12 @@ int test_etcp_reset(void) {
int test_conn_reset(void) { int test_conn_reset(void) {
printf("\n=== Test 4: conn_reset() Full Chain ===\n"); 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"); TEST_ASSERT(conn != NULL, "connection creation");
// Вызываем conn_reset (должен работать даже без установленного соединения) // Вызываем conn_reset (должен работать даже без установленного соединения)
@ -474,19 +486,25 @@ int test_conn_reset(void) {
// Очистка // Очистка
conn_destroy(conn); conn_destroy(conn);
printf("DEBUG: test_conn_reset end\n");
return 0; return 0;
} }
// ==================== Основная функция ==================== // ==================== Основная функция ====================
int main(void) { int main(void) {
setvbuf(stdout, NULL, _IONBF, 0); // disable buffering
printf("=== Testing New Features ===\n"); printf("=== Testing New Features ===\n");
int result = 0; int result = 0;
// Инициализируем uasync глобально // Инициализируем uasync глобально
uasync_init(); test_ua = uasync_create();
uasync_initialized = 1; if (!test_ua) {
fprintf(stderr, "Failed to create uasync instance\n");
return 1;
}
uasync_init_instance(test_ua);
// Запускаем тесты // Запускаем тесты
result |= test_fragmentation_fix(); result |= test_fragmentation_fix();
@ -505,6 +523,21 @@ int main(void) {
free(last_service_data); free(last_service_data);
last_service_data = NULL; 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; return result;
} }

17
tests/test_pkt_normalizer.c

@ -1,6 +1,7 @@
#include "u_async.h" #include "u_async.h"
#include "ll_queue.h" #include "ll_queue.h"
#include "pkt_normalizer.h" #include "pkt_normalizer.h"
#include "u_async.h"
#include "settings.h" #include "settings.h"
#include <stdio.h> #include <stdio.h>
#include <string.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 test_packet_t received_packets[MAX_TEST_PACKETS];
static int sent_count = 0; static int sent_count = 0;
static int received_count = 0; static int received_count = 0;
static uasync_t* test_ua = NULL;
static void reset_test_data(void) { static void reset_test_data(void) {
for (int i = 0; i < sent_count; i++) { 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) { static int test_async_wait(void) {
printf("\n--- Test async wait: threshold waiter ---\n"); 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"); TEST_ASSERT(q != NULL, "create queue for async wait test");
int callback_called = 0; int callback_called = 0;
@ -726,13 +728,13 @@ static int test_async_wait(void) {
static int test_lifecycle(void) { static int test_lifecycle(void) {
printf("\n--- Test 5: lifecycle ---\n"); 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 != NULL, "pair initialization");
TEST_ASSERT(pair->packer != NULL, "packer initialization"); TEST_ASSERT(pair->packer != NULL, "packer initialization");
TEST_ASSERT(pair->unpacker != NULL, "unpacker 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"); TEST_ASSERT(test_queue != NULL, "test queue creation");
/* Отправить один пакет */ /* Отправить один пакет */
@ -757,9 +759,14 @@ int main(void) {
printf("=== Packet Normalizer Unit Test ===\n"); printf("=== Packet Normalizer Unit Test ===\n");
srand((unsigned int)time(NULL)); 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) { if (!pair) {
fprintf(stderr, "Failed to initialize packet normalizer pair\n"); fprintf(stderr, "Failed to initialize packet normalizer pair\n");
return 1; return 1;

15
tests/test_udp_secure.c

@ -158,7 +158,12 @@ int main(void) {
uECC_set_rng(&default_CSPRNG); uECC_set_rng(&default_CSPRNG);
// Initialize async library // 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 // Create UDP sockets
client_a.sockfd = create_udp_socket(PORT_A, &client_a.addr); client_a.sockfd = create_udp_socket(PORT_A, &client_a.addr);
@ -184,18 +189,18 @@ int main(void) {
"session keys match"); "session keys match");
// Register sockets with async library // Register sockets with async library
uasync_add_socket(client_a.sockfd, client_a_read_callback, NULL, NULL, &client_a); uasync_add_socket(ua, 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_b.sockfd, client_b_read_callback, NULL, NULL, &client_b);
// Set timeout for test completion (2 seconds) // 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 from A to B
send_encrypted_message(&client_a, &client_b); send_encrypted_message(&client_a, &client_b);
// Run async mainloop (will exit via callback or timeout) // Run async mainloop (will exit via callback or timeout)
printf("Starting async mainloop...\n"); printf("Starting async mainloop...\n");
uasync_mainloop(); // This will not return uasync_mainloop(ua); // This will not return
// Should not reach here // Should not reach here
return 1; return 1;

20
timeout_heap.c

@ -1,4 +1,4 @@
// timeout_heap.c // timeout_heap.c
#include "timeout_heap.h" #include "timeout_heap.h"
#include <stdlib.h> #include <stdlib.h>
@ -19,6 +19,9 @@ TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
} }
h->size = 0; h->size = 0;
h->capacity = initial_capacity; h->capacity = initial_capacity;
h->freed_count = 0;
h->user_data = NULL;
h->free_callback = NULL;
return h; return h;
} }
@ -112,9 +115,16 @@ int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
if (h->size == 0) return -1; if (h->size == 0) return -1;
// Skip deleted // Skip deleted and free their data
while (h->size > 0 && h->heap[0].deleted) { while (h->size > 0 && h->heap[0].deleted) {
void *data_to_free = h->heap[0].data;
remove_root(h); 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; if (h->size == 0) return -1;
@ -131,4 +141,10 @@ int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
} }
} }
return -1; // Not found 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 #ifndef TIMEOUT_HEAP_H
#define TIMEOUT_HEAP_H #define TIMEOUT_HEAP_H
@ -14,11 +14,16 @@ typedef struct {
int deleted; // 0 = active, 1 = deleted int deleted; // 0 = active, 1 = deleted
} TimeoutEntry; } TimeoutEntry;
typedef struct { typedef struct TimeoutHeap TimeoutHeap;
struct TimeoutHeap {
TimeoutEntry *heap; // Dynamic array TimeoutEntry *heap; // Dynamic array
size_t size; // Current number of elements size_t size; // Current number of elements
size_t capacity; // Allocated size 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. * 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); 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. * Insert a new timeout into the heap.
* @param h 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); 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 #endif // TIMEOUT_HEAP_H

1
todo.txt

@ -0,0 +1 @@


271
u_async.c

@ -17,6 +17,7 @@ struct timeout_node {
void* arg; void* arg;
timeout_callback_t callback; timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds uint64_t expiration_ms; // absolute expiration time in milliseconds
uasync_t* ua; // Pointer back to uasync instance for counter updates
}; };
// Socket node // Socket node
@ -29,18 +30,32 @@ struct socket_node {
struct socket_node* next; struct socket_node* next;
}; };
// Global state // Uasync instance structure
static TimeoutHeap* timeout_heap = NULL; // Heap for timeout management struct uasync_s {
static struct socket_node* socket_head = NULL; TimeoutHeap* timeout_heap; // Heap for timeout management
static int max_fd = -1; 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 // No global instance - each module must use its own uasync_t instance
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) // Callback to free timeout node and update counters
static struct socket_node* fd_to_node[FD_SETSIZE]; 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 // Helper to get current time
static void get_current_time(struct timeval* tv) { 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 // Process expired timeouts
static void process_timeouts() { static void process_timeouts(struct uasync_s* ua) {
if (!timeout_heap) return; if (!ua || !ua->timeout_heap) return;
struct timeval now_tv; struct timeval now_tv;
get_current_time(&now_tv); get_current_time(&now_tv);
@ -73,29 +88,30 @@ static void process_timeouts() {
while (1) { while (1) {
TimeoutEntry entry; 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; if (entry.expiration > now_ms) break;
// Pop the expired timeout // 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; struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) { if (node && node->callback) {
node->callback(node->arg); node->callback(node->arg);
} }
ua->timer_free_count++;
free(node); free(node);
} }
} }
// Compute time to next timeout // Compute time to next timeout
static void get_next_timeout(struct timeval* tv) { static void get_next_timeout(struct uasync_s* ua, struct timeval* tv) {
if (!timeout_heap) { if (!ua || !ua->timeout_heap) {
tv->tv_sec = 0; tv->tv_sec = 0;
tv->tv_usec = 0; tv->tv_usec = 0;
return; return;
} }
TimeoutEntry entry; 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_sec = 0;
tv->tv_usec = 0; tv->tv_usec = 0;
return; return;
@ -119,26 +135,20 @@ static void get_next_timeout(struct timeval* tv) {
tv->tv_usec = (delta_ms % 1000) * 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; // Instance version
if (!timeout_heap) return NULL; 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)); struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) return NULL; if (!node) return NULL;
ua->timer_alloc_count++;
node->arg = arg; node->arg = arg;
node->callback = callback; node->callback = callback;
node->ua = ua;
// Calculate expiration time in milliseconds // Calculate expiration time in milliseconds
struct timeval now; 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); node->expiration_ms = timeval_to_ms(&now);
// Insert into heap // 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); free(node);
return NULL; return NULL;
} }
@ -155,54 +166,65 @@ void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback)
return node; 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; struct timeout_node* node = (struct timeout_node*)t_id;
// Try to cancel from heap // Try to cancel from heap
if (timeout_heap_cancel(timeout_heap, node->expiration_ms, node) == 0) { if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
free(node); // Mark as cancelled by clearing callback - memory will be freed later
node->callback = NULL;
return ERR_OK; return ERR_OK;
} }
// If not found in heap (maybe already expired and removed), still free // If not found in heap (maybe already expired and removed), do NOT free
free(node); // because node was already freed in process_timeouts
return ERR_FAIL; 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)); struct socket_node* node = malloc(sizeof(struct socket_node));
if (!node) return NULL; if (!node) return NULL;
ua->socket_alloc_count++;
node->fd = fd; node->fd = fd;
node->read_cbk = read_cbk; node->read_cbk = read_cbk;
node->write_cbk = write_cbk; node->write_cbk = write_cbk;
node->except_cbk = except_cbk; node->except_cbk = except_cbk;
node->user_data = user_data; node->user_data = user_data;
node->next = socket_head; node->next = ua->socket_head;
socket_head = node; ua->socket_head = node;
// Update masters (point 1) // Update masters (point 1)
if (read_cbk) FD_SET(fd, &master_readfds); if (read_cbk) FD_SET(fd, &ua->master_readfds);
if (write_cbk) FD_SET(fd, &master_writefds); if (write_cbk) FD_SET(fd, &ua->master_writefds);
if (except_cbk) FD_SET(fd, &master_exceptfds); if (except_cbk) FD_SET(fd, &ua->master_exceptfds);
// Update map (point 2) // 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; 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* node = (struct socket_node*)s_id;
struct socket_node* cur = socket_head; struct socket_node* cur = ua->socket_head;
struct socket_node* prev = NULL; struct socket_node* prev = NULL;
while (cur) { while (cur) {
@ -210,24 +232,25 @@ err_t uasync_remove_socket(void* s_id) {
if (prev) { if (prev) {
prev->next = cur->next; prev->next = cur->next;
} else { } else {
socket_head = cur->next; ua->socket_head = cur->next;
} }
// Update masters (point 1) // Update masters (point 1)
if (node->read_cbk) FD_CLR(node->fd, &master_readfds); if (node->read_cbk) FD_CLR(node->fd, &ua->master_readfds);
if (node->write_cbk) FD_CLR(node->fd, &master_writefds); if (node->write_cbk) FD_CLR(node->fd, &ua->master_writefds);
if (node->except_cbk) FD_CLR(node->fd, &master_exceptfds); if (node->except_cbk) FD_CLR(node->fd, &ua->master_exceptfds);
// Update map (point 2) // Update map (point 2)
fd_to_node[node->fd] = NULL; ua->fd_to_node[node->fd] = NULL;
ua->socket_free_count++;
free(cur); free(cur);
// Update max_fd (simple rescan; optimize if needed by checking if removed == max_fd) // Update max_fd (simple rescan; optimize if needed by checking if removed == max_fd)
max_fd = -1; ua->max_fd = -1;
cur = socket_head; cur = ua->socket_head;
while (cur) { 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; cur = cur->next;
} }
return ERR_OK; return ERR_OK;
@ -238,23 +261,28 @@ err_t uasync_remove_socket(void* s_id) {
return ERR_FAIL; return ERR_FAIL;
} }
void uasync_mainloop(void) {
void uasync_mainloop(uasync_t* ua) {
while (1) { 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 expired timeouts */
process_timeouts(); process_timeouts(ua);
/* Prepare select with copies of masters */ /* Prepare select with copies of masters */
fd_set readfds = master_readfds; fd_set readfds = ua->master_readfds;
fd_set writefds = master_writefds; fd_set writefds = ua->master_writefds;
fd_set exceptfds = master_exceptfds; fd_set exceptfds = ua->master_exceptfds;
struct timeval tv; 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, compute timeout as min(timeout_tb, existing timer) */
if (timeout_tb >= 0) { if (timeout_tb >= 0) {
@ -263,7 +291,7 @@ void uasync_poll(int timeout_tb) {
user_tv.tv_usec = (timeout_tb % 10000) * 100; user_tv.tv_usec = (timeout_tb % 10000) * 100;
/* If no internal timer or user timeout is smaller */ /* 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; tv = user_tv;
} else if (user_tv.tv_sec < tv.tv_sec || } else if (user_tv.tv_sec < tv.tv_sec ||
(user_tv.tv_sec == tv.tv_sec && user_tv.tv_usec < tv.tv_usec)) { (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 (nfds < 0) {
if (errno == EINTR) return; if (errno == EINTR) return;
perror("select"); perror("select");
@ -281,11 +309,11 @@ void uasync_poll(int timeout_tb) {
} }
/* Process timeouts that may have expired during select */ /* Process timeouts that may have expired during select */
process_timeouts(); process_timeouts(ua);
/* Process sockets with faster dispatch */ /* Process sockets with faster dispatch */
for (int fd = 0; nfds > 0 && fd <= max_fd; fd++) { for (int fd = 0; nfds > 0 && fd <= ua->max_fd; fd++) {
struct socket_node* node = fd_to_node[fd]; struct socket_node* node = ua->fd_to_node[fd];
if (!node) continue; if (!node) continue;
if (node->except_cbk && FD_ISSET(fd, &exceptfds)) { 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/time.h>
#include <sys/select.h> #include <sys/select.h>
#include <stddef.h>
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout 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 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_OK 0
#define ERR_FAIL -1 #define ERR_FAIL -1
// API functions // Opaque uasync instance handle
void uasync_init(void); typedef struct uasync_s uasync_t;
void uasync_mainloop(void);// бесконечный цикл, __noreturn
// 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 // Timeouts, timebase = 0.1 mS
void* uasync_set_timeout(int timeout_tb, void* user_arg, timeout_callback_t callback); void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* user_arg, timeout_callback_t callback);
err_t uasync_cancel_timeout(void* t_id); err_t uasync_cancel_timeout(uasync_t* ua, void* t_id);
// Sockets // 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); 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(void* s_id); err_t uasync_remove_socket(uasync_t* ua, void* s_id);
// Single iteration of event loop with timeout (timebase units) // 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 #endif // UASYNC_H

29
utun.c

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

1
utun_state.h

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

Loading…
Cancel
Save