Browse Source

Refactor u_async into shared module: move sources to u_async/, update Makefiles, remove duplicates

v2_dev
jek 9 months ago
parent
commit
ec49b12a19
  1. 14
      Makefile
  2. 15
      changelog.txt
  3. 1
      config_parser.h
  4. 1
      connection.h
  5. 1
      control_socket.h
  6. 1
      etcp.h
  7. 179
      etcp.h1
  8. 1
      ll_queue.h
  9. 20
      net_emulator/Makefile
  10. 412
      net_emulator/test_timeout_heap.c
  11. 258
      net_emulator/test_uasync_random.c
  12. 134
      net_emulator/timeout_heap.c
  13. 72
      net_emulator/timeout_heap.h
  14. 391
      net_emulator/u_async.c
  15. 45
      net_emulator/u_async.h
  16. 1
      pkt_normalizer.h
  17. 1
      routing.h
  18. 1
      sc_lib.h
  19. 1
      settings.h
  20. 2842
      stress.log
  21. 2834
      stress.out
  22. 1355
      stress_50.log
  23. 1098
      stress_new.log
  24. 2
      tests/simple_uasync.c
  25. 12
      tests/test_new_features.c
  26. 1
      timeout_heap.h
  27. 1
      tun_if.h
  28. 1
      u_async.h
  29. 0
      u_async/timeout_heap.c
  30. 0
      u_async/timeout_heap.h
  31. 0
      u_async/u_async.c
  32. 0
      u_async/u_async.h
  33. 1
      utun_state.h

14
Makefile

@ -2,7 +2,7 @@ CC := gcc
CFLAGS := -Os -std=c99 -Wall -Wextra -D_ISOC99_SOURCE -DENABLE_TESTS -DETCP_DEBUG -DETCP_DEBUG_EXT
SRC_DIR := src
TEST_DIR := tests
INCLUDES := -I$(SRC_DIR) -Itinycrypt/lib/include/ -Itinycrypt/lib/source/ -Itinycrypt/tests/include/ -I.
INCLUDES := -I$(SRC_DIR) -Iu_async -Itinycrypt/lib/include/ -Itinycrypt/lib/source/ -Itinycrypt/tests/include/ -I.
# Директории для объектных файлов
OBJ_DIR := obj
@ -20,6 +20,11 @@ TINYCRYPT_SRCS := \
tinycrypt/lib/source/ctr_mode.c \
tinycrypt/lib/source/ccm_mode.c
# Исходники u_async
UASYNC_SRCS := \
u_async/u_async.c \
u_async/timeout_heap.c
# Объектные файлы tinycrypt (остаются в своих директориях)
TINYCRYPT_OBJS := $(TINYCRYPT_SRCS:.c=.o)
@ -86,6 +91,13 @@ $(TEST_OBJ_DIR)/%.o: $(TEST_DIR)/%.c | $(TEST_OBJ_DIR)
tinycrypt/lib/source/%.o: tinycrypt/lib/source/%.c
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
# Правило компиляции для u_async файлов
$(SRC_OBJ_DIR)/u_async.o: u_async/u_async.c | $(SRC_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
$(SRC_OBJ_DIR)/timeout_heap.o: u_async/timeout_heap.c | $(SRC_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
# Правила линковки
utun: $(UTUN_OBJS) $(CONFIG_PARSER_OBJS) $(TUN_IF_OBJS) $(CONNECTION_OBJS) $(ROUTING_OBJS) $(CONTROL_SOCKET_OBJS) $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(SC_LIB_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^

15
changelog.txt

@ -53,3 +53,18 @@ Thu Jan 15 2026 17:09: Реорганизация структуры проек
- Добавлены символические ссылки на заголовочные файлы в корне для совместимости с тестами
- Обновлены пути включения заголовков (-Isrc)
- Все цели сборки работают корректно
Thu Jan 15 2026 17:23: Завершение реорганизации проекта - удаление симлинков и исправление includes
- Удалены все символические ссылки на заголовочные файлы из корневой директории
- Обновлены include директивы в тестовых файлах: заменены #include "../header.h" на #include "header.h"
- Исправлен файл tests/simple_uasync.c
- Все тесты и основное приложение успешно компилируются без симлинков
- Структура проекта: src/ для исходников, tests/ для тестов, obj/ для объектных файлов
Thu Jan 15 2026 17:49: Унификация модуля u_async для основного проекта и net_emulator
- Модуль u_async выделен в отдельный каталог u_async/ (исходники: u_async.c, timeout_heap.c)
- Обновлен основной Makefile: добавлен -Iu_async, правила компиляции для файлов из u_async/
- Обновлен net_emulator: удалены локальные копии u_async.c, u_async.h, timeout_heap.c, timeout_heap.h
- Обновлен Makefile net_emulator: использует общие объектные файлы из ../obj/src/, добавлен -I../u_async
- Удалены тестовые файлы net_emulator (test_timeout_heap.c, test_uasync_random.c) как несовместимые с общей реализацией
- Все цели сборки (основной проект и net_emulator) компилируются успешно

1
config_parser.h

@ -1 +0,0 @@
src/config_parser.h

1
connection.h

@ -1 +0,0 @@
src/connection.h

1
control_socket.h

@ -1 +0,0 @@
src/control_socket.h

1
etcp.h

@ -1 +0,0 @@
src/etcp.h

179
etcp.h1

@ -1,179 +0,0 @@
// etcp.h - Extended Transmission Control Protocol
#ifndef ETCP_H
#define ETCP_H
#include <stdint.h>
#include <stddef.h>
#include "ll_queue.h"
// Debug logging
#ifdef ETCP_DEBUG
#include <stdio.h>
#define ETCP_LOG(fmt, ...) printf("[ETCP] " fmt, ##__VA_ARGS__)
#else
#define ETCP_LOG(fmt, ...) ((void)0)
#endif
#ifdef __cplusplus
extern "C" {
#endif
// Forward declarations
typedef struct epkt epkt_t;
// Callback type for sending packets via UDP
typedef void (*etcp_tx_callback_t)(epkt_t* epkt, uint8_t* pkt, uint16_t len, void* arg);
// Main ETCP structure
struct epkt {
// Queues
ll_queue_t* tx_queue; // Queue of data to send
ll_queue_t* output_queue; // Output queue (reassembled data)
// Received packets sorted linked list
struct rx_packet* rx_list;
// Sent packets (for retransmission)
struct sent_packet* sent_list;
// Metrics
uint16_t rtt_last; // Last RTT (timebase 0.1us)
uint16_t rtt_avg_10; // Average RTT last 10 packets
uint16_t rtt_avg_100; // Average RTT last 100 packets
uint16_t jitter; // Jitter (averaged)
uint16_t bandwidth; // Current bandwidth (bytes per timebase)
uint32_t bytes_sent_total; // Total bytes sent
uint16_t last_sent_timestamp; // Timestamp of last sent packet
uint32_t bytes_allowed; // Calculated bytes allowed to send
// State
uint16_t next_tx_id; // Next ID for transmission
uint16_t last_rx_id; // Last received ID (for ACK)
uint16_t last_delivered_id; // Last delivered to output_queue ID
// Timers
void* next_tx_timer; // Timer for next transmission
void* retransmit_timer; // Timer for retransmissions
// Callback
etcp_tx_callback_t tx_callback;
void* tx_callback_arg;
// RTT history for averaging
uint16_t rtt_history[100];
uint8_t rtt_history_idx;
uint8_t rtt_history_count;
// Pending ACKs
uint16_t pending_ack_ids[32];
uint16_t pending_ack_timestamps[32];
uint8_t pending_ack_count;
// Pending retransmission requests
uint16_t pending_retransmit_ids[32];
uint8_t pending_retransmit_count;
// Window management
uint32_t unacked_bytes; // Number of bytes sent but not yet acknowledged
uint32_t window_size; // Current window size in bytes (calculated)
uint16_t last_acked_id; // Last acknowledged packet ID
uint16_t last_rx_ack_id; // Latest received ACK ID from receiver
uint16_t retrans_timer_period; // Current retransmission timer period (timebase)
uint16_t next_retrans_time; // Time of next retransmission check
uint8_t window_blocked; // Flag: transmission blocked by window limit
};
// API Functions
/**
* @brief Initialize new ETCP instance
* @return Pointer to new instance or NULL on error
*/
epkt_t* etcp_init(void);
/**
* @brief Free ETCP instance and all associated resources
* @param epkt Instance to free
*/
void etcp_free(epkt_t* epkt);
/**
* @brief Set callback for sending packets via UDP
* @param epkt ETCP instance
* @param cb Callback function
* @param arg User argument passed to callback
*/
void etcp_set_callback(epkt_t* epkt, etcp_tx_callback_t cb, void* arg);
/**
* @brief Process received UDP packet
* @param epkt ETCP instance
* @param pkt Packet data
* @param len Packet length
* @return 0 on success, -1 on error
*/
int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len);
/**
* @brief Get total number of packets waiting in transmission queues
* @param epkt ETCP instance
* @return Number of packets
*/
int etcp_tx_queue_size(epkt_t* epkt);
/**
* @brief Put data into transmission queue
* @param epkt ETCP instance
* @param data Data to send
* @param len Data length
* @return 0 on success, -1 on error
*/
int etcp_tx_put(epkt_t* epkt, uint8_t* data, uint16_t len);
/**
* @brief Get output queue for reading received data
* @param epkt ETCP instance
* @return Pointer to output queue (ll_queue_t*)
*/
ll_queue_t* etcp_get_output_queue(epkt_t* epkt);
/**
* @brief Set bandwidth limit
* @param epkt ETCP instance
* @param bandwidth Bytes per timebase (0.1us)
*/
void etcp_set_bandwidth(epkt_t* epkt, uint16_t bandwidth);
/**
* @brief Update window size based on current RTT and bandwidth
* @param epkt ETCP instance
* Window size = RTT * bandwidth * 2 (bytes in flight)
*/
void etcp_update_window(epkt_t* epkt);
/**
* @brief Get current RTT
* @param epkt ETCP instance
* @return RTT in timebase units
*/
uint16_t etcp_get_rtt(epkt_t* epkt);
/**
* @brief Get current jitter
* @param epkt ETCP instance
* @return Jitter in timebase units
*/
uint16_t etcp_get_jitter(epkt_t* epkt);
/**
* @brief Reset connection state (clear queues, metrics, timers)
* @param epkt ETCP instance
* Note: Keeps bandwidth setting and callback
*/
void etcp_reset(epkt_t* epkt);
#ifdef __cplusplus
}
#endif
#endif // ETCP_H

1
ll_queue.h

@ -1 +0,0 @@
src/ll_queue.h

20
net_emulator/Makefile

@ -1,33 +1,25 @@
# Makefile for network emulator
CC = gcc
CFLAGS = -Os -std=c99 -Wall -Wextra -D_ISOC99_SOURCE
INCLUDES = -I.
INCLUDES = -I. -I../u_async
SRCS = net_emulator.c u_async.c timeout_heap.c
SRCS = net_emulator.c
OBJS = $(SRCS:.c=.o)
UASYNC_OBJS = ../obj/src/u_async.o ../obj/src/timeout_heap.o
TARGET = net_emulator
all: $(TARGET)
$(TARGET): $(OBJS)
$(TARGET): $(OBJS) $(UASYNC_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
test: test_timeout_heap test_uasync_random
./test_timeout_heap
./test_uasync_random
test_timeout_heap: test_timeout_heap.o timeout_heap.o
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
test_uasync_random: test_uasync_random.o u_async.o timeout_heap.o
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET) test_timeout_heap.o timeout_heap.o test_timeout_heap \
test_uasync_random.o test_uasync_random
rm -f $(OBJS) $(TARGET)
.PHONY: all clean test
.PHONY: all clean

412
net_emulator/test_timeout_heap.c

@ -1,412 +0,0 @@
#include "timeout_heap.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <stdint.h>
#define TEST_ASSERT(cond, msg) do { \
if (!(cond)) { \
fprintf(stderr, "FAIL: %s:%d: %s\n", __FILE__, __LINE__, (msg)); \
exit(1); \
} \
} while (0)
// Element structure for large random test
typedef struct {
uint64_t expiration;
void *data;
int cancelled;
} HeapElement;
// Helper: compare two entries (for qsort)
static int compare_entries(const void *a, const void *b) {
const TimeoutEntry *ea = (const TimeoutEntry *)a;
const TimeoutEntry *eb = (const TimeoutEntry *)b;
if (ea->expiration < eb->expiration) return -1;
if (ea->expiration > eb->expiration) return 1;
return 0;
}
// Test 1: basic operations
static void test_basic(void) {
printf("Test 1: basic operations...\n");
TimeoutHeap *h = timeout_heap_create(10);
TEST_ASSERT(h != NULL, "heap creation");
TEST_ASSERT(h->size == 0, "initial size zero");
// Push one element
int data1 = 42;
int ret = timeout_heap_push(h, 100, &data1);
TEST_ASSERT(ret == 0, "push success");
TEST_ASSERT(h->size == 1, "size after push");
// Peek
TimeoutEntry entry;
ret = timeout_heap_peek(h, &entry);
TEST_ASSERT(ret == 0, "peek success");
TEST_ASSERT(entry.expiration == 100, "peek expiration");
TEST_ASSERT(entry.data == &data1, "peek data");
TEST_ASSERT(entry.deleted == 0, "peek deleted flag");
// Pop
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop success");
TEST_ASSERT(entry.expiration == 100, "pop expiration");
TEST_ASSERT(entry.data == &data1, "pop data");
TEST_ASSERT(h->size == 0, "size after pop");
// Pop empty
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == -1, "pop empty returns -1");
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Test 2: ordering with random values
static void test_ordering(size_t num_elements) {
printf("Test 2: ordering with %zu elements...\n", num_elements);
TimeoutHeap *h = timeout_heap_create(4); // small initial capacity
TEST_ASSERT(h != NULL, "heap creation");
// Generate random expirations and store them
TimeoutEntry *expected = malloc(num_elements * sizeof(TimeoutEntry));
TEST_ASSERT(expected != NULL, "alloc expected array");
srand((unsigned int)time(NULL));
for (size_t i = 0; i < num_elements; i++) {
// Generate unique expiration to avoid ties
uint64_t exp = i * 1000000ULL + (rand() % 1000000);
int *data = malloc(sizeof(int));
*data = (int)i;
int ret = timeout_heap_push(h, exp, data);
TEST_ASSERT(ret == 0, "push success");
expected[i].expiration = exp;
expected[i].data = data;
expected[i].deleted = 0;
}
// Sort expected by expiration
qsort(expected, num_elements, sizeof(TimeoutEntry), compare_entries);
// Pop and verify order
for (size_t i = 0; i < num_elements; i++) {
TimeoutEntry entry;
int ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop success");
TEST_ASSERT(entry.expiration == expected[i].expiration, "expiration order");
TEST_ASSERT(entry.data == expected[i].data, "data matches");
TEST_ASSERT(entry.deleted == 0, "not deleted");
free(entry.data); // cleanup
}
// Heap should be empty
TimeoutEntry entry;
int ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == -1, "heap empty after all pops");
free(expected);
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Test 3: cancel operation
static void test_cancel(void) {
printf("Test 3: cancel operation...\n");
TimeoutHeap *h = timeout_heap_create(10);
TEST_ASSERT(h != NULL, "heap creation");
int data1 = 1, data2 = 2, data3 = 3;
timeout_heap_push(h, 100, &data1);
timeout_heap_push(h, 200, &data2);
timeout_heap_push(h, 300, &data3);
// Cancel middle element
int ret = timeout_heap_cancel(h, 200, &data2);
TEST_ASSERT(ret == 0, "cancel success");
// Pop and verify we get data1 then data3, not data2
TimeoutEntry entry;
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop 1 success");
TEST_ASSERT(entry.expiration == 100, "first expiration");
TEST_ASSERT(entry.data == &data1, "first data");
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop 2 success");
TEST_ASSERT(entry.expiration == 300, "second expiration");
TEST_ASSERT(entry.data == &data3, "second data");
// Heap empty
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == -1, "heap empty");
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Test 4: duplicate expiration times
static void test_duplicate_expirations(void) {
printf("Test 4: duplicate expirations...\n");
TimeoutHeap *h = timeout_heap_create(10);
TEST_ASSERT(h != NULL, "heap creation");
int data1 = 1, data2 = 2, data3 = 3;
// Push same expiration for data1 and data2
timeout_heap_push(h, 100, &data1);
timeout_heap_push(h, 100, &data2);
timeout_heap_push(h, 200, &data3);
// Pop should give both items with expiration 100 (order not guaranteed)
TimeoutEntry entry;
int ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "first pop success");
TEST_ASSERT(entry.expiration == 100, "first pop expiration");
// Record which data pointer we got
void *first_data = entry.data;
TEST_ASSERT(first_data == &data1 || first_data == &data2, "first pop data matches one of duplicates");
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "second pop success");
TEST_ASSERT(entry.expiration == 100, "second pop expiration");
void *second_data = entry.data;
TEST_ASSERT(second_data == &data1 || second_data == &data2, "second pop data matches one of duplicates");
TEST_ASSERT(first_data != second_data, "two different data pointers");
// Third pop should give expiration 200
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "third pop success");
TEST_ASSERT(entry.expiration == 200, "third pop expiration");
TEST_ASSERT(entry.data == &data3, "third pop data");
// Heap empty
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == -1, "heap empty");
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Test 5: deleted flag handling (multiple deleted at root)
static void test_deleted_root(void) {
printf("Test 5: deleted root handling...\n");
TimeoutHeap *h = timeout_heap_create(10);
TEST_ASSERT(h != NULL, "heap creation");
int data1 = 1, data2 = 2, data3 = 3, data4 = 4;
timeout_heap_push(h, 100, &data1);
timeout_heap_push(h, 50, &data2); // earliest
timeout_heap_push(h, 150, &data3);
timeout_heap_push(h, 75, &data4);
// Cancel the earliest (root)
timeout_heap_cancel(h, 50, &data2);
// Peek should skip deleted root and give next earliest (75)
TimeoutEntry entry;
int ret = timeout_heap_peek(h, &entry);
TEST_ASSERT(ret == 0, "peek success after root deleted");
TEST_ASSERT(entry.expiration == 75, "peek expiration after skip");
TEST_ASSERT(entry.data == &data4, "peek data after skip");
// Pop should also skip deleted root
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop success after root deleted");
TEST_ASSERT(entry.expiration == 75, "pop expiration after skip");
// Next pop should be 100
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop second");
TEST_ASSERT(entry.expiration == 100, "second pop expiration");
// Cancel 150, then push another earlier
timeout_heap_cancel(h, 150, &data3);
int data5 = 5;
timeout_heap_push(h, 60, &data5); // earlier than 150 but after 100
// Pop should give 60
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop after new push");
TEST_ASSERT(entry.expiration == 60, "expiration of newly pushed");
// Heap empty (150 deleted)
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == -1, "heap empty after deleted remaining");
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Test 6: capacity growth
static void test_growth(void) {
printf("Test 6: capacity growth...\n");
TimeoutHeap *h = timeout_heap_create(2); // tiny capacity
TEST_ASSERT(h != NULL, "heap creation");
TEST_ASSERT(h->capacity == 2, "initial capacity");
// Push 10 elements
for (int i = 0; i < 10; i++) {
int ret = timeout_heap_push(h, i * 10, NULL);
TEST_ASSERT(ret == 0, "push success");
}
TEST_ASSERT(h->size == 10, "size after pushes");
TEST_ASSERT(h->capacity >= 10, "capacity grown");
// Pop all, verify order
uint64_t prev = 0;
for (int i = 0; i < 10; i++) {
TimeoutEntry entry;
int ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop success");
TEST_ASSERT(entry.expiration == prev, "order after growth");
prev = entry.expiration + 10;
}
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Comparison function for HeapElement
static int compare_elements(const void *a, const void *b) {
const HeapElement *ea = (const HeapElement *)a;
const HeapElement *eb = (const HeapElement *)b;
if (ea->expiration < eb->expiration) return -1;
if (ea->expiration > eb->expiration) return 1;
return 0;
}
// Test 8: large number of elements, random order, with some cancellations
static void test_large_random(size_t num_elements, size_t cancel_percent) {
printf("Test 8: large random test (%zu elements, cancel %zu%%)...\n",
num_elements, cancel_percent);
TimeoutHeap *h = timeout_heap_create(100);
TEST_ASSERT(h != NULL, "heap creation");
// Generate elements with unique expiration values
HeapElement *elems = malloc(num_elements * sizeof(HeapElement));
TEST_ASSERT(elems != NULL, "alloc elems");
// Create array of unique expiration values
uint64_t *expirations = malloc(num_elements * sizeof(uint64_t));
TEST_ASSERT(expirations != NULL, "alloc expirations");
for (size_t i = 0; i < num_elements; i++) {
expirations[i] = i; // base unique value
}
// Shuffle using Fisher-Yates
srand((unsigned int)time(NULL) ^ 0x1234);
for (size_t i = num_elements - 1; i > 0; i--) {
size_t j = rand() % (i + 1);
uint64_t tmp = expirations[i];
expirations[i] = expirations[j];
expirations[j] = tmp;
}
for (size_t i = 0; i < num_elements; i++) {
elems[i].expiration = expirations[i];
elems[i].data = malloc(1); // unique pointer
elems[i].cancelled = 0;
int ret = timeout_heap_push(h, elems[i].expiration, elems[i].data);
TEST_ASSERT(ret == 0, "push success");
}
free(expirations);
// Cancel some random elements
size_t cancel_count = num_elements * cancel_percent / 100;
for (size_t i = 0; i < cancel_count; i++) {
size_t idx = rand() % num_elements;
if (!elems[idx].cancelled) {
int ret = timeout_heap_cancel(h, elems[idx].expiration, elems[idx].data);
TEST_ASSERT(ret == 0, "cancel success");
elems[idx].cancelled = 1;
}
}
// Build array of non-cancelled elements, sort by expiration
HeapElement *remaining = malloc(num_elements * sizeof(HeapElement));
TEST_ASSERT(remaining != NULL, "alloc remaining");
size_t remain_count = 0;
for (size_t i = 0; i < num_elements; i++) {
if (!elems[i].cancelled) {
remaining[remain_count++] = elems[i];
}
}
qsort(remaining, remain_count, sizeof(HeapElement), compare_elements);
// Pop and verify order
uint64_t last_expiration = 0;
for (size_t i = 0; i < remain_count; i++) {
TimeoutEntry entry;
int ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop success");
TEST_ASSERT(entry.expiration == remaining[i].expiration, "expiration order");
TEST_ASSERT(entry.data == remaining[i].data, "data matches");
TEST_ASSERT(entry.expiration >= last_expiration, "non-decreasing");
last_expiration = entry.expiration;
free(entry.data);
}
// Heap empty
TimeoutEntry entry;
int ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == -1, "heap empty after all pops");
// Free cancelled elements data
for (size_t i = 0; i < num_elements; i++) {
if (elems[i].cancelled) {
free(elems[i].data);
}
}
free(elems);
free(remaining);
timeout_heap_destroy(h);
printf(" Passed\n");
}
// Test 7: peek should not remove
static void test_peek_no_remove(void) {
printf("Test 7: peek does not remove...\n");
TimeoutHeap *h = timeout_heap_create(10);
TEST_ASSERT(h != NULL, "heap creation");
int data1 = 1, data2 = 2;
timeout_heap_push(h, 100, &data1);
timeout_heap_push(h, 200, &data2);
TimeoutEntry entry;
int ret = timeout_heap_peek(h, &entry);
TEST_ASSERT(ret == 0, "peek success");
TEST_ASSERT(entry.expiration == 100, "peek expiration");
TEST_ASSERT(h->size == 2, "size unchanged after peek");
ret = timeout_heap_peek(h, &entry);
TEST_ASSERT(ret == 0, "second peek success");
TEST_ASSERT(entry.expiration == 100, "peek same element");
// Pop should still give same element
ret = timeout_heap_pop(h, &entry);
TEST_ASSERT(ret == 0, "pop after peek");
TEST_ASSERT(entry.expiration == 100, "pop expiration");
timeout_heap_destroy(h);
printf(" Passed\n");
}
int main(void) {
printf("=== Timeout Heap Tests ===\n");
test_basic();
test_ordering(100); // moderate number
test_ordering(1000); // more elements
test_cancel();
test_duplicate_expirations();
test_deleted_root();
test_growth();
test_peek_no_remove();
test_large_random(5000, 10); // 5000 elements, 10% cancelled
test_large_random(10000, 5); // 10000 elements, 5% cancelled
printf("\n=== All tests passed! ===\n");
return 0;
}

258
net_emulator/test_uasync_random.c

@ -1,258 +0,0 @@
// test_uasync_random.c - Test uasync instance with random timeouts
#include "u_async.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAX_TIMEOUTS 1000
#define MAX_TIMEOUT_TB 10000 // 1 second in timebase units (0.1 ms)
#define CANCEL_PROBABILITY 20 // 20% chance to cancel a timeout
#define NUM_ITERATIONS 50000
typedef struct {
int id;
int fired;
int cancelled;
uasync_t* ua;
} test_context_t;
static int g_fired_count = 0;
static int g_cancelled_count = 0;
static int g_expected_fired = 0;
static void timeout_callback(void* arg) {
test_context_t* ctx = (test_context_t*)arg;
if (!ctx) return;
if (ctx->cancelled) {
printf("ERROR: Cancelled timeout %d fired!\n", ctx->id);
exit(1);
}
ctx->fired = 1;
g_fired_count++;
// Verify the timeout was removed from heap
// (we can't directly check, but we trust the implementation)
}
static void run_random_test(void) {
printf("=== Random uasync timeout test ===\n");
// Create uasync instance
uasync_t* ua = uasync_create();
if (!ua) {
printf("FAIL: uasync_create returned NULL\n");
exit(1);
}
printf("Created uasync instance\n");
// Allocate test contexts
test_context_t* contexts = malloc(MAX_TIMEOUTS * sizeof(test_context_t));
if (!contexts) {
printf("FAIL: malloc failed\n");
uasync_destroy(ua);
exit(1);
}
memset(contexts, 0, MAX_TIMEOUTS * sizeof(test_context_t));
srand(time(NULL));
// Set random timeouts
int active_timeouts = 0;
for (int i = 0; i < MAX_TIMEOUTS; i++) {
contexts[i].id = i;
contexts[i].ua = ua;
// Random timeout between 1 and MAX_TIMEOUT_TB
int timeout_tb = (rand() % MAX_TIMEOUT_TB) + 1;
void* handle = uasync_set_timeout(ua, timeout_tb, &contexts[i], timeout_callback);
if (!handle) {
printf("WARN: uasync_set_timeout failed for timeout %d\n", i);
continue;
}
// Store handle in context (we could store it, but we'll just track)
contexts[i].fired = 0;
contexts[i].cancelled = 0;
active_timeouts++;
// Randomly cancel some timeouts
if (rand() % 100 < CANCEL_PROBABILITY) {
if (uasync_cancel_timeout(ua, handle) == ERR_OK) {
contexts[i].cancelled = 1;
g_cancelled_count++;
active_timeouts--;
} else {
printf("WARN: Failed to cancel timeout %d\n", i);
}
}
}
g_expected_fired = active_timeouts;
printf("Set %d timeouts, cancelled %d, expecting %d to fire\n",
MAX_TIMEOUTS, g_cancelled_count, g_expected_fired);
// Run event loop for enough time to fire all timeouts
// Calculate maximum timeout in milliseconds plus some margin
int max_timeout_ms = (MAX_TIMEOUT_TB + 1000) / 10; // convert to ms with margin
int iterations = 0;
while (g_fired_count < g_expected_fired && iterations < NUM_ITERATIONS) {
// Poll with short timeout (10ms = 100 timebase units)
uasync_poll(ua, 100);
iterations++;
// Check if we've waited long enough
if (iterations * 10 > max_timeout_ms * 5) {
// We've waited five times the max timeout, something might be wrong
if (g_fired_count < g_expected_fired) {
printf("WARN: Only %d of %d timeouts fired after %d ms\n",
g_fired_count, g_expected_fired, iterations * 10);
break;
}
}
}
// Verify all non-cancelled timeouts fired
int failed_fires = 0;
for (int i = 0; i < MAX_TIMEOUTS; i++) {
if (!contexts[i].cancelled && !contexts[i].fired) {
failed_fires++;
if (failed_fires < 10) { // Limit output
printf("ERROR: Timeout %d didn't fire (not cancelled)\n", i);
}
}
if (contexts[i].cancelled && contexts[i].fired) {
printf("ERROR: Cancelled timeout %d fired\n", i);
failed_fires++;
}
}
if (failed_fires > 0) {
printf("FAIL: %d timeout failures\n", failed_fires);
} else {
printf("PASS: All %d expected timeouts fired correctly\n", g_fired_count);
}
// Verify that no cancelled timeouts fired (already checked above)
printf("Cancelled timeouts: %d (none should fire)\n", g_cancelled_count);
// Cleanup
free(contexts);
uasync_destroy(ua);
if (failed_fires == 0) {
printf("=== Test PASSED ===\n");
} else {
printf("=== Test FAILED ===\n");
exit(1);
}
}
static void simple_callback(void* arg) {
int* flag = (int*)arg;
*flag = 1;
}
static void free_callback(void* arg) {
free(arg);
}
static void test_instance_isolation(void) {
printf("\n=== Instance isolation test ===\n");
// Create two independent instances
uasync_t* ua1 = uasync_create();
uasync_t* ua2 = uasync_create();
if (!ua1 || !ua2) {
printf("FAIL: Failed to create instances\n");
exit(1);
}
int fired1 = 0;
int fired2 = 0;
void* timeout1 = uasync_set_timeout(ua1, 10, &fired1, simple_callback);
void* timeout2 = uasync_set_timeout(ua2, 20, &fired2, simple_callback);
if (!timeout1 || !timeout2) {
printf("FAIL: Failed to set timeouts\n");
exit(1);
}
// Run first instance - should fire timeout1 only
uasync_poll(ua1, 50); // 5ms wait
if (fired1 != 1) {
printf("FAIL: Instance 1 timeout didn't fire\n");
exit(1);
}
if (fired2 != 0) {
printf("FAIL: Instance 2 timeout fired incorrectly\n");
exit(1);
}
// Cancel timeout2
if (uasync_cancel_timeout(ua2, timeout2) != ERR_OK) {
printf("FAIL: Failed to cancel timeout2\n");
exit(1);
}
// Run second instance - nothing should fire
fired1 = 0;
uasync_poll(ua2, 50);
if (fired1 != 0) {
printf("FAIL: Instance 1 timeout fired from instance 2\n");
exit(1);
}
if (fired2 != 0) {
printf("FAIL: Cancelled timeout fired\n");
exit(1);
}
uasync_destroy(ua1);
uasync_destroy(ua2);
printf("PASS: Instance isolation works correctly\n");
}
static void test_memory_cleanup(void) {
printf("\n=== Memory cleanup test ===\n");
uasync_t* ua = uasync_create();
if (!ua) {
printf("FAIL: uasync_create failed\n");
exit(1);
}
// Set many timeouts
for (int i = 0; i < 100; i++) {
int* counter = malloc(sizeof(int));
*counter = i;
uasync_set_timeout(ua, 1000 + i, counter, free_callback);
}
// Destroy instance without waiting for timeouts
// This should free all pending timeout nodes
uasync_destroy(ua);
printf("PASS: Memory cleanup completed\n");
}
int main(void) {
printf("Starting uasync random timeout tests\n");
printf("====================================\n");
run_random_test();
test_instance_isolation();
test_memory_cleanup();
printf("\n====================================\n");
printf("All tests PASSED!\n");
return 0;
}

134
net_emulator/timeout_heap.c

@ -1,134 +0,0 @@
// timeout_heap.c
#include "timeout_heap.h"
#include <stdlib.h>
#include <stdio.h> // For potential error printing, optional
// Helper macros for 1-based indices
#define PARENT(i) ((i) / 2)
#define LEFT_CHILD(i) (2 * (i))
#define RIGHT_CHILD(i) (2 * (i) + 1)
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
TimeoutHeap *h = malloc(sizeof(TimeoutHeap));
if (!h) return NULL;
h->heap = malloc(sizeof(TimeoutEntry) * initial_capacity);
if (!h->heap) {
free(h);
return NULL;
}
h->size = 0;
h->capacity = initial_capacity;
return h;
}
void timeout_heap_destroy(TimeoutHeap *h) {
if (h) {
free(h->heap);
free(h);
}
}
static void bubble_up(TimeoutHeap *h, size_t i) {
// i is 1-based
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
// Swap with parent
TimeoutEntry temp = h->heap[PARENT(i) - 1];
h->heap[PARENT(i) - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
i = PARENT(i);
}
}
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
TimeoutEntry *new_heap = realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
if (!new_heap) return -1; // Allocation failed
h->heap = new_heap;
h->capacity = new_cap;
}
// Insert at end (0-based)
size_t idx = h->size++;
h->heap[idx].expiration = expiration;
h->heap[idx].data = data;
h->heap[idx].deleted = 0;
// Bubble up (1-based)
bubble_up(h, idx + 1);
return 0;
}
static void heapify_down(TimeoutHeap *h, size_t i) {
// i is 1-based
while (1) {
size_t smallest = i;
size_t left = LEFT_CHILD(i);
size_t right = RIGHT_CHILD(i);
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = left;
}
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = right;
}
if (smallest == i) break;
// Swap
TimeoutEntry temp = h->heap[smallest - 1];
h->heap[smallest - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
i = smallest;
}
}
static void remove_root(TimeoutHeap *h) {
if (h->size == 0) return;
// Move last to root
h->heap[0] = h->heap[--h->size];
// Heapify down (1-based)
if (h->size > 0) {
heapify_down(h, 1);
}
}
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
if (h->size == 0) return -1;
// Skip deleted
size_t i = 0;
while (i < h->size && h->heap[0].deleted) {
remove_root(h);
}
if (h->size == 0) return -1;
*out = h->heap[0];
return 0;
}
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
if (h->size == 0) return -1;
// Skip deleted
while (h->size > 0 && h->heap[0].deleted) {
remove_root(h);
}
if (h->size == 0) return -1;
*out = h->heap[0];
remove_root(h);
return 0;
}
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
for (size_t i = 0; i < h->size; ++i) {
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
h->heap[i].deleted = 1;
return 0;
}
}
return -1; // Not found
}

72
net_emulator/timeout_heap.h

@ -1,72 +0,0 @@
// timeout_heap.h
#ifndef TIMEOUT_HEAP_H
#define TIMEOUT_HEAP_H
#include <stdint.h> // For uint64_t
#include <stddef.h> // For size_t
typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
typedef struct {
TimeoutTime expiration; // Sort key (smaller = earlier)
void *data; // User data (e.g., callback or ID)
int deleted; // 0 = active, 1 = deleted
} TimeoutEntry;
typedef struct {
TimeoutEntry *heap; // Dynamic array
size_t size; // Current number of elements
size_t capacity; // Allocated size
} TimeoutHeap;
/**
* Create a new timeout heap with initial capacity.
* @param initial_capacity Starting capacity (will grow as needed).
* @return Pointer to the heap, or NULL on failure.
*/
TimeoutHeap *timeout_heap_create(size_t initial_capacity);
/**
* Destroy the timeout heap and free resources.
* @param h The heap to destroy.
*/
void timeout_heap_destroy(TimeoutHeap *h);
/**
* Insert a new timeout into the heap.
* @param h The heap.
* @param expiration The expiration time.
* @param data User data associated with the timeout.
* @return 0 on success, -1 on allocation failure.
*/
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data);
/**
* Peek at the earliest non-deleted timeout without removing it.
* @param h The heap.
* @param out Where to store the entry.
* @return 0 on success, -1 if empty.
*/
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out);
/**
* Pop the earliest non-deleted timeout from the heap.
* @param h The heap.
* @param out Where to store the entry.
* @return 0 on success, -1 if empty.
*/
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
/**
* Cancel a timeout by matching expiration and data.
* Scans the heap linearly, so O(n) time.
* Assumes combinations are unique; cancels the first match.
* @param h The heap.
* @param expiration The expiration time to match.
* @param data The data to match.
* @return 0 if found and canceled, -1 if not found.
*/
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
#endif // TIMEOUT_HEAP_H

391
net_emulator/u_async.c

@ -1,391 +0,0 @@
// 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;
};
// 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];
};
// 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;
}
// Instance management
uasync_t* uasync_create(void) {
uasync_t* ua = malloc(sizeof(uasync_t));
if (!ua) return NULL;
memset(ua, 0, sizeof(uasync_t));
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;
}
return ua;
}
void uasync_destroy(uasync_t* ua) {
if (!ua) return;
// Free all remaining timeouts
if (ua->timeout_heap) {
while (1) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
struct timeout_node* node = (struct timeout_node*)entry.data;
free(node);
}
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;
free(cur);
cur = next;
}
free(ua);
}
void uasync_init(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);
}
}
// Process expired timeouts
static void process_timeouts(uasync_t* ua) {
if (!ua || !ua->timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
int processed = 0;
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// DEBUG
// printf("process_timeouts: expiration=%lu now=%lu diff=%ld heap_size=%zu\n",
// entry.expiration, now_ms, (long)(now_ms - entry.expiration),
// ua->timeout_heap->size);
// Pop the expired timeout
timeout_heap_pop(ua->timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) {
node->callback(node->arg);
processed++;
} else {
printf("WARN: timeout node missing callback\n");
}
free(node);
}
if (processed > 0) {
// printf("process_timeouts: processed %d timeouts, now_ms=%lu heap_size=%zu\n",
// processed, now_ms, ua->timeout_heap->size);
}
}
// Compute time to next timeout
static void get_next_timeout(uasync_t* ua, struct timeval* tv) {
if (!ua || !ua->timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
if (entry.expiration <= now_ms) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
uint64_t delta_ms = entry.expiration - now_ms;
if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow
delta_ms = 86400000;
}
tv->tv_sec = delta_ms / 1000;
tv->tv_usec = (delta_ms % 1000) * 1000;
// DEBUG
// printf("get_next_timeout: expiration=%lu now=%lu delta=%lu sec=%ld usec=%ld\n",
// entry.expiration, now_ms, delta_ms, (long)tv->tv_sec, (long)tv->tv_usec);
}
void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
// printf("SETTIMEOUT called\n");
// fflush(stdout);
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;
node->arg = arg;
node->callback = callback;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
uint64_t now_ms = timeval_to_ms(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
// DEBUG
// printf("set_timeout: tb=%d (%.2fms), now=%lu, expiration=%lu, delta=%ldms\n",
// timeout_tb, timeout_tb/10.0, now_ms, node->expiration_ms,
// (long)(node->expiration_ms - now_ms));
// Insert into heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
free(node);
return NULL;
}
return node;
}
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(ua->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(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;
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 = ua->socket_head;
ua->socket_head = node;
// Update masters (point 1)
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)
ua->fd_to_node[fd] = node;
if (fd > ua->max_fd) ua->max_fd = fd;
return node;
}
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 = ua->socket_head;
struct socket_node* prev = NULL;
while (cur) {
if (cur == node) {
if (prev) {
prev->next = cur->next;
} else {
ua->socket_head = cur->next;
}
// Update masters (point 1)
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)
ua->fd_to_node[node->fd] = NULL;
free(cur);
// Update max_fd (simple rescan; optimize if needed by checking if removed == max_fd)
ua->max_fd = -1;
cur = ua->socket_head;
while (cur) {
if (cur->fd > ua->max_fd) ua->max_fd = cur->fd;
cur = cur->next;
}
return ERR_OK;
}
prev = cur;
cur = cur->next;
}
return ERR_FAIL;
}
void uasync_mainloop(uasync_t* ua) {
while (1) {
uasync_poll(ua, -1); /* infinite timeout */
}
}
void uasync_poll(uasync_t* ua, int timeout_tb) {
if (!ua) return;
/* Process expired timeouts */
process_timeouts(ua);
/* Prepare select with copies of masters */
fd_set readfds = ua->master_readfds;
fd_set writefds = ua->master_writefds;
fd_set exceptfds = ua->master_exceptfds;
struct timeval tv;
get_next_timeout(ua, &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 && (!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)) {
tv = user_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(ua->max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
if (nfds < 0) {
if (errno == EINTR) return;
perror("select");
return;
}
/* Process sockets with faster dispatch */
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)) {
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--;
}
}
/* Process timeouts that may have expired during select. тайм-ауты после данных. */
process_timeouts(ua);
}

45
net_emulator/u_async.h

@ -1,45 +0,0 @@
// uasync.h
// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает.
#ifndef UASYNC_H
#define UASYNC_H
#include <sys/time.h>
#include <sys/select.h>
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket
// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е
// Error type
typedef int err_t;
#define ERR_OK 0
#define ERR_FAIL -1
// Opaque uasync instance handle
typedef struct uasync_s uasync_t;
// Instance management
uasync_t* uasync_create(void);
void uasync_destroy(uasync_t* ua);
void uasync_init(uasync_t* ua); // Alternative: initialize existing instance
// Main loop
void uasync_mainloop(uasync_t* ua);// бесконечный цикл, __noreturn
// Timeouts, timebase = 0.1 mS
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(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(uasync_t* ua, int timeout_tb);
#endif // UASYNC_H

1
pkt_normalizer.h

@ -1 +0,0 @@
src/pkt_normalizer.h

1
routing.h

@ -1 +0,0 @@
src/routing.h

1
sc_lib.h

@ -1 +0,0 @@
src/sc_lib.h

1
settings.h

@ -1 +0,0 @@
src/settings.h

2842
stress.log

File diff suppressed because it is too large Load Diff

2834
stress.out

File diff suppressed because it is too large Load Diff

1355
stress_50.log

File diff suppressed because it is too large Load Diff

1098
stress_new.log

File diff suppressed because it is too large Load Diff

2
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>

12
tests/test_new_features.c

@ -4,12 +4,12 @@
// 3. Сброс соединения ETCP (0x02/0x03) с повторными попытками
// 4. Функция conn_reset() для всей цепочки
#include "../ll_queue.h"
#include "../pkt_normalizer.h"
#include "../etcp.h"
#include "../connection.h"
#include "../settings.h"
#include "../u_async.h"
#include "ll_queue.h"
#include "pkt_normalizer.h"
#include "etcp.h"
#include "connection.h"
#include "settings.h"
#include "u_async.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

1
timeout_heap.h

@ -1 +0,0 @@
src/timeout_heap.h

1
tun_if.h

@ -1 +0,0 @@
src/tun_if.h

1
u_async.h

@ -1 +0,0 @@
src/u_async.h

0
src/timeout_heap.c → u_async/timeout_heap.c

0
src/timeout_heap.h → u_async/timeout_heap.h

0
src/u_async.c → u_async/u_async.c

0
src/u_async.h → u_async/u_async.h

1
utun_state.h

@ -1 +0,0 @@
src/utun_state.h
Loading…
Cancel
Save